Поиск…


Вступление

Отражение обычно используется программами, которые требуют возможности исследовать или модифицировать поведение среды выполнения приложений, запущенных в JVM. Java Reflection API используется для этой цели, где он позволяет проверять классы, интерфейсы, поля и методы во время выполнения, не зная их имена во время компиляции. А также позволяет создавать новые объекты и вызывать методы с использованием отражения.

замечания

Спектакль

Имейте в виду, что отражение может снизить производительность, использовать его только тогда, когда ваша задача не может быть выполнена без отражения.

Из учебника Java API Reflection :

Поскольку отражение включает типы, которые динамически разрешены, некоторые оптимизации виртуальной машины Java не могут быть выполнены. Следовательно, рефлексивные операции имеют более низкую производительность, чем их неотражающие аналоги, и их следует избегать в разделах кода, которые часто называются приложениями, чувствительными к характеристикам.

Вступление

основы

API Reflection позволяет проверять структуру класса кода во время выполнения и динамически вызывать код. Это очень мощно, но это также опасно, поскольку компилятор не может статически определять, действительны ли динамические вызовы.

Простым примером могло бы стать создание публичных конструкторов и методов данного класса:

import java.lang.reflect.Constructor;
import java.lang.reflect.Method;

// This is a object representing the String class (not an instance of String!)
Class<String> clazz = String.class;

Constructor<?>[] constructors = clazz.getConstructors(); // returns all public constructors of String
Method[] methods = clazz.getMethods(); // returns all public methods from String and parents

С помощью этой информации можно указать объект и динамически вызвать разные методы.

Отражение и общие типы

Информация общего типа доступна для:

  • параметры метода, используя getGenericParameterTypes() .
  • методы возвращают типы, используя getGenericReturnType() .
  • публичные поля, используя getGenericType .

В следующем примере показано, как извлечь информацию об общем типе во всех трех случаях:

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.List;
import java.util.Map;

public class GenericTest {

    public static void main(final String[] args) throws Exception {
        final Method method = GenericTest.class.getMethod("testMethod", Map.class);
        final Field field = GenericTest.class.getField("testField");

        System.out.println("Method parameter:");
        final Type parameterType = method.getGenericParameterTypes()[0];
        displayGenericType(parameterType, "\t");

        System.out.println("Method return type:");
        final Type returnType = method.getGenericReturnType();
        displayGenericType(returnType, "\t");

        System.out.println("Field type:");
        final Type fieldType = field.getGenericType();
        displayGenericType(fieldType, "\t");

    }

    private static void displayGenericType(final Type type, final String prefix) {
        System.out.println(prefix + type.getTypeName());
        if (type instanceof ParameterizedType) {
            for (final Type subtype : ((ParameterizedType) type).getActualTypeArguments()) {
                displayGenericType(subtype, prefix + "\t");
            }
        }

    }

    public Map<String, Map<Integer, List<String>>> testField;

    public List<Number> testMethod(final Map<String, Double> arg) {
        return null;
    }

}

Это приводит к следующему результату:

Method parameter:
    java.util.Map<java.lang.String, java.lang.Double>
        java.lang.String
        java.lang.Double
Method return type:
    java.util.List<java.lang.Number>
        java.lang.Number
Field type:
    java.util.Map<java.lang.String, java.util.Map<java.lang.Integer, java.util.List<java.lang.String>>>
        java.lang.String
        java.util.Map<java.lang.Integer, java.util.List<java.lang.String>>
            java.lang.Integer
            java.util.List<java.lang.String>
                java.lang.String

Вызов метода

Используя отражение, метод объекта может быть вызван во время выполнения.

В этом примере показано, как вызвать методы объекта String .

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

String s = "Hello World!";

// method without parameters
// invoke s.length()
Method method1 = String.class.getMethod("length");
int length = (int) method1.invoke(s); // variable length contains "12"

// method with parameters
// invoke s.substring(6)
Method method2 = String.class.getMethod("substring", int.class);
String substring = (String) method2.invoke(s, 6); // variable substring contains "World!"

Получение и настройка полей

Используя Reflection API, можно изменить или получить значение поля во время выполнения. Например, вы можете использовать его в API для извлечения разных полей на основе фактора, например ОС. Вы также можете удалить модификаторы, такие как final чтобы поля модификации были окончательными.

Для этого вам нужно будет использовать метод Class # getField () таким образом, который показан ниже:

// Get the field in class SomeClass "NAME".
Field nameField = SomeClass.class.getDeclaredField("NAME");

// Get the field in class Field "modifiers". Note that it does not 
// need to be static
Field modifiersField = Field.class.getDeclaredField("modifiers");

// Allow access from anyone even if it's declared private
modifiersField.setAccessible(true);

// Get the modifiers on the "NAME" field as an int.
int existingModifiersOnNameField = nameField.getModifiers();

// Bitwise AND NOT Modifier.FINAL (16) on the existing modifiers
// Readup here https://en.wikipedia.org/wiki/Bitwise_operations_in_C
// if you're unsure what bitwise operations are.
int newModifiersOnNameField = existingModifiersOnNameField & ~Modifier.FINAL;

// Set the value of the modifiers field under an object for non-static fields
modifiersField.setInt(nameField, newModifiersOnNameField);

// Set it to be accessible. This overrides normal Java 
// private/protected/package/etc access control checks.
nameField.setAccessible(true);

 // Set the value of "NAME" here. Note the null argument. 
 // Pass null when modifying static fields, as there is no instance object
nameField.set(null, "Hacked by reflection...");

// Here I can directly access it. If needed, use reflection to get it. (Below)
System.out.println(SomeClass.NAME);

Получение полей намного проще. Мы можем использовать Field # get () и его варианты, чтобы получить его значение:

// Get the field in class SomeClass "NAME".
Field nameField = SomeClass.class.getDeclaredField("NAME");

// Set accessible for private fields
nameField.setAccessible(true);

// Pass null as there is no instance, remember?
String name = (String) nameField.get(null);

Обратите внимание:

При использовании класса # getDeclaredField используйте его для получения поля в самом классе:

class HackMe extends Hacked {
    public String iAmDeclared;
}

class Hacked {
    public String someState;
}

Здесь HackMe#iAmDeclared является объявленным полем. Однако HackMe#someState не является объявленным полем, поскольку он унаследован от своего суперкласса Hacked.

Конструктор вызовов

Получение объекта-конструктора

Класс Constructor можно получить из объекта Class следующим образом:

Class myClass = ... // get a class object
Constructor[] constructors = myClass.getConstructors();

Если переменная constructors будет иметь один экземпляр Constructor для каждого публичного конструктора, объявленного в классе.

Если вы знаете точные типы параметров конструктора, к которому хотите получить доступ, вы можете отфильтровать конкретный конструктор. Следующий пример возвращает публичный конструктор данного класса, который принимает параметр Integer as:

Class myClass = ... // get a class object
Constructor constructor = myClass.getConstructor(new Class[]{Integer.class});

Если конструктор не соответствует заданным аргументам конструктора, NoSuchMethodException .

Новый экземпляр с использованием объекта «Конструктор»

Class myClass = MyObj.class // get a class object
Constructor constructor = myClass.getConstructor(Integer.class);
MyObj myObj = (MyObj) constructor.newInstance(Integer.valueOf(123));

Получение констант перечисления

Предоставление этого перечисления в качестве примера:

enum Compass {
    NORTH(0),
    EAST(90),
    SOUTH(180),
    WEST(270);
    private int degree;
    Compass(int deg){
        degree = deg;
    }
    public int getDegree(){
        return degree;
    }
}

В Java класс enum похож на любой другой класс, но имеет определенные определенные константы для значений enum. Кроме того, у него есть поле, которое представляет собой массив, который содержит все значения и два статических метода с именами values() и valueOf(String) .
Мы можем это увидеть, если мы используем Reflection для печати всех полей этого класса

for(Field f : Compass.class.getDeclaredFields())
    System.out.println(f.getName());

выход будет:

К СЕВЕРУ
ВОСТОК
ЮЖНЫЙ
WEST
степень
ENUM $ ЗНАЧЕНИЯ

Поэтому мы могли бы изучить классы enum с Reflection, как и любой другой класс. Но API Reflection предлагает три метода enum-specific.

проверка перечислений

Compass.class.isEnum();

Возвращает true для классов, представляющих тип перечисления.

получение значений

Object[] values = Compass.class.getEnumConstants();

Возвращает массив всех значений перечисления, таких как Compass.values ​​(), но без необходимости экземпляра.

постоянная проверка enum

for(Field f : Compass.class.getDeclaredFields()){
    if(f.isEnumConstant())
        System.out.println(f.getName());
}

Выводит список всех полей класса, которые являются значениями перечисления.

Получить класс с его (полностью квалифицированным) именем

Учитывая String содержащую имя класса, к объекту Class можно получить доступ с помощью Class.forName :

Class clazz = null;
try {
    clazz = Class.forName("java.lang.Integer");
} catch (ClassNotFoundException ex) {
    throw new IllegalStateException(ex);
}
Java SE 1.2

Он может быть указан, если класс должен быть инициализирован (второй параметр forName ) и какой ClassLoader должен использоваться (третий параметр):

ClassLoader classLoader = ...
boolean initialize = ...
Class clazz = null;
try {
    clazz = Class.forName("java.lang.Integer", initialize, classLoader);
} catch (ClassNotFoundException ex) {
    throw new IllegalStateException(ex);
}

Вызов перегруженных конструкторов с использованием отражения

Пример: вызывать разные конструкторы путем передачи соответствующих параметров

import java.lang.reflect.*;

class NewInstanceWithReflection{
    public NewInstanceWithReflection(){
        System.out.println("Default constructor");
    }
    public NewInstanceWithReflection( String a){
        System.out.println("Constructor :String => "+a);
    }
    public static void main(String args[]) throws Exception {
        
        NewInstanceWithReflection object = (NewInstanceWithReflection)Class.forName("NewInstanceWithReflection").newInstance();
        Constructor constructor = NewInstanceWithReflection.class.getDeclaredConstructor( new Class[] {String.class});
        NewInstanceWithReflection object1 = (NewInstanceWithReflection)constructor.newInstance(new Object[]{"StackOverFlow"});
        
    }
}

выход:

Default constructor
Constructor :String => StackOverFlow

Объяснение:

  1. Создать экземпляр класса с использованием Class.forName : он вызывает конструктор по умолчанию
  2. Вызывать getDeclaredConstructor класса, передавая тип параметров как Class array
  3. После получения конструктора создайте newInstance , передав значение параметра как Object array

Неправильное использование API Reflection для изменения частных и конечных переменных

Отражение полезно, когда оно правильно используется для правильной цели. Используя отражение, вы можете получить доступ к закрытым переменным и повторно инициализировать конечные переменные.

Ниже приведен фрагмент кода, который не рекомендуется.

import java.lang.reflect.*;

public class ReflectionDemo{
    public static void main(String args[]){
        try{
            Field[] fields = A.class.getDeclaredFields();
            A a = new A();
            for ( Field field:fields ) {
                if(field.getName().equalsIgnoreCase("name")){
                    field.setAccessible(true);
                    field.set(a, "StackOverFlow");
                    System.out.println("A.name="+field.get(a));
                }
                if(field.getName().equalsIgnoreCase("age")){
                    field.set(a, 20);
                    System.out.println("A.age="+field.get(a));
                }
                if(field.getName().equalsIgnoreCase("rep")){
                    field.setAccessible(true);
                    field.set(a,"New Reputation");
                    System.out.println("A.rep="+field.get(a));
                }
                if(field.getName().equalsIgnoreCase("count")){
                    field.set(a,25);
                    System.out.println("A.count="+field.get(a));
                }
            }                
        }catch(Exception err){
            err.printStackTrace();
        }
    }
}

class A {
    private String name;
    public int age;
    public final String rep;
    public static int count=0;
    
    public A(){
        name = "Unset";
        age = 0;
        rep = "Reputation";
        count++;
    }
}

Выход:

A.name=StackOverFlow
A.age=20
A.rep=New Reputation
A.count=25

Объяснение:

В обычном сценарии private переменные не могут быть доступны за пределами объявленного класса (без методов getter и setter). final переменные не могут быть повторно назначены после инициализации.

Reflection разрывов обоих барьеров можно злоупотреблять, чтобы изменить как частные, так и конечные переменные, как описано выше.

field.setAccessible(true) - ключ к достижению желаемой функциональности.

Конструктор вызовов вложенного класса

Если вы хотите создать экземпляр внутреннего вложенного класса, вам необходимо предоставить объект класса охватывающего класса в качестве дополнительного параметра с классом getDeclaredConstructor .

public class Enclosing{
    public class Nested{
    public Nested(String a){
            System.out.println("Constructor :String => "+a);
        }
    }       
    public static void main(String args[]) throws Exception {
        Class<?> clazzEnclosing = Class.forName("Enclosing");            
        Class<?> clazzNested = Class.forName("Enclosing$Nested");
        Enclosing objEnclosing = (Enclosing)clazzEnclosing.newInstance();
        Constructor<?> constructor = clazzNested.getDeclaredConstructor(new Class[]{Enclosing.class, String.class});
        Nested objInner = (Nested)constructor.newInstance(new Object[]{objEnclosing, "StackOverFlow"});
    }
}

Если вложенный класс является статическим, вам не понадобится этот закрытый экземпляр.

Динамические прокси

Динамические прокси-серверы не имеют особого отношения к Reflection, но они являются частью API. Это в основном способ создания динамической реализации интерфейса. Это может быть полезно при создании макетов.
Динамический прокси - это экземпляр интерфейса, который создается с помощью так называемого обработчика вызовов, который перехватывает все вызовы методов и позволяет обрабатывать их вызов вручную.

public class DynamicProxyTest {

    public interface MyInterface1{
        public void someMethod1();
        public int someMethod2(String s);
    }

    public interface MyInterface2{
        public void anotherMethod();
    }
   
    public static void main(String args[]) throws Exception {
        // the dynamic proxy class 
        Class<?> proxyClass = Proxy.getProxyClass(
                ClassLoader.getSystemClassLoader(),
                new Class[] {MyInterface1.class, MyInterface2.class});
        // the dynamic proxy class constructor
        Constructor<?> proxyConstructor = 
            proxyClass.getConstructor(InvocationHandler.class);

        // the invocation handler
        InvocationHandler handler = new InvocationHandler(){
            // this method is invoked for every proxy method call
            // method is the invoked method, args holds the method parameters
            // it must return the method result
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { 
                String methodName = method.getName();

                if(methodName.equals("someMethod1")){
                    System.out.println("someMethod1 was invoked!");
                    return null;
                }
                if(methodName.equals("someMethod2")){
                    System.out.println("someMethod2 was invoked!");
                    System.out.println("Parameter: " + args[0]);
                    return 42;
                }
                if(methodName.equals("anotherMethod")){
                    System.out.println("anotherMethod was invoked!");
                    return null;
                }
                System.out.println("Unkown method!");
                return null;
            }
        };

        // create the dynamic proxy instances
        MyInterface1 i1 = (MyInterface1) proxyConstructor.newInstance(handler);
        MyInterface2 i2 = (MyInterface2) proxyConstructor.newInstance(handler);

        // and invoke some methods
        i1.someMethod1();
        i1.someMethod2("stackoverflow");
        i2.anotherMethod();
    }
}

Результатом этого кода является следующее:

someMethod1 was invoked!
someMethod2 was invoked!
Parameter: stackoverflow
anotherMethod was invoked!

Злые Java-хаки с отражением

API Reflection можно использовать для изменения значений частных и конечных полей даже в библиотеке по умолчанию JDK. Это можно использовать для управления поведением некоторых известных классов, как мы увидим.

Что не возможно

Давайте сначала начнем с единственного ограничения, это единственное поле, которое мы не можем изменить с помощью Reflection. Это Java SecurityManager . Он объявлен в java.lang.System как

private static volatile SecurityManager security = null;

Но он не будет указан в классе System, если мы запустим этот код

for(Field f : System.class.getDeclaredFields())
    System.out.println(f);

Thats из-за fieldFilterMap в sun.reflect.Reflection который содержит карту и поле безопасности в System.class и защищает их от любого доступа с помощью Reflection. Поэтому мы не смогли деактивировать SecurityManager .

Сумасшедшие струны

Каждая строка Java представлена ​​JVM как экземпляр класса String . Однако в некоторых ситуациях JVM экономит кучу пространства, используя тот же экземпляр для строк. Это происходит для строковых литералов, а также для строк, которые были «интернированы», вызывая String.intern() . Поэтому, если у вас есть "hello" в вашем коде несколько раз, это всегда один и тот же экземпляр объекта.

Строки должны быть неизменными, но можно использовать «злобное» отражение, чтобы изменить их. Пример ниже показывает, как мы можем изменить символы в String, заменив его поле value .

public class CrazyStrings {
    static {
        try {
            Field f = String.class.getDeclaredField("value");
            f.setAccessible(true);
            f.set("hello", "you stink!".toCharArray());
        } catch (Exception e) {
        }
    }
    public static void main(String args[])  {
        System.out.println("hello");
    }
}

Таким образом, этот код напечатает «вы воняете!»

1 = 42

Та же идея может быть использована с классом Integer

public class CrazyMath {
    static {
        try {
            Field value = Integer.class.getDeclaredField("value");    
            value.setAccessible(true);          
            value.setInt(Integer.valueOf(1), 42);      
        } catch (Exception e) {
        }
    }
    public static void main(String args[])  {
        System.out.println(Integer.valueOf(1));
    }
}

Все верно

И, согласно этому сообщению stackoverflow, мы можем использовать отражение, чтобы сделать что-то действительно злое.

public class Evil {    
    static {
        try {
            Field field = Boolean.class.getField("FALSE");
            field.setAccessible(true);
            Field modifiersField = Field.class.getDeclaredField("modifiers");
            modifiersField.setAccessible(true);
            modifiersField.setInt(field, field.getModifiers() & ~Modifier.FINAL);
            field.set(null, true);
        } catch (Exception e) {
        }
    }
    public static void main(String args[]){
        System.out.format("Everything is %s", false);
    }
}

Обратите внимание, что то, что мы делаем здесь, приведет к тому, что JVM будет вести себя необъяснимыми способами. Это очень опасно.



Modified text is an extract of the original Stack Overflow Documentation
Лицензировано согласно CC BY-SA 3.0
Не связан с Stack Overflow