深入理解java反射
- 前言
- 一、反射机制相关的类?
- 1.1获得类相关的方法
- 1.2获得类中属性相关的方法
- 1.3获得类中构造器相关的方法
- 1.4获得类中构造器相关的方法
- 1.5Field类、Method类、Constructor类方法
- 二、反射的基本运用
- 2.1获得 Class 对象
- 2.2创建实例
- 2.3获取方法
- 2.4调用方法
- 3.浅析invoke过程
- 3.1invoke()方法实现源码
- 3.1.1权限检查
- 3.1.2调用MethodAccessor的invoke方法
- 注意
前言
JAVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意方法和属性;这种动态获取信息以及动态调用对象方法的功能称为java语言的反射机制。
一、反射机制相关的类?
反射机制的相关类
与Java反射相关的类如下:
类名 | 用途 |
Class类 | 代表类的实体,在运行的Java应用程序中表示类和接口 |
Field类 | 代表类的成员变量(成员变量也称为类的属性) |
Method类 | 代表类的方法 |
Constructor类 | 代表类的构造方法 |
1.1获得类相关的方法
方法 | 用途 |
forName(String className) | 根据类名返回类的对象 |
newInstance() | 创建类的实例 |
1.2获得类中属性相关的方法
方法 | 用途 |
getField(String name) | 获得某个公有的属性对象 |
getFields() | 获得所有公有的属性对象 |
getDeclaredField(String name) | 获得某个属性对象 |
getFields() | 获得所有公有的属性对象 |
1.3获得类中构造器相关的方法
1.4获得类中构造器相关的方法
1.5Field类、Method类、Constructor类方法
二、反射的基本运用
2.1获得 Class 对象
三种方法:
(1) 使用 Class 类的 forName 静态方法:
(2)直接获取某一个对象的 class;
(3)调用某个对象的 getClass() 方法;
2.2创建实例
通过反射来生成对象主要有两种方式。
使用Class对象的newInstance()方法来创建Class对象对应类的实例。
Class<?> clazz = String.class;
Object str = clazz.newInstance();
先通过Class对象获取指定的Constructor对象,再调用Constructor对象的newInstance()方法来创建实例。这种方法可以用指定的构造器构造类的实例。
//获取String所对应的Class对象
Class<?> clazz = String.class;
//获取String类带一个String参数的构造器
Constructor constructor = clazz.getConstructor(String.class);
//根据构造器创建实例
Object obj = constructor.newInstance("java");
System.out.println(obj);
2.3获取方法
方法 | 用途 |
getDeclaredMethods | 方法返回类或接口声明的所有方法,包括公共、保护、默认(包)访问和私有方法,但不包括继承的方法。 |
getMethods | 方法返回某个类的所有公用(public)方法,包括其继承类的公用方法。 |
getMethod | 方法返回一个特定的方法,其中第一个参数为方法名称,后面的参数为方法的参数对应Class的对象。 |
2.4调用方法
当我们从类中获取了一个方法后,我们就可以用 invoke() 方法来调用这个方法。invoke 方法的原型为:
public Object invoke(Object obj, Object... args)
throws IllegalAccessException, IllegalArgumentException,
InvocationTargetException
3.浅析invoke过程
3.1invoke()方法实现源码
@CallerSensitive
public Object invoke(Object obj, Object... args)
throws IllegalAccessException, IllegalArgumentException,
InvocationTargetException
{
if (!override) {
if (!Reflection.quickCheckMemberAccess(clazz, modifiers)) {
Class<?> caller = Reflection.getCallerClass();
checkAccess(caller, clazz, obj, modifiers);
}
}
MethodAccessor ma = methodAccessor; // read volatile
if (ma == null) {
ma = acquireMethodAccessor();
}
return ma.invoke(obj, args);
}
根据invoke方法的实现,将其分为以下几步:
3.1.1权限检查
首先检查AccessibleObject的override属性的值。AccessibleObject 类是 Field、Method 和 Constructor 对象的基类。它提供了将反射的对象标记为在使用时取消默认 Java 语言访问控制检查的能力。
override的值默认是false,表示需要权限调用规则,调用方法时需要检查权限;我们也可以用setAccessible方法设置为true,若override的值为true,表示忽略权限规则,调用方法时无需检查权限(也就是说可以调用任意的private方法,违反了封装)。
如果override属性为默认值false,则进行进一步的权限检查:
用Reflection.quickCheckMemberAccess(clazz, modifiers)方法检查方法是否为public,如果是的话跳出本步;如果不是public方法,那么用Reflection.getCallerClass()方法获取调用这个方法的Class对象,获取了这个Class对象caller后用checkAccess方法做一次快速的权限校验,一旦调用方法的Class正确则权限检查通过。
若未通过,则创建一个缓存,中间再进行一堆检查(比如检验是否为protected属性)。
如果上面的所有权限检查都未通过,那么将执行更详细的检查,大体意思就是,用Reflection.ensureMemberAccess方法继续检查权限,若检查通过就更新缓存,这样下一次同一个类调用同一个方法时就不用执行权限检查了,这是一种简单的缓存机制。由于JMM的happens-before规则能够保证缓存初始化能够在写缓存之前发生,因此两个cache不需要声明为volatile。
到这里,前期的权限检查工作就结束了。如果没有通过检查则会抛出异常,如果通过了检查则会到下一步。
3.1.2调用MethodAccessor的invoke方法
Method.invoke()实际上并不是自己实现的反射调用逻辑,而是委托给sun.reflect.MethodAccessor来处理。首先要了解Method对象的基本构成,每个Java方法有且只有一个Method对象作为root,它相当于根对象,对用户不可见。当我们创建Method对象时,我们代码中获得的Method对象都相当于它的副本(或引用)。root对象持有一个MethodAccessor对象,所以所有获取到的Method对象都共享这一个MethodAccessor对象,因此必须保证它在内存中的可见性。
第一次调用一个Java方法对应的Method对象的invoke()方法之前,实现调用逻辑的MethodAccessor对象还没有创建;等第一次调用时才新创建MethodAccessor并更新给root,然后调用MethodAccessor.invoke()完成反射调用
// NOTE that there is no synchronization used here. It is correct
// (though not efficient) to generate more than one MethodAccessor
// for a given Method. However, avoiding synchronization will
// probably make the implementation more scalable.
private MethodAccessor acquireMethodAccessor() {
// First check to see if one has been created yet, and take it
// if so
MethodAccessor tmp = null;
if (root != null) tmp = root.getMethodAccessor();
if (tmp != null) {
methodAccessor = tmp;
} else {
// Otherwise fabricate one and propagate it up to the root
tmp = reflectionFactory.newMethodAccessor(this);
setMethodAccessor(tmp);
}
return tmp
实际的MethodAccessor实现有两个版本,一个是Java版本,一个是native版本,两者各有特点。初次启动时Method.invoke()和Constructor.newInstance()方法采用native方法要比Java方法快3-4倍,而启动后native方法又要消耗额外的性能而慢于Java方法。也就是说,Java实现的版本在初始化时需要较多时间,但长久来说性能较好;native版本正好相反,启动时相对较快,但运行时间长了之后速度就比不过Java版了。这是HotSpot的优化方式带来的性能特性,同时也是许多虚拟机的共同点:跨越native边界会对优化有阻碍作用,它就像个黑箱一样让虚拟机难以分析也将其内联,于是运行时间长了之后反而是托管版本的代码更快些。
为了尽可能地减少性能损耗,HotSpot JDK采用“inflation”的技巧:让Java方法在被反射调用时,开头若干次使用native版,等反射调用次数超过阈值时则生成一个专用的MethodAccessor实现类,生成其中的invoke()方法的字节码,以后对该Java方法的反射调用就会使用Java版本。 这项优化是从JDK 1.4开始的。每次NativeMethodAccessorImpl.invoke()方法被调用时,程序调用计数器都会增加1,看看是否超过阈值;一旦超过,则调用MethodAccessorGenerator.generateMethod()来生成Java版的MethodAccessor的实现类,并且改变DelegatingMethodAccessorImpl所引用的MethodAccessor为Java版。后续经由DelegatingMethodAccessorImpl.invoke()调用到的就是Java版的实现了。
注意
反射会额外消耗一定的系统资源,因此如果不需要动态地创建一个对象,那么就不需要用反射。,反射调用方法时可以忽略权限检查,因此可能会破坏封装性而导致安全问题。
https://www.jianshu.com/p/9be58ee20dee
https://www.sczyh30.com/posts/Java/java-reflection-2/#%E5%BC%95%E5%85%A5