Java基础:Java动态代理
最后更新时间:
文章总字数:
预计阅读时间:
Java基础:Java动态代理
Java反射提供了一种类动态代理机制,可以通过代理接口实现类来完成程序无侵入式扩展。
动态代理基础
首先看看class和interface有什么不同,非abstract的class可以被实例化,而interface不能实例化,必须由类实现,其变量总是通过某个实例向上转型并赋值给接口类型变量。
静态情况
先定义接口:
1 | public interface Hello { |
编写实现类并创建实例调用方法
1 | public class HelloWorld implements Hello { |
动态代理情况
Java标准库提供了一种动态代理(Dynamic Proxy)的机制:可以在运行期动态创建某个interface的实例,也就是说不需要编写实现类,直接在运行期创建interface实例。
我们仍然先定义了接口Hello
,但是我们并不去编写实现类,而是直接通过JDK提供的一个Proxy.newProxyInstance()
创建了一个Hello
接口对象。这种没有实现类但是在运行期动态创建了一个接口对象的方式,我们称为动态代码。JDK提供的动态创建接口对象的方式,就叫动态代理。将刚刚的静态代码转换为动态代码如下:
1 | import java.lang.reflect.InvocationHandler; |
在运行期动态创建一个interface
实例的方法如下:
- 定义一个
InvocationHandler
实例,它负责实现接口的方法调用; - 通过
Proxy.newProxyInstance()
创建interface
实例,它需要3个参数:- 使用的
ClassLoader
,通常就是接口类的ClassLoader
; - 需要实现的接口数组,至少需要传入一个接口进去;
- 用来处理接口方法调用的
InvocationHandler
实例。
- 使用的
- 将返回的
Object
强制转型为接口。
动态代理实际上就是JVM在运行期间动态创建class字节码并加载的过程,其实并没有什么神奇的,只是JVM帮你自动写了一个类,简要代码如下(真实代码会在后面分析):
1 | public class HelloDynamicProxy implements Hello { |
通过反射来调用了InvocationHandler中定义的invoke方法,这个类其实并没有源码,JVM自动生成了字节码并注册。
动态代理API
创建动态代理类会使用到java.lang.reflect.Proxy
类和java.lang.reflect.InvocationHandler
接口。java.lang.reflect.Proxy
主要用于生成动态代理类Class
、创建代理类实例,该类实现了java.io.Serializable
接口。
java.lang.reflect.Proxy
类主要方法如下:
1 | package java.lang.reflect; |
java.lang.reflect.InvocationHandler
接口用于调用Proxy
类生成的代理类方法,该类只有一个invoke
方法。
java.lang.reflect.InvocationHandler
接口代码:
1 | package java.lang.reflect; |
使用Proxy动态加载类
在看Proxy源码的时候,发现了一个令人感兴趣的native方法:
看这个方法的名字和传入的参数也知道这是一个可以通过字节码动态加载类的方法,和我们之前分析过的ClassLoader和Unsafe里面的是一样的,于是我们也可以通过反射调用这个defineClass0方法来动态加载类并创建类对象(在动态代理中,defineClass0是用来向JVM动态注册JVM自动生成的字节码):
1 | package com.anbai.sec.proxy; |
其实步骤也是和之前一样的。
$ProxyXXX类代码分析
java.lang.reflect.Proxy
类是通过创建一个新的Java类(类名为com.sun.proxy.$ProxyXXX)
,然后将其字节码注册到JVM的方式来实现无侵入的类方法代理功能。
动态代理生成出来的类有如下技术细节和特性:
- 动态代理的必须是接口类,通过
动态生成一个接口实现类
来代理接口的方法调用(反射机制
)。 - 动态代理类会由
java.lang.reflect.Proxy.ProxyClassFactory
创建。 ProxyClassFactory
会调用sun.misc.ProxyGenerator
类生成该类的字节码,并调用java.lang.reflect.Proxy.defineClass0()
方法将该类注册到JVM
。- 该类继承于
java.lang.reflect.Proxy
并实现了需要被代理的接口类,因为java.lang.reflect.Proxy
类实现了java.io.Serializable
接口,所以被代理的类支持序列化/反序列化
。 - 该类实现了代理接口类,会通过
ProxyGenerator
动态生成接口类的所有方法, - 该类因为实现了代理的接口类,所以当前类是代理的接口类的实例(
proxyInstance instanceof FileSystem
为true
),但不是代理接口类的实现类的实例(proxyInstance instanceof UnixFileSystem
为false
)。 - 该类方法中包含了被代理的接口类的所有方法,通过调用动态代理处理类(
InvocationHandler
)的invoke
方法获取方法执行结果。 - 该类代理的方式重写了
java.lang.Object
类的toString
、hashCode
、equals
方法。 - 如果动过动态代理生成了多个动态代理类,新生成的类名中的
0
会自增,如com.sun.proxy.$Proxy0/$Proxy1/$Proxy2
。
总结:动态代理具有比静态代码更优越的特性,对我们的方法进行了无侵入的扩展。
主要使用场景:
- 统计方法执行所耗时间。
- 在方法执行前后添加日志。
- 检测方法的参数或返回值。
- 方法访问权限控制。
- 方法
Mock
测试。
其实这些场景都是对原有的方法进行了拓展。
一句话总结动态代理:运行期间JVM自动帮你实现了接口并完成注册,从而可以调用接口的方法而不需要静态运行前自己写类去实现接口,至于具体怎么实现就需要你通过invoke告诉他。