前言
反射的几个基本作用:
- 获取程序在运行时刻的内部结构。例如Constructor、Field和Method。ide 便运用了这一功能:每当我们敲入点号时,ide 便会根据点号前的内容,动态展示可以访问的字段或者方法。
- 在运行时刻对一个Java对象进行操作。
- 处理泛型
使用场景:
- 笔者曾开发一个框架,通过注解来整合执行流程。涉及到不同策略,除默认策略外,使用方可以自定义类实现特定接口,通过注解标记,由框架加载执行。
- kafka,一些策略类依赖特定jar,使用反射加载特定jar的类,因不一定使用该策略,则kafka打默认包时无需依赖特定类。
- 近日在读《程序员的自我修养》,看到动态链接一章,书中提到,程序可以利用特定的api在运行时将某些共享对象(比如windows中的dll)加载到内存中(注意,此时程序已经在内存中运行了),我们根据函数的名字拿到了共享对象中对应函数在内存中的地址。问题是光知道地址有时候还是不够,我们不知道函数签名(比如函数参数类型,返回值类型等,c/c++编译器没有将这些信息保存到共享对象中)。
但反射还有更大的惊喜喔!
反射的实现
以下来自极客时间付费课程《深入拆解java虚拟机》的笔记。
public class Test {
public static void target(int i){
new Exception("#" + i).printStackTrace();
}
public static void main(String[] args) throws Exception {
Method method = Test.class.getMethod("target",int.class);
method.invoke(null,0);
}
}
java.lang.Exception: #0
at com.xx.xx.Test.target(Test.java:7)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at com.xx.xx.Test.main(Test.java:11)
从异常堆栈可以看到, method.invoke
最终还是 转换为了执行 Test.target
方法。此外, method.invoke
和 Test.target
之间还隔了
DelegatingMethodAccessorImpl.invoke
NativeMethodAccessorImpl.invoke
NativeMethodAccessorImpl.invoke0
也就是method.invoke
实际的执行过程为:
- 先执行java 代码
- 再执行 native 代码(也就是c++代码)
- 再执行java 代码(也就是
Test.target
)
整体比较耗时。
扩展:Java编译器将包含本地方法的class对应的方法添加ACC_NATIVE标识,而JVM负责将动态库加载到内存,Java执行引擎执行到本地方法时找到对应的函数,完成本地方法的调用。
为提高效率,当某个反射调用的调用次数在15(默认)以上时,便开始动态生成字节码。直接使用 invoke 指令来调用目标方法。之所以使用 委派模式DelegatingMethodAccessorImpl,便是为了能够在 NativeMethodAccessorImpl 以及 动态实现 中切换。参见下列的GenerateMethodAccessor1, 其在java 代码层面是不存在的,但在字节码层面是存在的。这样 便免掉了java==>c++==> java 的代码切换。
// GenerateMethodAccessor1 伪代码
public class GenerateMethodAccessor1 extends ...{
public Object invoke(Object obj,Object[] args)throws ...{
Test.target((int)args[0]);
return null;
}
}
如何提高反射效率
反射调用的开销
- 一些反射对象的获取比较麻烦。比如
Class.getMethod
会遍历该类的公有方法,如果没有匹配到,它还将遍历父类的公有方法。 - Method.getMethods, Method.getDeclaredMethods 会返回查找结果的一份儿拷贝(方法返回值都是数组)。
-
调用本身的开销
- Method.invoke 是一个变长参数方法,在字节码层面它的最后一个参数回事Object 数据。java 编译器会在方法调用处生成一个长度为传入参数数量的Object 数组,并将传入参数传入该数组中
- 由于Object 数组不能存储 基本类型, java 编译器会对传入的基本类型参数进行自动装箱
- 反射本身的委派机制
- 每次反射调用都会检查目标方法的权限。这个检查可以在java 代码里关闭
整体来说,使用反射除了带来性能开销外, 还可能占用堆内存(变长参数的分配和释放等),使得gc 更加频繁。
- 缓存得到的class/method/field/constructor对象,最好在系统启动阶段就一次性完成缓存
- 高版本jdk
setAccessible(true)
性能提高完爆前两种
The AccessibleObject class is the base class for Field, Method and
Constructor objects. It provides the ability to flag a reflected
object as suppressing default Java language access control checks
when it is used. The access checks–for public, default (package)
access, protected, and private members–are performed when Fields,
Methods or Constructors are used to set or get fields, to invoke
methods, or to create and initialize new instances of classes,
respectively. 当 Field, Method和 Constructor 这些反射对象执行的时候,默认有一个access control check,哪怕是public 方法或字段,setAccessible(true)
用于屏蔽这种检查,因而可以极大地提高效率。
joor
使用
Reflect.on("java.lang.String").create("Hello World").call("toString")
- Wrap an Object / Class / class name with the on() method:
on("java.lang.String")
- Invoke constructors using the create() method:
create("Hello World")
- Invoke methods using the call() method:
call("toString")
源码分析
只提供Reflect和ReflectException 两个类,核心是Reflect 类
Reflect 有两个成员,本质是对 object 的封装
- Object object
- Class<?> type,object 的类型
Reflect 主要通过 setAccessible(true)
提高反射效率,并没有提供缓存机制。
java 反射执行 scala类的方法(未完成)
How can I reflectively call a method on a Scala object from Java?
反射与动态代理
2018.11.02 补充
前文提到的反射,作用有两类
- 运行时感知 类的信息
- 运行时 加载class 文件到jvm
前两者 算是信息的读取,class 信息事先便已经存在。今天再补充一个:运行时,动态提供接口实现。
动态接口实现有什么用处呢?一些接口实现 取决于 用户的配置,只有加载了用户的配置才可以确认处理逻辑。此时你可以:
- 根据用户配置穷举所有可能性, 然后根据配置调用 不同的实现类来处理。
- 等运行时加载了用户的配置之后,再实现该接口。也就是 将实现接口的行为 放在 运行时。
Java Reflection - Dynamic ProxiesUsing Java Reflection you create dynamic implementations of interfaces at runtime. You do so using the class java.lang.reflect.Proxy. The name of this class is why I refer to these dynamic interface implementations as dynamic proxies. 我们常说的 dynamic proxies 总让人跟代理模式扯上联系,但实际上说dynamic interface implementations 更为直观。
我们按下jdk 官方对 java.lang.reflect.Proxy
的注释:proxy instance has an associated invocation handler object, which implements the interface InvocationHandler. A method invocation on a proxy instance through one of its proxy interfaces will be dispatched to the InvocationHandler#invoke invoke method of the instance’s invocation handler, passing the proxy instance, a ` java.lang.reflect.Method` object identifying the method that was invoked, and an array of type Object containing the arguments. The invocation handler processes the encoded method invocation as appropriate and the result that it returns will be returned as the result of the method invocation on the proxy instance.
静态实现一个接口, 可以一个方法一个方法的慢慢实现。你动态实现一个接口,没有地方让你一个方法一个方法的实现,只能依靠一个机制。
Java动态代理的实现机制所谓的动态代理就是这样一种class,它是在运行时生成的class,在生成它时你必须提供一组interface给它,然后该 class就宣称它实现了这些interface,但是其实它不会替你做实质性的工作,而是根据你在生成实例时提供的参数handler(即 InvocationHandler接口的实现类),由这个Handler来接管实际的工作。
动态代理的本质 为什么要用 InvocationHandler 插一脚呢?因为sun.misc.ProxyGenerator
在生成 proxy class byte[] 时,自然希望具体的方法实现是一个模式化的code,这样才方便自动生成代码。所以将差异化的逻辑 转移到了 InvocationHandler 。
interface Target {
void handle();
}
public final class $Proxy0 extends Proxy implements Target{
...
private static Method m3;
...
public $Proxy0(InvocationHandler invocationHandler){
super(invocationHandler);
}
static{
try{
...
m3=Class.forName("Target").getMethod("handle",new Class[0]);
...
}
}
public final void handle() throws Exception{
try{
this.h.invoke(this, m3,null);
}catch(...){
...
}
}
}
小结
到这里,我们说反射能干什么?
- 动态读取class
- 动态加载class
- 动态生成class
Java Reflection - Dynamic ProxiesDynamic proxies are known to be used for at least the following purposes:
- Database Connection and Transaction Management
- Dynamic Mock Objects for Unit Testing
- Adaptation of DI Container to Custom Factory Interfaces
- AOP-like Method Interception