Отражение резюме 4D (Душа кадра)

Java

предисловие

Готовьтесь к Новому году посмотреть исходники Spring, блефовать людей, хахахаха. Серьезно, это значит знать, почему, когда есть проблема. Но прежде чем мы начнем, сначала сделайте некоторые основы. Смотрите сегодня Soul of Frame - Reflection.

Обзор отражений (начиная с основ)

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

Это слишком сухо и бездушно, как две картинки ниже.


Итак, давайте возьмем пример, отказ не имеет души. O(∩_∩)О, ха-ха~

Зачем отражать?

Если у нас нет класса Orange, то класс сообщит об ошибке, что класс не может быть найден при компиляции. Это «ортофото», которое мы обычно используем. Название должно соответствовать отражению, а не официальному термину.


Но у этого есть очевидный недостаток, то есть класс Apple вызывается в основном методе, а класс Orange не вызывается, поэтому он должен вызываться нормально.Когда я хочу вызвать класс Orange, я сообщу об ошибке. Однако это дало обратный эффект, и все пошло не так, как мы думали.

Нам нужна ситуация вызова, которая не проверяет класс во время компиляции, а только вызывает соответствующий класс в соответствии с соответствующими требованиями во время выполнения, что является «отражением».

использование отражения

Наиболее важным применением рефлексии является разработка различных общих рамок.Многие фреймворки (такие как Spring) настроены (например, настройка bean-компонентов через XML-файлы). Чтобы обеспечить универсальность фреймворка, им может потребоваться загрузить разные объекты или классы в соответствии с файлом конфигурации и вызвать разные методы. В этом случае необходимо использовать отражение. Объекты, которые необходимо загрузить, динамически загружаются во время выполнения.

Например, при разработке с использованием фреймворка Struts 2 мы обычноstruts.xmlнастроитьAction,Например:

<action name="login"       
        class="org.ScZyhSoft.test.action.SimpleLoginAction"   
        method="execute">      
      <result>/shop/shop-index.jsp</result>     
      <result name="error">login.jsp</result>
</action> 
файл конфигурации сActionОтношения сопоставления устанавливаются, когда слой просмотра делает запрос, запрос будетStrutsPrepareAndExecuteFilterперехватить, тоStrutsPrepareAndExecuteFilterБудет динамически создавать экземпляры Action. Например, мы просимlogin.action,ТакStrutsPrepareAndExecuteFilterОн проанализирует файл struts.xml, извлечет действие, имя которого — login в действии, создаст экземпляр SimpleLoginAction в соответствии с атрибутом класса и использует метод вызова для вызова метода выполнения.Этот процесс неотделим от отражения.

Три способа получить объект файла класса

Все является объектом.

Apple apple=new Apple();Яблоко в является экземпляром Apple. Какой это экземпляр объекта Apple?

По сути, это экземпляр класса Class.

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


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

  • статический класс свойств

  • Метод getClass класса Object, если вы знаете экземпляр, вызовите его метод getClass напрямую.


  • Статический метод forName() класса Class, параметр — полный путь к классу(推荐使用)


Здесь следует отметить, что получение объекта Class через полное имя пути к классу вызовет исключение, используйте try....catch... для перехвата исключения. Если класс не может быть найден по пути к классу, будет выброшено это исключение.Исходный код метода forName в классе Class выглядит следующим образом:


注:虽然写了三种方式,但平常使用最多,最推荐的是第三种方式,因为第一种方式需要知道类,第二种方式需要知道实例,如果知道了这些,可以直接调用其方法和参数,没必要再用Class来实现功能。举个例子,你从北京去上海,第一种方式直达就行,第二种方式和第三种方式则是先从北京到云南,再从云南到上海,显得太冗余。

Использование отражения

Возьмем в качестве примера класс Apple и воспользуемся эмиссией для получения его параметров и методов. Он имеет три параметра: цвет параметра по умолчанию по умолчанию, размер общедоступного общедоступного параметра и цену частного частного параметра. Существует три конструктора, а именно конструктор по умолчанию по умолчанию, общедоступный параметризованный конструктор с тремя параметрами и закрытый параметризованный конструктор с двумя параметрами. Шесть методов установки/получения являются общедоступными методами: метод установки/получения уровня изоляции цвета по умолчанию, метод установки/получения общедоступного уровня изоляции размера и метод установки/получения частного уровня изоляции цены. . методы toString и setter/getter для трех параметров. Наконец, есть метод toString общедоступного уровня изоляции. Это подробное описание выглядит сложным, но на самом деле оно очень простое Конкретный код выглядит следующим образом:

package com.eastrobot.reflect;

public class Apple extends Fruit{
    String color;//默认default
    public int size;
    private int price;

    Apple() {//默认default
        System.out.println("Apple的无参构造");
    }

    public Apple(String color, int size, int price) {
        this.color = color;
        this.size = size;
        this.price = price;
        System.out.println("Apple的有参构造——三个参数");
    }

    private Apple(String color, int size) {
        this.color = color;
        this.size = size;
        this.price = 10;
        System.out.println("Apple的有参构造——两个参数");
    }

    @Override
    public String toString() {
        return "color:" + color + ",size:" + size + ",price:" + price;
    }

    //默认default
    String getColor() {
        return color;
    }

    public int getSize() {
        return size;
    }

    private int getPrice() {
        return price;
    }

    //默认default
    void setColor(String color) {
        this.color = color;
    }

    public void setSize(int size) {
        this.size = size;
    }

    private void setPrice(int price) {
        this.price = price;
    }
}

Унаследованный родительский класс Fruit, включая вкус параметра открытого типа и его методы установки/получения открытого типа.

public class Fruit {   
 public String taste; 
 public String getTaste() {  
      return taste;    
 }   
 public void setTaste(String taste) {   
     this.taste = taste;  
  }
}

1. Получить все параметры через рефлексию getDeclaredFields

System.out.println("getDeclaredFields**********");
Field[] fields=appleClass.getDeclaredFields();
for(Field field:fields){   
 System.out.println(field.toString());
}

Результаты приведены ниже:


注:不管何种隔离级别,getDeclaredFields都会获取到所有参数。

2. Получить указанный параметр getDeclaredField через отражение

//指定参数
System.out.println("getDeclaredField**********");
Field colorField=appleClass.getDeclaredField("color");
System.out.println("color:"+colorField.toString());

Field sizeField=appleClass.getDeclaredField("size");
System.out.println("size:"+sizeField.toString());

Field priceField=appleClass.getDeclaredField("price");
System.out.println("price:"+priceField.toString());

Результаты приведены ниже:


注:不管何种隔离级别,getDeclaredField可以通过输入值获取指定参数。

3. Получить все параметры публичного типа getFields через отражение

System.out.println("getFields**********");
Field[] fields=appleClass.getFields();
for(Field field:fields){    
    System.out.println(field.toString());
}

Результаты приведены ниже:


注:只能通过反射获取public类型的属性,也包括继承自父类的属性。

4. Получить параметр getField указанного публичного типа через отражение

Field colorField=appleClass.getField("color");
System.out.println("color:"+colorField.toString());

Результаты приведены ниже:


------------------- Разделить строку вручную -------------------

Field sizeField=appleClass.getField("size");
System.out.println("size:"+sizeField.toString());

Результаты приведены ниже:


------------------- Разделить строку вручную -------------------

Field priceField=appleClass.getField("price");
System.out.println("price:"+priceField.toString());

Результаты приведены ниже:


注:只有public类型才能通过getField方法获取到,其他类型均获取不到。

Видя это, некоторые друзья хотят спросить, почему это, причина? Мы не можем запомнить его наизусть, так что мы забываем его через два дня.Если память не устойчива, давайте посмотрим, что сделал нижний слой.

Интерлюдия: почему getFields и getField могут получать поля только общедоступного типа?

Давайте возьмем getField в качестве примера, чтобы увидеть разницу между getDeclaredField и getField.Вы можете видеть, что оба вызывают метод privateGetDeclaredFields, но разница в том, что параметр publicOnly в методе getDeclaredField равен false, а параметр publicOnly в методе getField равен true. .

Метод getDeclaredField:


Метод getField:



Давайте посмотрим, что происходит в privateGetDeclaredFields.


Мы можем видеть, что если оно истинно, то берем поле DeclarationPublicFields, то есть публичное поле, а если ложно, то берем DeclaredFields.

5. Получить все методы через отражение getDeclaredMethods

//所有方法
System.out.println("getDeclaredMethods**********");
Method[] methods=appleClass.getDeclaredMethods();
for(Method method:methods){    
    System.out.println(method.toString());
}

Результаты приведены ниже:


6. Получить указанный метод через рефлексию getDeclaredMethod

//指定方法
System.out.println("getDeclaredMethod**********");

//default
Method getColorMethod=appleClass.getDeclaredMethod("getColor");
System.out.println("getColorMethod:"+getColorMethod.toString());

//public
Method getSizeMethod=appleClass.getDeclaredMethod("getSize");
System.out.println("getSizeMethod:"+getSizeMethod.toString());

//private
Method getPriceMethod=appleClass.getDeclaredMethod("getPrice");
System.out.println("getPriceMethod:"+getPriceMethod.toString());

//父类的public
Method getTasteMethod=appleClass.getDeclaredMethod("getTaste");
System.out.println("getTasteMethod:"+getTasteMethod.toString());

Результаты приведены ниже:


注:getDeclaredMethod只能获取自己定义的方法,不能获取从父类的方法。

7. Получить все публичные типы методов через рефлексию getMethods

//所有方法
System.out.println("getMethods**********");
Method[] methods=appleClass.getMethods();
for(Method method:methods){
    System.out.println(method.toString());
}

Результаты приведены ниже:


注:getMethods可以通过反射获取所有的public方法,包括父类的public方法。

8. Получить метод указанного публичного типа через рефлексию getMethod

//指定方法
System.out.println("getMethod**********");
Method method=appleClass.getMethod("toString");
System.out.println(method.toString());

Результаты приведены ниже:


9. Получить все конструкторы через отражение getDeclaredConstuctors

//构造方法
System.out.println("getDeclaredConstructors**********");
Constructor[] constructors=appleClass.getDeclaredConstructors();
for(Constructor constructor:constructors){   
 System.out.println(constructor.toString());
}

Результаты приведены ниже:


10. Получить конструктор с параметрами через рефлексию getDeclaredConstructor

//指定构造方法
System.out.println("getDeclaredConstructor**********");
Class[] cArg = new Class[3];
cArg[0] = String.class;
cArg[1] = int.class;
cArg[2] = int.class;
Constructor constructor=appleClass.getDeclaredConstructor(cArg);
System.out.println(constructor.toString());

Результаты приведены ниже:


11. Получить все конструкторы открытого типа getConstructors через отражение

System.out.println("getConstructors**********");
Constructor[] constructors=appleClass.getConstructors();
for(Constructor constructor:constructors){
    System.out.println(constructor.toString());
}

результат операции:


12. Получить конструктор getConstructor публичного типа через рефлексию

//构造方法
System.out.println("getConstructor**********");
Constructor constructor1 = appleClass.getConstructor(String.class,int.class,int.class);
System.out.println("public类型的有参构造:" + constructor1.toString());

Constructor constructor2 = appleClass.getConstructor(String.class, int.class);
System.out.println("private类型的有参构造:" + constructor2.toString());

результат операции:


13. Получить объект этого класса, построив без параметров newInstance()

//调用无参构造创建对象
Class appleClass = Class.forName("com.eastrobot.reflect.Apple");
Apple apple=(Apple)appleClass.newInstance();

Результаты приведены ниже:


14. Получить объект этого класса, построив с параметрами newInstance (XXXX)

Constructor constructor=appleClass.getConstructor(String.class,int.class,int.class);
Apple apple=(Apple)constructor.newInstance("红色",10,5);

Результаты приведены ниже:


15. Получите имя класса, включая путь к пакету getName()

String name= appleClass.getName();
System.out.println("name:"+name);

Результаты приведены ниже:


16. Получить имя класса, не содержащее пути пакета getSimpleName()

String simpleName =appleClass.getSimpleName();
System.out.println("simpleName:"+simpleName);

Результаты приведены ниже:


17. Вызовите вызов метода через отражение

//调用无参构造创建对象
Class appleClass = Class.forName("com.eastrobot.reflect.Apple");

//调用有参构造
Constructor constructor = appleClass.getDeclaredConstructor(String.class, int.class, int.class);
Apple apple = (Apple) constructor.newInstance("红色", 10, 20);

//获取toString方法并调用
Method method = appleClass.getDeclaredMethod("toString");
String str=(String)method.invoke(apple);
System.out.println(str);


注:invoke+实例可以调用相关public方法。

18. Определите, может ли метод вызывать isAccessible

//调用无参构造创建对象
Class appleClass = Class.forName("com.eastrobot.reflect.Apple");

//调用有参构造
Constructor constructor = appleClass.getDeclaredConstructor(String.class, int.class, int.class);
Apple apple = (Apple) constructor.newInstance("红色", 10, 20);

//获取public的getSize方法并调用
Method method = appleClass.getDeclaredMethod("getSize");
System.out.println("getSize方法的isAccessible:" + method.isAccessible());
int size = (Integer) method.invoke(apple);
System.out.println("size:" + size);

//获取private的getPrice方法并调用
method = appleClass.getDeclaredMethod("getPrice");
System.out.println("getPrice的isAccessible:" + method.isAccessible());
int price = (Integer) method.invoke(apple);
System.out.println("price:" + price);

результат операции:


注:这样一看,public和private类型的isAccessible都为false,但是public类型的值可以获取到,但是private类型的值并不能获取到。其实isAccessible()值为 true或false,是指启用和禁用访问安全检查的开关,如果为true,则取消安全检查,为false,则执行安全检查。如上,两者都为false,说明两者的进行了安全检查,getSize为public类型,则可以获取值,而getPrice为private,则不能获取值。

19. Установите переключатель проверки безопасности setAccessible

//调用无参构造创建对象
Class appleClass = Class.forName("com.eastrobot.reflect.Apple");

//调用有参构造
Constructor constructor = appleClass.getDeclaredConstructor(String.class, int.class, int.class);
Apple apple = (Apple) constructor.newInstance("红色", 10, 20);

//获取price
Method otherMethod = appleClass.getDeclaredMethod("getPrice");
System.out.println("getPrice方法的isAccessible:" + otherMethod.isAccessible());
otherMethod.setAccessible(true);
int price = (Integer) otherMethod.invoke(apple);
System.out.println("之前的price:" + price);

//重新设置price
Method method = appleClass.getDeclaredMethod("setPrice", int.class);
System.out.println("isAccessible:" + method.isAccessible());
method.setAccessible(true);
method.invoke(apple, 100);

//再次获取price
otherMethod = appleClass.getDeclaredMethod("getPrice");
otherMethod.setAccessible(true);
price = (Integer) otherMethod.invoke(apple);
System.out.println("之后的price:" + price);

результат операции:


注:setAccessible(true)表示取消安全检查,setAccessible(false)表示启用安全检查。

Ответы на распространенные вопросы интервью (начало продвинутой части)

Обязательно ли для отраженного класса требуется конструктор без аргументов?

Разные. Поскольку параметризованный метод построения также может быть отражен, конкретный код выглядит следующим образом:

Constructor constructor=appleClass.getConstructor(String.class,int.class,int.class);
Apple apple=(Apple)constructor.newInstance("红色",10,5);

Каковы преимущества и недостатки использования отражения?

Преимущество:

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

Недостаток:

Использование отражения — это в основном операция интерпретации, которая используется при доступе к полям и методам.远慢于直接代码.

использовать отражение模糊程序内部逻辑, программист надеется увидеть логику программы в исходном коде, а технология отражения и т.п. обходит исходный код, что принесет проблемы с сопровождением. (Вот почему так сложно посмотреть исходный код? Эй...)

Почему говорят, что отражение уменьшает связь?

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

Отражение сравнивает показатели потерь, почему вы так говорите? (акцент)

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

Ниже приведена разница в стоимости прямого получения экземпляра, прямого метода получения, отражения, получающего экземпляр, и отражения, получающего метод, соответственно, выполняющегося 1 миллион раз.

try {    
    //直接获取实例
    long startTime1 = System.currentTimeMillis();   
    for (int i = 0; i < 1000000; i++) { 
       new Apple();   
    }  
    long endTime1 = System.currentTimeMillis();  
    System.out.println("直接获取实例时间:" + (endTime1 - startTime1));   
 
    //直接获取方法  
    long startTime2= System.currentTimeMillis();   
    for (int i = 0; i < 1000000; i++) {  
      new Apple().toString();   
    }  
    long endTime2 = System.currentTimeMillis();  
    System.out.println("直接获取方法时间:" + (endTime2- startTime2)); 
   
   //反射获取实例  
   Class appleClass=Class.forName("com.eastrobot.reflect.Apple");   
   long startTime3 = System.currentTimeMillis();   
   for (int i = 0; i < 1000000; i++) {
       appleClass.getDeclaredConstructor().newInstance();  
    }   
   long endTime3 = System.currentTimeMillis(); 
   System.out.println("反射获取实例:" + (endTime3 - startTime3));   
 
   //反射获取方法  
   Apple apple= (Apple)appleClass.getDeclaredConstructor().newInstance();   
   long startTime4 = System.currentTimeMillis();   
   for (int i = 0; i < 1000000; i++) {    
     Method method=appleClass.getMethod("toString");    
     method.invoke(apple);   
   }    
   long endTime4 = System.currentTimeMillis();  
   System.out.println("反射获取方法:" + (endTime4 - startTime4));

Скриншот результата работы:


Мы видим, что отражение действительно вызывает проблемы с производительностью, но серьезные проблемы с производительностью, вызванные отражением, связаны с количеством раз использования.Если оно контролируется в пределах 100 раз, в основном нет разницы.Если количество вызовов превышает 100 раз, разница в производительности будет очень большой очевидно.

Например, если курьер находится в сообществе, где вы живете, то вы сообщаете адрес: № хх, дом хх, то курьер может сразу узнать, где вы находитесь, и пройти прямо к вашей двери, однако, если курьер в первый раз, когда он пришел к вам, должен ли он сначала проверить карту Baidu, чтобы узнать, как туда проехать, а затем, когда он прибыл в сообщество, должен ли он спросить здание управления имуществом xx, как его найти, а затем он может спуститься вниз и пару раз повернуться кругом до твоей двери.

Давайте посмотрим на сцену выше.Если курьер не знаком с вашим сообществом, будет ли он работать медленнее?В основном его время уходит на поиск карт Baidu и расспросы об управлении недвижимостью. Хорошо, то же самое касается размышлений, потому что я ничего не знаю заранее, поэтому мне придется потратить время на поиск другой информации, прежде чем я смогу найти вас.

Подводя итог, можно сказать, что большинство из нас используют отражение без учета производительности, и обычно мы используем его реже.Если мы действительно сталкиваемся с проблемами производительности, такими как эффективность отражения, влияющего на логику программы, мы можем использовать кэширование или технологию улучшения байт-кода Java. В библиотеке есть asm, а также есть сторонняя инструментальная библиотека ReflectAsm (GitHub.com/esoteric, так что F…).

Нарушает ли метод setAccessible() в отражении правила доступа класса

setAccessible(true) отменяет проверку контроля доступа в Java (обратите внимание, что это не изменяет права доступа к методам или полям).Что касается того, разрушит ли метод setAccessible() правила доступа класса и вызовет угрозы безопасности, см. следующий:


Анализ исходного кода отражения

Мы следим за методом вызова метода, который разделен на два шага. Первый — переопределен ли уровень доступа к языку. имеет права доступа.Метод quickCheckMemberAccess в основном просто оценивает.Являются ли модификаторы общедоступными, возвращает false, если нет. Поэтому модификаторы protected, private и default вернут false, и только public вернет true. Если false, вызывается метод checkAccess. Второй — получить объект MethodAccessor и вызвать его метод вызова.

public final class Method extends AccessibleObject implements GenericDeclaration, Member {   
     private volatile MethodAccessor methodAccessor; 
    //每个Java方法只有一个对应Method对象作为root,这个root不会暴露给用户,
    //而是每次通过反射获取Method对象时新创建的Method对象将root包装起来。
     private Method   root;

    @CallerSensitive 
    public Object invoke(Object obj, Object... args)throws IllegalAccessException, IllegalArgumentException,InvocationTargetException
    {
        if (!override) {
            if (!Reflection.quickCheckMemberAccess(clazz, modifiers)) {
                Class<?> caller = getCallerClass();
                checkAccess(caller, clazz, obj, modifiers);
            }
        }
        MethodAccessor ma = methodAccessor;
        //在第一次调用一个实际Java方法应该的Method对象的invoke方法之前
        //实现调用逻辑的MethodAccessor对象还没有创建
        //等到第一次调用时才创建MethodAccessor,并通过该MethodAccessor.invoke真正完成反射调用
        if (ma == null) {
            ma = acquireMethodAccessor();
        }
        //invoke并不是自己实现的反射调用逻辑,而是委托给sun.reflect.MethodAccessor来处理
        return ma.invoke(obj, args);
    } 

    ...

     private MethodAccessor acquireMethodAccessor() {
        MethodAccessor tmp = null;
        if (root != null) tmp = root.getMethodAccessor();
        if (tmp != null) {
            methodAccessor = tmp;
        } else {
            //调用ReflectionFactory的newMethodAccessor方法,见下
            tmp = reflectionFactory.newMethodAccessor(this);
            //更新root,以便下次直接使用
            setMethodAccessor(tmp);
        }
        return tmp;
    }

    ...

     void setMethodAccessor(MethodAccessor accessor) {
        methodAccessor = accessor;
        // Propagate up
        if (root != null) {
            root.setMethodAccessor(accessor);
        }
    }

Класс отражения:

public static boolean quickCheckMemberAccess(Class<?> memberClass,
                                                 int modifiers)
{
    return Modifier.isPublic(getClassAccessFlags(memberClass) & modifiers);
} 

Класс ReflectionFactory:

private static boolean noInflation = false;
//选择java版还是C语言版的阈值
private static int inflationThreshold = 15;

public MethodAccessor newMethodAccessor(Method var1) {
        checkInitted();
        if (noInflation) {
            //java版
            return (new MethodAccessorGenerator()).generateMethod(var1.getDeclaringClass(), var1.getName(), var1.getParameterTypes(), var1.getReturnType(), var1.getExceptionTypes(), var1.getModifiers());
        } else {
            //c语言版
            NativeMethodAccessorImpl var2 = new NativeMethodAccessorImpl(var1);
            DelegatingMethodAccessorImpl var3 = new DelegatingMethodAccessorImpl(var2);
            var2.setParent(var3);
            return var3;
        }
    }

Как показано в приведенном выше коде, существует две версии фактической реализации MethodAccessor: одна на Java и одна на собственном коде. Версия, реализованная на Java, требует больше времени для инициализации, но имеет более высокую производительность в долгосрочной перспективе; нативная версия как раз наоборот, запускается относительно быстро, но после длительного времени работы скорость не может сравниться с версией на Java. Это характеристика производительности, обеспечиваемая методом оптимизации HotSpot, и она также характерна для многих виртуальных машин: пересечение собственной границы будет препятствовать оптимизации, это похоже на черный ящик, который затрудняет анализ и встраивание виртуальной машины, поэтому запустите После долгого времени управляемая версия кода работает быстрее.

Чтобы сбалансировать производительность двух версий, Sun JDK использует метод «инфляции»: когда Java-метод вызывается путем отражения, в начале несколько раз используется собственная версия, а при вызове метода создается специальный класс реализации MethodAccessor. количество вызовов отражения превышает пороговое значение. , генерируется байт-код метода invoke(), и версия Java будет использоваться для будущих вызовов отражения к методу Java.
JDK Sun использует эту оптимизацию начиная с версии 1.4. Главный автор —Ken Russell

Реализация MethodAccessor на языке C (по умолчанию)

Версия MethodAccessor на языке C в основном включает в себя два класса, NativeMethodAccessorImpl и DelegatingMethodAccessorImpl, а DelegatingMethodAccessorImpl является косвенным слоем, это не слишком важно, поэтому я не буду публиковать код. Ниже приведен код NativeMethodAccessorImpl, ядром которого является метод вызова:

class NativeMethodAccessorImpl extends MethodAccessorImpl {
    private final Method method;
    private DelegatingMethodAccessorImpl parent;
    private int numInvocations;

    NativeMethodAccessorImpl(Method var1) {
        this.method = var1;
    }

    public Object invoke(Object var1, Object[] var2) throws IllegalArgumentException, InvocationTargetException {
        if (++this.numInvocations > ReflectionFactory.inflationThreshold()) {
            MethodAccessorImpl var3 = (MethodAccessorImpl)(new MethodAccessorGenerator()).generateMethod(this.method.getDeclaringClass(), this.method.getName(), this.method.getParameterTypes(), this.method.getReturnType(), this.method.getExceptionTypes(), this.method.getModifiers());
            this.parent.setDelegate(var3);
        }

        return invoke0(this.method, var1, var2);
    }

    void setParent(DelegatingMethodAccessorImpl var1) {
        this.parent = var1;
    }

    private static native Object invoke0(Method var0, Object var1, Object[] var2);
}

Каждый раз, когда вызывается метод NativeMethodAccessorImpl.invoke(), будет добавлен счетчик вызовов, чтобы увидеть, превышено ли пороговое значение; после его превышения будет вызываться MethodAccessorGenerator.generateMethod() для создания Java-версии класса реализации MethodAccessor и объект, на который ссылается DelegatingMethodAccessorImpl, будет изменен. Последующие вызовы через DelegatingMethodAccessorImpl.invoke() являются реализацией версии Java.

Java-реализация MethodAccessor

 return (new MethodAccessorGenerator()).generateMethod(var1.getDeclaringClass(), var1.getName(), var1.getParameterTypes(), var1.getReturnType(), var1.getExceptionTypes(), var1.getModifiers());

В Java MethodAccessor в основном используется класс MethodAccessorGenerator. Конкретный код слишком длинный, и перехватывается только часть кода. Существует три основных метода, которые являются непосредственно вышеуказанным методом generateMethod. Код выглядит следующим образом:

public MethodAccessor generateMethod(Class var1, String var2, Class[] var3, Class var4, Class[] var5, int var6) {
        return (MethodAccessor)this.generate(var1, var2, var3, var4, var5, var6, false, false, (Class)null);
    }
 private MagicAccessorImpl generate(final Class var1, String var2, Class[] var3, Class var4, Class[] var5, int var6, boolean var7, boolean var8, Class var9) {
        ByteVector var10 = ByteVectorFactory.create();
        this.asm = new ClassFileAssembler(var10);
        this.declaringClass = var1;
        this.parameterTypes = var3;
        this.returnType = var4;
        this.modifiers = var6;
        this.isConstructor = var7;
        this.forSerialization = var8;
        this.asm.emitMagicAndVersion();
        short var11 = 42;
        boolean var12 = this.usesPrimitiveTypes();
        if (var12) {
            var11 = (short)(var11 + 72);
        }

        if (var8) {
            var11 = (short)(var11 + 2);
        }

        var11 += (short)(2 * this.numNonPrimitiveParameterTypes());
        this.asm.emitShort(add(var11, (short)1));
        final String var13 = generateName(var7, var8);
        this.asm.emitConstantPoolUTF8(var13);
        this.asm.emitConstantPoolClass(this.asm.cpi());
        this.thisClass = this.asm.cpi();
        if (var7) {
            if (var8) {
                this.asm.emitConstantPoolUTF8("sun/reflect/SerializationConstructorAccessorImpl");
            } else {
                this.asm.emitConstantPoolUTF8("sun/reflect/ConstructorAccessorImpl");
            }
        } else {
            this.asm.emitConstantPoolUTF8("sun/reflect/MethodAccessorImpl");
        }

        this.asm.emitConstantPoolClass(this.asm.cpi());
        this.superClass = this.asm.cpi();
        this.asm.emitConstantPoolUTF8(getClassName(var1, false));
        this.asm.emitConstantPoolClass(this.asm.cpi());
        this.targetClass = this.asm.cpi();
        short var14 = 0;
        if (var8) {
            this.asm.emitConstantPoolUTF8(getClassName(var9, false));
            this.asm.emitConstantPoolClass(this.asm.cpi());
            var14 = this.asm.cpi();
        }

        this.asm.emitConstantPoolUTF8(var2);
        this.asm.emitConstantPoolUTF8(this.buildInternalSignature());
        this.asm.emitConstantPoolNameAndType(sub(this.asm.cpi(), (short)1), this.asm.cpi());
        if (this.isInterface()) {
            this.asm.emitConstantPoolInterfaceMethodref(this.targetClass, this.asm.cpi());
        } else if (var8) {
            this.asm.emitConstantPoolMethodref(var14, this.asm.cpi());
        } else {
            this.asm.emitConstantPoolMethodref(this.targetClass, this.asm.cpi());
        }

        this.targetMethodRef = this.asm.cpi();
        if (var7) {
            this.asm.emitConstantPoolUTF8("newInstance");
        } else {
            this.asm.emitConstantPoolUTF8("invoke");
        }

        this.invokeIdx = this.asm.cpi();
        if (var7) {
            this.asm.emitConstantPoolUTF8("([Ljava/lang/Object;)Ljava/lang/Object;");
        } else {
            this.asm.emitConstantPoolUTF8("(Ljava/lang/Object;[Ljava/lang/Object;)Ljava/lang/Object;");
        }

        this.invokeDescriptorIdx = this.asm.cpi();
        this.nonPrimitiveParametersBaseIdx = add(this.asm.cpi(), (short)2);

        for(int var15 = 0; var15 < var3.length; ++var15) {
            Class var16 = var3[var15];
            if (!isPrimitive(var16)) {
                this.asm.emitConstantPoolUTF8(getClassName(var16, false));
                this.asm.emitConstantPoolClass(this.asm.cpi());
            }
        }

        this.emitCommonConstantPoolEntries();
        if (var12) {
            this.emitBoxingContantPoolEntries();
        }

        if (this.asm.cpi() != var11) {
            throw new InternalError("Adjust this code (cpi = " + this.asm.cpi() + ", numCPEntries = " + var11 + ")");
        } else {
            this.asm.emitShort((short)1);
            this.asm.emitShort(this.thisClass);
            this.asm.emitShort(this.superClass);
            this.asm.emitShort((short)0);
            this.asm.emitShort((short)0);
            this.asm.emitShort((short)2);
            this.emitConstructor();
            this.emitInvoke();
            this.asm.emitShort((short)0);
            var10.trim();
            final byte[] var17 = var10.getData();
            return (MagicAccessorImpl)AccessController.doPrivileged(new PrivilegedAction<MagicAccessorImpl>() {
                public MagicAccessorImpl run() {
                    try {
                        return (MagicAccessorImpl)ClassDefiner.defineClass(var13, var17, 0, var17.length, var1.getClassLoader()).newInstance();
                    } catch (InstantiationException var2) {
                        throw (InternalError)(new InternalError()).initCause(var2);
                    } catch (IllegalAccessException var3) {
                        throw (InternalError)(new InternalError()).initCause(var3);
                    }
                }
            });
        }
    } 
private static synchronized String generateName(boolean var0, boolean var1) {
        int var2;
        if (var0) {
            if (var1) {
                var2 = ++serializationConstructorSymnum;
                return "sun/reflect/GeneratedSerializationConstructorAccessor" + var2;
            } else {
                var2 = ++constructorSymnum;
                return "sun/reflect/GeneratedConstructorAccessor" + var2;
            }
        } else {
            var2 = ++methodSymnum;
            return "sun/reflect/GeneratedMethodAccessor" + var2;
        }
    }

Если вы прочтете исходный код, то увидите, как MethodAccessorGenerator постепенно создает Java-версию класса реализации MethodAccessor, что на самом деле представляет собой процесс пошагового анализа.

此时要注意的是最后的“sun/reflect/GeneratedMethodAccessor”+var2的代码。

пример

Вышеупомянутый пустой треп бесполезен, слишком сух, возьмем пример.

public class Foo {

public void foo(String name) {       
     System.out.println("Hello, " + name); 
   }
}
public class test {    
    public static void main(String[] args) {  
          try {          
                  Class<?> clz = Class.forName("com.eastrobot.reflect.Foo");      
                  Object o = clz.newInstance();  
                  Method m = clz.getMethod("foo", String.class);    
                  for (int i = 0; i < 17; i++) {        
                        m.invoke(o, Integer.toString(i));      
                  }       
         } catch (Exception e) {
         } 
   }
}

В дополнение к приведенному выше коду вам также необходимо настроить соответствующие рабочие параметры в идее и добавить параметр -XX:+TraceClassLoading, который представляет собой информацию мониторинга, необходимую для печати загруженного класса.


Давайте сначала выполним приведенный выше пример, и результаты будут следующими. Первые пятнадцать раз нормально. На 16-м разе появляется много информации для печати. ​​Я отметил строку красным цветом «GeneratedMethodAccessor1», что на самом деле то, что я Как сказано выше, Java-версия получает последнюю строку MethodAccessorGenerator, а 1 является параметром автоинкремента. Когда 17-й раз, он не будет повторно получен в версии Java, а будет повторно использован напрямую.


Эпилог

Наконец-то все кончилось, играю и пишу, пишу уже пять дней, устал, обещай, ты должен прочитать внимательно, ладно?

Если что-то не так, пожалуйста, поправьте меня.

просить внимания

Добро пожаловать, чтобы обратить внимание, давайте учиться вместе.

использованная литература

Отражение Java в деталях

Разница между getDeclaredField и getField в отражении Java

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

Рефлексия действительно замедляет работу вашей программы?

Углубленный анализ отражения Java (1) — основы

Журнал о методе, вызванном отражением

Расширенное отражение, многие детали, на которые стоит обратить внимание при написании кода отражения