Анализ принципов отражения Java — метод

Java
Анатомия метода отражения

ClassObject предоставляет следующие методы для получения объекта (Method):

  • getMethod- Вернуть определенный метод класса или интерфейса, который может быть только общедоступным измененным методом.
  • getDeclaredMethod- Возвращает определенный метод класса или интерфейса.
  • getMethods- Возвращает все общедоступные методы класса или интерфейса, включая общедоступные методы родительских классов.
  • getDeclaredMethods- Возвращает все методы класса или интерфейса, включая общедоступные, защищенные, доступ по умолчанию (пакет) и частные методы, но исключая унаследованные методы.

прецедент

//父类
public class RefFather {
    public void refFatherMethod(){
        System.out.println("我是父类方法 refFatherMethod");
    }
}

//子类
public class RefSon extends RefFather{
    private void refSonMethod(){
        System.out.println("我是子类方法 refMethod");
    }
}

//测试方法
public static void main(String[] args) {
    try {
            //类的全限定名 即包名+类名
            Class<?> clz = Class.forName("main.ref.RefSon");
            Object obj = clz.newInstance();
            Method refFatherMethod = clz.getMethod("refFatherMethod");
            Method refSonMethod = clz.getDeclaredMethod("refSonMethod");
            refSonMethod.invoke(obj);
     } catch (Exception e) {
            e.printStackTrace();
     }
}
Method

Первый взглядClass.getMethod(String name, Class<?>... parameterTypes)Исходный код:

    @CallerSensitive
    public Method getMethod(String name, Class<?>... parameterTypes)
        throws NoSuchMethodException, SecurityException {
        checkMemberAccess(Member.PUBLIC, Reflection.getCallerClass(), true);
        Method method = getMethod0(name, parameterTypes, true);
        if (method == null) {
            throw new NoSuchMethodException(getName() + "." + name + argumentTypesToString(parameterTypes));
        }
        return method;
    }

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

  1. @CallerSensitive
  2. checkMemberAccess(Member.PUBLIC, Reflection.getCallerClass(), true);
  3. Reflection.getCallerClass()
  4. Method method = getMethod0(name, parameterTypes, true);
@CallerSensitive

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

Например, в классе отражения метод должен выяснить, есть ли у вызывающего с двумя фиксированными слоями разрешение (разрешения класса, связанные с отражением, относительно высоки), я могу передать свой собственный класс -> Reflection 1 -> Reflection 2. В таком цепочка вызовов, отражение 2 проверяет разрешения отражения 1, что приводит к уязвимостям безопасности. Используя такие аннотации, затемgetCallerClassпропустит напрямую@CallerSensitiveУкрашенный интерфейсный метод, который напрямую ищет реального вызывающего абонента (actual caller).

checkMemberAccess

Сначала посмотрите на исходный код этого метода:

private void checkMemberAccess(int which, Class<?> caller, boolean checkProxyInterfaces) {
        final SecurityManager s = System.getSecurityManager();
        if (s != null) {
            final ClassLoader ccl = ClassLoader.getClassLoader(caller);
            final ClassLoader cl = getClassLoader0();
            if (which != Member.PUBLIC) {
                if (ccl != cl) {
                    s.checkPermission(SecurityConstants.CHECK_MEMBER_ACCESS_PERMISSION);
                }
            }
            this.checkPackageAccess(ccl, checkProxyInterfaces);
        }
    }

Его функция в основном состоит в том, чтобы проверить, разрешен ли клиенту доступ к элементу.

Политика по умолчанию: разрешить всем клиентам использовать обычный доступ к Java для управления доступом.

Что это значит?

То есть, если вы не будете активно настраиватьSecurityManagerМенеджер безопасности определяет, есть ли у него разрешение в соответствии с модификатором доступа класса Class. НапримерprotectedРазрешен доступ только к одному и тому же пакету или подклассу, а межпакетный доступ становится закрытым и запрещенным.

final ClassLoader ccl = ClassLoader.getClassLoader(caller);

final ClassLoader cl = getClassLoader0();

Если есть обычайSecurityManager(передачаSystem.setSecurityManager(new MySecurityManager())), он будет судить, имеют ли вызывающий и посетитель один и тот же загрузчик классов.Если это так, даже если это не модификатор общего доступа, ограничений на разрешения нет. В общем загрузчик классов тутClassLoaderОба являются загрузчиками классов приложений.Application ClassLoader.

Вот краткое введениеApplication ClassLoader :

Этот загрузчик классов отвечает за загрузку библиотеки классов по пользовательскому пути к классу (CLASSPATH). Как правило, классы Java, которые мы пишем, загружаются этим загрузчиком классов. Этот загрузчик классов является возвращаемым значением метода getSystemClassLoader() в CLassLoader, поэтому его также называют системным загрузчиком классов.В общем, это системный загрузчик классов по умолчанию.

Reflection.getCallerClass()

Продолжайте смотреть исходный код:

    /** @deprecated */
    @Deprecated
    public static native Class<?> getCallerClass(int var0);

native означает, что этот метод фактически реализован другими языками, поэтому здесь объясняется только его использование.

Краткое введениеReflection.getCallerClass(int var0), вы можете узнать, если вы заинтересованы.

Если значение var0 равно 0 или меньше 0, вернутьclass sun.reflect.Reflection;

Если значение var0 равно 1, он возвращает свой собственный класс. То есть, в каком классе находится эта строка кода, этот класс и возвращается.

Если значение var0 равно 2, возвращается класс, возвращаемый вызывающей программе. Например, этот метод находится в методе A пользователя A, а метод A вызывается в методе B. В настоящее времяReflection.getCallerClass(int var0)То, что возвращается, является классом B.

getMethod0(name, parameterTypes, true)

Продолжайте получать OvO:

private Method getMethod0(String name, Class<?>[] parameterTypes, boolean includeStaticMethods) {
        MethodArray interfaceCandidates = new MethodArray(2);
        Method res =  privateGetMethodRecursive(name, parameterTypes, includeStaticMethods, interfaceCandidates);
        if (res != null)
            return res;

        // Not found on class or superclass directly
        interfaceCandidates.removeLessSpecifics();
        return interfaceCandidates.getFirst(); // may be null
    }

 private Method privateGetMethodRecursive(String name,
            Class<?>[] parameterTypes,
            boolean includeStaticMethods,
            MethodArray allInterfaceCandidates) {
        Method res;
        // Search declared public methods
        if ((res = searchMethods(privateGetDeclaredMethods(true),
                                 name,
                                 parameterTypes)) != null) {
            if (includeStaticMethods || !Modifier.isStatic(res.getModifiers()))
                return res;
        }
        // Search superclass's methods
        if (!isInterface()) {
            Class<? super T> c = getSuperclass();
            if (c != null) {
                if ((res = c.getMethod0(name, parameterTypes, true)) != null) {
                    return res;
                }
            }
        }
        // Search superinterfaces' methods
        Class<?>[] interfaces = getInterfaces();
        for (Class<?> c : interfaces)
            if ((res = c.getMethod0(name, parameterTypes, false)) != null)
                allInterfaceCandidates.add(res);
        // Not found
        return null;
    }

	private static Method searchMethods(Method[] methods,
                                        String name,
                                        Class<?>[] parameterTypes)
    {
        Method res = null;
        String internedName = name.intern();
        for (int i = 0; i < methods.length; i++) {
            Method m = methods[i];
            if (m.getName() == internedName
                && arrayContentsEq(parameterTypes, m.getParameterTypes())
                && (res == null
                    || res.getReturnType().isAssignableFrom(m.getReturnType())))
                res = m;
        }

        return (res == null ? res : getReflectionFactory().copyMethod(res));
    }

  private Method[] privateGetDeclaredMethods(boolean publicOnly) {
        checkInitted();
        Method[] res;
        ReflectionData<T> rd = reflectionData();
        if (rd != null) {
            res = publicOnly ? rd.declaredPublicMethods : rd.declaredMethods;
            if (res != null) return res;
        }
        // No cached value available; request value from VM
        res = Reflection.filterMethods(this, getDeclaredMethods0(publicOnly));
        if (rd != null) {
            if (publicOnly) {
                rd.declaredPublicMethods = res;
            } else {
                rd.declaredMethods = res;
            }
        }
        return res;
    }

при звонкеgetMethod0когда он вызывает метод, который является частным для классаprivateGetMethodRecursiveРекурсивно получить весь этот классpublicМодифицированный метод (включая родительский класс).

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

privateGetDeclaredMethods(boolean publicOnly)Если параметр имеет значение true, возвращаются все общедоступные методы этого класса, в противном случае возвращаются все измененные методы.

согласно сprivateGetDeclaredMethods, нетрудно понять, почему getMethods() возвращает все публичные методы родительского класса, аgetDeclaredFields()Get — это все методы-модификаторы текущего метода.

Сделай смелое предположение,getMethods()Должен быть рекурсивный вызовprivateGetDeclaredMethods(true)метод, в то время какgetDeclaredMethods()следует вызывать только один разprivateGetDeclaredMethods(false)Методы.

getMethods

Взгляните на исходный код:

    @CallerSensitive
    public Method[] getMethods() throws SecurityException {
        checkMemberAccess(Member.PUBLIC, Reflection.getCallerClass(), true);
        return copyMethods(privateGetPublicMethods());
    }
    
    private Method[] privateGetPublicMethods() {
        checkInitted();
        Method[] res;
        ReflectionData<T> rd = reflectionData();
        if (rd != null) {
            res = rd.publicMethods;
            if (res != null) return res;
        }
      
      	//没有可用的缓存值;递归计算值。
				//从获取公共声明的方法开始
      	MethodArray methods = new MethodArray();
        {
            //请注意这里
            Method[] tmp = privateGetDeclaredMethods(true);
            methods.addAll(tmp);
        }
        ...
        return res;
    }
getDeclaredMethods

Исходный код выглядит следующим образом:

    @CallerSensitive
    public Method[] getDeclaredMethods() throws SecurityException {
        checkMemberAccess(Member.DECLARED, Reflection.getCallerClass(), true);
        //请注意这里
        return copyMethods(privateGetDeclaredMethods(false));
    }

Это было именно так, как и ожидалось.

Наконец еще одинgetDeclaredMethodМетод не представлен, но я полагаю, что каждый может догадаться о процессе реализации после прочтения приведенного выше исходного кода.

    @CallerSensitive
    public Method getDeclaredMethod(String name, Class<?>... parameterTypes)
        throws NoSuchMethodException, SecurityException {
        checkMemberAccess(Member.DECLARED, Reflection.getCallerClass(), true);
        Method method = searchMethods(privateGetDeclaredMethods(false), name, parameterTypes);
        if (method == null) {
            throw new NoSuchMethodException(getName() + "." + name + argumentTypesToString(parameterTypes));
        }
        return method;
    }

Весь приведенный выше анализ исходного кода о методе приобретения Class был разобран. ~(@^_^@)~ Если вам это нравится, пожалуйста, поставьте лайк. Ваша поддержка - самая большая мотивация для моего обновления.

Ввиду ограниченности техники автора, в статье неизбежны некоторые ошибки, добро пожаловать ━(`∀´)ノПравильно!

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

Полезность аннотации jvm @CallerSensitive

Все говорят, что отражение Java неэффективно, знаете причину?