前言:Java标准库提供的动态代理(Dynamic Proxy)功能,允许在运行期动态创建一个接口的实例。动态代理实际上是通过JDK在运行期动态创建class字节码并加载的过程。
代理模式
关于代理模式
代理模式(Proxy pattern)是经典的23种设计模式之一,Java的动态代理就是代理模式。首先来看看什么是代理模式?下面是代理模式的UML图:

该模式的参与者:
- Proxy
- 保存一个引用使得代理可以访问实体。Proxy引用Subject。
- 控制实体的存取,负责创建和删除它。
- 提供一个与Subject的接口相同的接口,这样代理可以用来代替实体。
- Suject
- 定义RealSubject和Proxy的共用接口,这样就在任何使用RealSubject的地方都可以使用Proxy。
- RealSubject
- 定义Proxy所代表的的实体。
代理模式的例子
首先定义一个代表“可移动”交通工具的接口:
public interface Moveable {
/**
* 移动指定距离, 以kilometer为单位
* @param km 移动的距离
*/
void move(Integer km);
}
接下来创建一个接口实现类:Metro(地铁),该类实现了Moveable接口的move方法,并模拟了调用该方法后的运行时间。
public class Metro implements Moveable {
@Override
public void move(Integer km) {
try {
System.out.println("Metro is moving ...");
Thread.sleep(km * 1000); // 模拟移动时间
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
然后创建一个可以测试方法性能的PerformanceProxy:
public class PerformanceProxy implements Moveable {
private Moveable m;
public PerformanceProxy(Moveable m) {
this.m = m;
}
@Override
public void move(Integer km) {
long start = System.currentTimeMillis(); // 记录方法调用开始时间
m.move(km); // 方法调用
long end = System.currentTimeMillis(); // 方法结束的时间
System.out.println("耗时: " + (end - start)/1000.0); // 计算耗时
}
}
测试代理模式:
public static void main(String[] args) {
new PerformanceProxy(new Metro()).move(5);
}
---- 测试的结果 ----
Metro is moving ...
耗时: 5.005
这样一个简单的代理模式的例子就完成了。这个模式的好处是以后需要测试其他的交通工具(如飞机、高铁)的性能的时候,只需要让它们(飞机、高铁)实现Moveable接口,测试的时候将它们的实例传给PerformanceProxy代理即可(每个类不需要自己实现测试代码)。
这样,PerformanceProxy可以检测所有实现Moveable的实体类。如果还想实现其他代理,比如给某方法埋点(跟踪该方法的调用次数)、日志打印等等,只需要创建TrackingProxy、LoggingProxy等代理。当然因为这些代理都是实现的Moveable接口,所以它们可以链式使用:
public static void main(String[] args) {
Plane plane = new Plane();
PerformanceProxy pp = new PerformanceProxy(plane);
LoggingProxy lp = new LoggingProxy(PerformanceProxy);
lp.move();
}
静态代理
什么是静态代理?
像上面的例子,先编辑好源代码,自己手动实现代理,再对其编译。这样,在程序运行前,代理类就已经确定,代理类的.class文件(二进制文件)就已经存在了,这就是所谓的静态代理。
动态代理
Java标准库提供的动态代理(Dynamic Proxy)功能,允许在运行期动态创建一个接口的实例。动态代理实际上是通过JDK在运行期动态创建class字节码并加载的过程。将上面静态代理的例子用动态代理实现如下:
public static void main(String[] args) {
final Metro metro = new Metro();
Moveable m = (Moveable) Proxy.newProxyInstance(Metro.class.getClassLoader(), // 被代理的class loader
new Class[]{Moveable.class}, // 传入要实现的接口
new InvocationHandler() { // 传入处理调用方法的InvocationHandler (匿名类)
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { // 1
long start = System.currentTimeMillis();
Object res = method.invoke(metro, args); // 2
long end = System.currentTimeMillis();
System.out.println("耗时: " + (end - start)/1000.0);
return res; // 3
}
});
m.move(5);
}
// 1. invoke中的参数proxy就是动态创建的proxy;method为接口的方法:可根据不同的方法做不同的处理。
// 2. method.invoke(metro, args)等价于这里的metro.move(),这和js中的function.call(obj, param)类似。
// 3. 2中方法的返回值,如果是void方法返回null,如果是基本数据类型则会自动装箱(例如:int会自动转换成Integer)。
上面的代码测试可以得到和上面静态代理同样的结果。从动态代理可以看出主要是调用了Proxy.newProxyInstance(...)操作以及InvocationHandler的invoke的方法的实现。要查看JDK运行时做了什么,可以在测试代码上加上以下代码:
// env: JDK1.8 / IntelliJ IDEA 15 / macOS Majave
System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");
运行之后可以看到IDEA多了个com文件夹,动态生成的proxy就在里面:
# 动态生成代理的位置
...
|-- com/
| |--sun/
| | |--proxy/
| | | |--$Proxy0.class
...
下面是反编译$Proxy0.class后的部分代码:可以看出该代理继承于Proxy类并实现了Moveable接口。主要实现了Object类的equals、hashCode和toString基本方法和Moveable的接口方法move。
// ----- $Proxy0.class
public final class $Proxy0 extends Proxy implements Moveable {
...
public $Proxy0(InvocationHandler var1) throws { super(var1); }
public final void move(Integer var1) throws {
try {
super.h.invoke(this, m3, new Object[]{var1}); // 1
} ...
}
public final boolean equals(Object var1) throws { ... }
public final String toString() throws { ... }
public final int hashCode() throws { ... }
static {
try {
m1 = Class.forName("java.lang.Object").getMethod("equals", new Class[]{Class.forName("java.lang.Object")});
m3 = Class.forName("proxy.lizi.Moveable").getMethod("move", new Class[]{Class.forName("java.lang.Integer")});
...
} ...
}
}
上面代码中(1位置)的super.h就是手动编写的InvocationHandler实例,m3可以从后面的代码看出就是move方法。所以调用动态代理的move实际就是调用InvocationHandler实例的invoke方法。下面的时序图简易的描述了上述动态代理的创建及执行过程:

注:由JDK源码可了解到动态代理的实现的方式是:Java的反射机制+ASM(一个可操作字节码的框架)。反射机制可动态获取类及其方法,但仅限于获取及调用;ASM是可以直接操作字节码(.class文件),以此来实现动态创建代理类。
动态代理的应用例子
动态代理的运用之MyBatis
MyBatis是一款优秀的持久层框架,它支持定制化SQL、存储过程以及高级映射。使用MyBatis时一般只需要写好Mapper.xml配置及接口文件XXXMapper.java,如下:
<!-- XXXMapper.xml -->
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="tk.mybatis.simple.mapper.UserMapper">
<select id="selectById" resultMap="userMap">
SELECT * from sys_user where id = #{id}
</select>
<select id="selectAll" resultType="tk.mybatis.simple.model.SysUser">
select id,
user_name userName
from sys_user
</select>
...
</mapper>
// ---- UserMapper.java
public interface UserMapper {
/**
* 通过 id 查询用户
*
* @param id
* @return
*/
SysUser selectById(Long id);
/**
* 查询所有用户
*
* @return
*/
List<SysUser> selectAll();
}
Mapper接口没有实现类却能被正常调用接口方法,这是因为MyBaits在Mapper接口上使用了动态代理的。基本原理就是通过获取XML文件的配置信息再根据Mapper接口定义实现其接口方法。
动态代理运用之Spring
Spring框架的核心是IoC(Inversion of Control)和AOP(Aspect Oriented Programming)。Spring bean的依赖注入(DI:可以通过XML方式和注解方式)以及面向切面的方法织入,实质是使用了cglib的动态代理。cglib(Code Generation Library)的原理是动态生成了一个要代理类的子类,子类重写要代理的类的所有不是final的方法,在子类中采用方法拦截的技术拦截所有父类方法的调用,顺势织入横切逻辑。cglib的底层也是使用了ASM框架。