Java动态代理
2023-10-30 15:00:29 # Language # Java

1. 代理模式

代理模式分为 3 种角色

  • Subject(抽象主题角色):定义代理类和真实主题的公共对外方法,也是代理类代理真实主题的方法
  • RealSubject(真实主题角色):真正实现业务逻辑的类
  • Proxy(代理主题角色):用来代理和封装真实主题

根据字节码的创建时机来分类,可以分为静态代理和动态代理:

  • 所谓静态也就是在程序运行前就已经存在代理类的字节码文件,代理类和真实主题角色的关系在运行前就确定了。
  • 动态代理的源码是在程序运行期间由JVM根据反射等机制动态地生成,所以在运行前并不存在代理类的字节码文件

2. 静态代理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class UserServiceProxy implements UserService {
private UserService target; // 被代理的对象

public UserServiceProxy(UserService target) {
this.target = target;
}

public void select() {
before();
target.select(); // 这里才实际调用真实主题角色的方法
after();
}
private void before() { // 在执行方法之前执行
...
}
private void after() { // 在执行方法之后执行
...
}
}

虽然静态代理实现简单,且不侵入原代码,但是,当场景稍微复杂一些的时候,静态代理的缺点也会暴露出来。

  1. 当需要代理多个类的时候,由于代理对象要实现与目标对象一致的接口,有两种方式:
  • 只维护一个代理类,由这个代理类实现多个接口,但是这样就导致代理类过于庞大
  • 新建多个代理类,每个目标对象对应一个代理类,但是这样会产生过多的代理类
  1. 当接口需要增加、删除、修改方法的时候,目标对象与代理类都要同时修改,不易维护

3. 动态代理

为什么类可以动态地生成?

JVM的类加载过程分为 5 个阶段:加载、验证、准备、解析、初始化。其中加载阶段需要完成以下三件事情:

  1. 通过一个类的全限定名来获取定义此类的二进制字节流
  2. 将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构
  3. 在内存中生成一个代表这个类的 java.lang.Class 对象,作为方法区这个类的各种数据访问入口

由于《Java虚拟机规范》对这三点要求并不具体,因此实现非常灵活。例如第一点获取类的二进制字节流就有很多途径:

  • 从ZIP包获取,这是JAR、EAR、WAR等格式的基础
  • 从网络中获取,典型的应用是 Applet
  • 运行时计算生成,这种场景使用最多的是动态代理技术,在 java.lang.reflect.Proxy 类中,就是用了 ProxyGenerator.generateProxyClass() 来为特定接口生成形式为 *$Proxy 的代理类的二进制字节流
  • 由其它文件生成,典型应用是JSP,由JSP文件生成对应的Class文件
  • 从数据库中读取
  • 从加密文件中获取等等

为了让生成的代理类与目标对象(真实主题角色)保持一致性,从现在开始将介绍以下两种最常见的方式:

  1. 通过代理类和目标类实现同一个接口的方式 -> JDK动态代理
  2. 通过代理类继承目标类的方式 -> CGLIB动态代理

3.1 JDK动态代理

JDK动态代理主要涉及两个类:java.lang.reflect.Proxyjava.lang.reflect.InvocationHandler。通过案例来说明

编写一个调用逻辑处理器 LogHandler 类,提供日志增强功能,并实现 InvocationHandler 接口;在 LogHandler 中维护一个目标对象,这个对象是被代理的对象(真实主题角色);在 invoke 方法中编写方法调用的逻辑处理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class LogHandler implements InvocationHandler {

Object target;

public LogHandler(Object target) {
this.target = target;
}

@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.printf("log start time [%s]", new Date());
Object result = method.invoke(target, args);
System.out.printf("log end time [%s]", new Date());
return result;
}
}

编写测试类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
@Test
public void JDKProxyTest() {
// 设置变量可以保存动态代理类,默认名称以 $Proxy0 格式命名
System.getProperties().setProperty("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");

// 1. 创建被代理的对象
UserService userService = new UserServiceImpl();
// 2. 获取对应ClassLoader, userService的类为UserServiceImpl
ClassLoader classLoader = userService.getClass().getClassLoader();
// 3. 获取所有接口的Class, 这里的userService只实现了一个接口UserService
Class<?>[] interfaces = userService.getClass().getInterfaces();
// 4. 创建一个将传给代理类的调用请求处理器, 代理对象上的方法调用实际为调用 InvocationHandler 的 invoke 方法
// 这里创建的是一个自定义的日志处理器, 须传入实际的执行对象 userService
InvocationHandler logHandler = new LogHandler(userService);
/*
5.根据上面提供的信息, 创建代理对象, 在这个过程中,
a.JDK会通过根据传入的参数信息动态地在内存中创建和.class 文件等同的字节码
b.然后根据相应的字节码转换成对应的class,
c.然后调用newInstance()创建代理实例
*/
UserService proxy = (UserService) Proxy.newProxyInstance(classLoader, interfaces, logHandler);
proxy.select();
proxy.update();

// 保存JDK动态代理生成的代理类,类名保存为 UserServiceProxy
ProxyUtils.generateClassFile(userService.getClass(), "UserServiceProxy");
}

运行结果

image-20231019145819867

因为 InvocationHandler 是一个函数接口,可以使用匿名内部类或Lambda表达式简化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@Test
public void JDKProxyByLambdaTest() {
UserService userService = new UserServiceImpl();
UserService proxy = (UserService) Proxy
.newProxyInstance(userService.getClass().getClassLoader(),
userService.getClass().getInterfaces(),
((proxy1, method, args) -> {
System.out.printf("log start time [%s]\n", new Date());
Object result = method.invoke(userService, args);
System.out.printf("log end time [%s]\n", new Date());
return result;
}));
proxy.select();
proxy.update();
}

3.2 代理类源码分析

通过上面测试类的配置,可以在target的类路径下找到代理类的class文件,反编译得到

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
public final class UserServiceProxy extends Proxy implements UserService {
private static Method m1;
private static Method m2;
private static Method m4;
private static Method m0;
private static Method m3;

public UserServiceProxy(InvocationHandler var1) throws {
super(var1);
}

public final boolean equals(Object var1) throws {
// ...
}

public final String toString() throws {
// ...
}

public final void select() throws {
try {
super.h.invoke(this, m4, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}

public final int hashCode() throws {
// ...
}

public final void update() throws {
try {
super.h.invoke(this, m3, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}

static {
try {
m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
m2 = Class.forName("java.lang.Object").getMethod("toString");
m4 = Class.forName("top.whale.proxy.UserService").getMethod("select");
m0 = Class.forName("java.lang.Object").getMethod("hashCode");
m3 = Class.forName("top.whale.proxy.UserService").getMethod("update");
} catch (NoSuchMethodException var2) {
throw new NoSuchMethodError(var2.getMessage());
} catch (ClassNotFoundException var3) {
throw new NoClassDefFoundError(var3.getMessage());
}
}
}

UserServiceProxy 的代码中可以发现:

  • UserServiceProxy 继承了 Proxy 类,并且实现了被代理的所有接口,以及equals、hashCode、toString等方法
  • 由于 UserServiceProxy 继承了 Proxy 类,所以每个代理类都会关联一个 InvocationHandler 方法调用处理器
  • 类和所有方法都被 public final 修饰,所以代理类只可被使用,不可以再被继承
  • 每个方法都有一个 Method 对象来描述,Method 对象在static静态代码块中创建,以 m + 数字 的格式命名
  • 调用方法的时候通过 super.h.invoke(this, m3, (Object[])null); 调用,其中的 super.h.invoke 实际上是在创建代理的时候传递给 Proxy.newProxyInstanceLogHandler 对象,它继承 InvocationHandler 类,负责实际的调用处理逻辑

而 LogHandler 的 invoke 方法接收到 method、args 等参数后,进行一些处理,然后通过反射让被代理的对象 target 执行方法

3.3 CGLIB动态代理

img

CGLIB动态代理是代理类去继承目标类,然后重写目标类的方法,每次调用代理类的方法都会被方法拦截器拦截,在拦截器中才是调用目标类的该方法的逻辑

JDK动态代理中提供了一个Proxy类来创建代理类,而在CGLIB动态代理中也提供了一个类似的类Enhancer

编写 LogInterceptor ,实现 MethodInterceptor,用于方法的拦截回调

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class LogInterceptor implements MethodInterceptor {
/**
* @param o 表示要进行增强的对象
* @param method 表示拦截的方法
* @param objects 数组表示参数列表,基本数据类型需要传入其包装类型,如int-->Integer
* @param methodProxy 表示对方法的代理,invokeSuper方法表示对被代理对象方法的调用
* @return 执行结果
* @throws Throwable 异常
*/
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
System.out.printf("log start time [%s]\n", new Date());
// 注意这里是调用 invokeSuper 而不是 invoke,否则死循环
// methodProxy.invokeSuper执行的是原始类的方法,method.invoke执行的是子类的方法
Object result = methodProxy.invokeSuper(o, objects);
System.out.printf("log end time [%s]\n", new Date());
return result;
}
}

编写测试类

1
2
3
4
5
6
7
8
9
@Test
public void cglibTest() {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(UserDAO.class);
enhancer.setCallback(new LogInterceptor());
UserDAO proxyDao = (UserDAO) enhancer.create();
proxyDao.select();
proxyDao.update();
}

image-20231019162025635

同样可以使用匿名内部类或Lambda表达式简化