java反射原理解析

反射通过3种方式获取字节码对象
对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性
      (1)对象.getClass()                  Object类的getClass()方法,判断两个方法是否是同一个字节码文件
      (2)类名.class                            静态属性class锁对象
      (3)class.forName()                  class类中静态方法forName(),读取配置文件

主要介绍class.forName()获取反射

1、通过反射创建对象:

1、无参构造方法:使用newInstance()方法

    //传入要实例化类完整的"包,类"名称
    Class clazz = Class.forName("e.com.javatest.Person");
   //实例化person对象
    Person p = (Person) clazz.newInstance();

2、有参的构造方法:使用getConstructors()获取全部构造方法

       Class clazz = Class.forName("e.com.javatest.Person");
        //通过反射获取全部构造方法
        Constructor[] cons = clazz.getConstructors();
        //传递参数,实例化person对象,只有一个构造方法,所以数组角标为0
        Person p = (Person) cons[0].newInstance("小明");

2、通过反射访问属性:

在反射机制中,属性的操作是通过Field类实现的,如果访问是私有的
使用Field类中的setAccessible()方法将需要操作的属性设置为可以被外界所访问。

        Class clazz = Class.forName("e.com.javatest.Person");
        Person p = (Person) clazz.newInstance();
        //获取person类中指定名称的属性
        Field name = clazz.getDeclaredField("name");
        //通过反射访问该属性时取消权限检查
        name.setAccessible(true);


3、通过反射调用方法

通过class对象的getMethod()或getDeclaredMethod()方法,通过Method中的invoke()调用对应的方法

        Class clazz = Class.forName("e.com.javatest.Person");
        //获取person类中的sayHello()方法,一个形参name
        Method md = clazz.getMethod("sayHello", String.class);
        或
        Method md = clazz.getDeclaredMethod("sayHello", String.class);
        //调用sayHello方法
        md.invoke(clazz.newInstance(),"小明");

这里我们先整体看一下 getMethod 和 getDeclaredMethod 的实现

class Class {
    @CallerSensitive
    public Method getMethod(String name, Class<?>... parameterTypes)
        throws NoSuchMethodException, SecurityException {
        Objects.requireNonNull(name);
        SecurityManager sm = System.getSecurityManager();
        if (sm != null) {
            // 1. 检查方法权限
            checkMemberAccess(sm, Member.PUBLIC, Reflection.getCallerClass(), true);
        }
        // 2. 获取方法
        Method method = getMethod0(name, parameterTypes);
        if (method == null) {
            throw new NoSuchMethodException(methodToString(name, parameterTypes));
        }
        // 3. 返回方法的拷贝
        return getReflectionFactory().copyMethod(method);
    }

    @CallerSensitive
    public Method getDeclaredMethod(String name, Class<?>... parameterTypes)
        throws NoSuchMethodException, SecurityException {
        Objects.requireNonNull(name);
        SecurityManager sm = System.getSecurityManager();
        if (sm != null) {
            // 1. 检查方法是权限
            checkMemberAccess(sm, Member.DECLARED, Reflection.getCallerClass(), true);
        }
        // 2. 获取方法
        Method method = searchMethods(privateGetDeclaredMethods(false), name, parameterTypes);
        if (method == null) {
            throw new NoSuchMethodException(methodToString(name, parameterTypes));
        }
        // 3. 返回方法的拷贝
        return getReflectionFactory().copyMethod(method);
    }
}

从上面的代码,我们可以看到,获取方法的流程分三步走:

  1. 检查方法权限
  2. 获取方法 Method 对象
  3. 返回方法的拷贝

这里主要有两个区别:

  1. getMethod 中 checkMemberAccess 传入的是 Member.PUBLIC,而 getDeclaredMethod 传入的是 Member.DECLARED 这两个值有什么区别呢?我们看下代码中的注释:
    interface Member {
       
        public static final int PUBLIC = 0;
    
        public static final int DECLARED = 1;
    }
    

    清楚的解释了 PUBLIC 和 DECLARED 的不同,PUBLIC 会包括所有的 public 方法,包括父类的方法,而 DECLARED 会包括所有自己定义的方法,public,protected,private 都在此,但是不包括父类的方法。
    这也正是 getMethod 和 getDeclaredMethod 的区别。
    2. getMethod 中获取方法调用的是 getMethod0,而 getDeclaredMethod 获取方法调用的是 privateGetDeclaredMethods 关于这个区别,这里简单提及一下,后面具体分析代码。
    privateGetDeclaredMethods 是获取类自身定义的方法,参数是 boolean publicOnly,表示是否只获取公共方法

    private Method[] privateGetDeclaredMethods(boolean publicOnly) {
        //...
    }

    看了 getMethod 和 getDeclaredMethod 的区别,我们自然选择 getMethod 方法进行分析,这样可以走到整个流程

4、我们上面说到获取方法分三步走:

  1. 检查方法权限
  2. 获取方法 Method 对象
  3. 返回方法的拷贝

我们先看看检查方法权限做了些什么事情。

1.3.1 checkMemberAccess

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

在这里可以看到,对于非 Member.PUBLIC 的访问,会增加一项检测,SecurityManager.checkPermission(SecurityConstants.CHECK_MEMBER_ACCESS_PERMISSION); 这项检测需要运行时申请 RuntimePermission("accessDeclaredMembers")
这里就不继续往下看了,方法整体是在检查是否可以访问对象成员

1.3.2 getMethod0

class Class {
    private Method getMethod0(String name, Class<?>[] parameterTypes) {
        PublicMethods.MethodList res = getMethodsRecursive(
            name,
            parameterTypes == null ? EMPTY_CLASS_ARRAY : parameterTypes,
            /* includeStatic */ true);
        return res == null ? null : res.getMostSpecific();
    }
}

这里是通过 getMethodsRecursive 获取到 MethodList 对象,然后通过 MethodList#getMostSpecific 方法筛选出对应的方法。 MethodList#getMOstSpecific 会筛选返回值类型最为具体的方法

1.3.3 Method#copy

在获取到对应方法以后,并不会直接返回,而是会通过 getReflectionFactory().copyMethod(method); 返回方法的一个拷贝。
最终调用的是 Method#copy,我们来看看其实现。

class Method {
    Method copy() {
         Method res = new Method(clazz, name, parameterTypes, returnType,
                                exceptionTypes, modifiers, slot, signature,
                                annotations, parameterAnnotations, annotationDefault);
        res.root = this;
        res.methodAccessor = methodAccessor;
        return res;
    }
}

会 new 一个 Method 实例并返回。
这里有两点要注意:

1、设置 root = this

2、会给 Method 设置 MethodAccessor,用于后面方法调用。也就是所有的 Method 的拷贝都会使用同一份 methodAccessor。


5、总结-------Java 反射效率低的原因

(1)、 Method#invoke 方法会对参数做封装和解封操作
上面 MethodAccessorGenerator#emitInvoke 方法里我们看到,生成的字节码时,会把参数数组拆解开来,把参数恢复到没有被 Object[] 包装前的样子,同时还要对参数做校验,这里就涉及到了解封操作。当调用次数达到一定量的时候,还会导致 GC。

(2)、 需要检查方法可见性
反射时每次调用都必须检查方法的可见性(在 Method.invoke 里)

(3)反射方法难以内联
Method#invoke 就像是个独木桥一样,各处的反射调用都要挤过去,在调用点上收集到的类型信息就会很乱,影响内联程序的判断,使得 Method.invoke() 自身难以被内联调用。

(4)、JIT 无法优化
因为反射涉及到动态加载的类型,所以无法进行优化。

展开阅读全文

没有更多推荐了,返回首页

©️2019 CSDN 皮肤主题: 技术工厂 设计师: CSDN官方博客
应支付0元
点击重新获取
扫码支付

支付成功即可阅读