反射通过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);
}
}
从上面的代码,我们可以看到,获取方法的流程分三步走:
- 检查方法权限
- 获取方法 Method 对象
- 返回方法的拷贝
这里主要有两个区别:
- 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、我们上面说到获取方法分三步走:
- 检查方法权限
- 获取方法 Method 对象
- 返回方法的拷贝
我们先看看检查方法权限做了些什么事情。
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 无法优化
因为反射涉及到动态加载的类型,所以无法进行优化。