Java 体系常见的三种动态代理实现

常见的三种动态代理

Java 生态中常见的动态代理实现主要有 3 类(如果算上较底层的字节码操作,例如老牌的 Javassist,可以算 4 类)。


JDK 原生(基于接口)

这是 Java 原生自带的工具,主要涉及 java.lang.reflect.Proxy 和 InvocationHandler。

  • 原理:利用反射机制。在内存中动态创建一个代理类,这个代理类实现了和你目标类一样的接口。
  • 硬性要求:目标类必须实现接口。如果没有接口,JDK 代理就没法玩了。
  • 特点:生成的代理类是接口的实现者。

简单示例:

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
public interface BankService {
void transfer(String from, String to, double amount) throws Exception;
}

public class BankServiceImpl implements BankService {
@Override
public void transfer(String from, String to, double amount) throws Exception {
System.out.println("【核心业务】" + from + " 向 " + to + " 转账 " + amount + " 元");
if (amount > 10000) {
throw new Exception("单笔转账金额超限!");
}
}
}

@Test
public void test01() throws Exception {
// 真实目标对象
BankService target = new BankServiceImpl();

// 代理对象
BankService bankServiceProxy = (BankService) Proxy.newProxyInstance(
BankService.class.getClassLoader(),
new Class[]{BankService.class}, // 接口数组
new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println(target.getClass() + ": " + Integer.toHexString(System.identityHashCode(target))); // BankServiceImpl: 2e817b38
System.out.println(proxy.getClass() + ": " + Integer.toHexString(System.identityHashCode(proxy))); // jdk.proxy1.$Proxy0: 7a07c5b4 通常用于返回代理对象以支持链式调用

Object result;
try {
System.out.println("【前置通知】准备执行 " + method.getName());
result = method.invoke(target, args); // 第一个参数必须是 target(真实对象),而不是 proxy(代理对象)
System.out.println("【返回通知】执行成功");
return result;
} catch (InvocationTargetException e) {
System.out.println("【异常通知】" + e.getTargetException().getMessage());
throw e.getTargetException();
} finally {
System.out.println("【后置通知】清理资源");
}
}
});

// 调用代理对象
bankServiceProxy.transfer("张三", "李四", 100000);
}


CGLIB(基于继承)

CGLIB (Code Generation Library) 是一个强大的第三方字节码生成库。

  • 原理:利用 ASM 框架,在内存中动态构建一个目标类的子类,并重写父类的方法。
  • 优势:目标类不需要实现接口。
  • 硬性要求:目标类不能是 final 的,目标方法也不能是 final 的(因为子类没法重写 final 方法)。
  • 特点:Spring 会根据情况自动切换,有接口用 JDK,没接口用 CGLIB。

要实现 CGLIB 版本的代理,逻辑结构与 JDK 动态代理非常相似,但核心驱动力从 “接口” 变成了 “继承”。在 CGLIB 中,最核心的两个角色是:

  1. MethodInterceptor(方法拦截器):相当于 JDK 的 InvocationHandler。
  2. Enhancer(增强器):相当于 JDK 的 Proxy,用来创建代理子类。

由于 CGLIB 不是 JDK 自带的,你需要引入相关的库(如果你在使用 Spring,它已经内嵌在 spring-core 里了)。

1
2
3
4
5
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.3.0</version>
</dependency>
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
@Test
public void test02() throws Exception {
// 真实目标对象
BankServiceImpl target = new BankServiceImpl();

// 初始化增强器
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(BankServiceImpl.class);
enhancer.setCallback(new MethodInterceptor() {
/**
* @param obj 代理对象本身 (相当于 JDK 的 proxy)
* @param method 目标方法
* @param args 参数
* @param proxy CGLIB 提供的用于调用父类方法的代理对象 (MethodProxy)
*/
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
System.out.println("目标对象:" + target.getClass() + ": " + Integer.toHexString(System.identityHashCode(target))); // class BankServiceImpl: 1f2586d6
System.out.println("代理对象:" + obj.getClass() + ": " + Integer.toHexString(System.identityHashCode(obj))); // class BankServiceImpl$$EnhancerByCGLIB$$f605a54d: 10683d9d

Object result;
try {
System.out.println("【CGLIB 前置通知】准备执行 " + method.getName());

// 方式 A:使用目标对象调用 (推荐,逻辑最清晰)
result = method.invoke(target, args);

// 方式 B:使用 MethodProxy 调用父类方法 (性能更高,不需要目标对象)
// result = proxy.invokeSuper(obj, args);

System.out.println("【CGLIB 返回通知】执行成功");
return result;
} catch (InvocationTargetException e) {
System.out.println("【CGLIB 异常通知】" + e.getTargetException().getMessage());
throw e.getTargetException();
} finally {
System.out.println("【CGLIB 后置通知】清理资源");
}
}
});

// 创建和使用代理对象
BankServiceImpl bankServiceProxy = (BankServiceImpl) enhancer.create();
bankServiceProxy.transfer("张三", "李四", 100000);
}


ByteBuddy(现代首选)

在现代 Java 开发(尤其是最新的 Spring Boot)中,ByteBuddy 已经逐渐取代了 CGLIB,是现代框架的首选。它比 CGLIB 更快,对 Java 新版本的支持更好,API 也更优雅。Spring Boot 2.x/3.x 内部大量使用了 ByteBuddy 来处理各种增强逻辑。

1
2
3
4
5
<dependency>
<groupId>net.bytebuddy</groupId>
<artifactId>byte-buddy</artifactId>
<version>1.14.12</version>
</dependency>
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
@Test
public void test03() throws Exception {
BankServiceImpl target = new BankServiceImpl();

BankServiceImpl proxy = new ByteBuddy()
.subclass(BankServiceImpl.class) // 指定父类(继承模式)
.method(ElementMatchers.named("transfer")) // 匹配要拦截的方法
.intercept(InvocationHandlerAdapter.of(new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
System.out.println("【ByteBuddy 前置】验证身份...");
Object result = method.invoke(target, args);
System.out.println("【ByteBuddy 返回】记录日志...");
return result;
} catch (InvocationTargetException e) {
System.out.println("【ByteBuddy 异常】" + e.getTargetException().getMessage());
throw e.getTargetException();
} finally {
System.out.println("【ByteBuddy 后置】销毁上下文...");
}
}
}))
.make()
.load(BankServiceImpl.class.getClassLoader())
.getLoaded()
.getDeclaredConstructor()
.newInstance();

proxy.transfer("张三", "李四", 500);
}


Spring 是怎么嵌入代理的?

前面我们介绍了 spring 的三级缓存机制,当三级缓存里的 “ObjectFactory.getObject()” 被调用时,它内部其实就是在做这件事:

  • 判断当前 Bean 是否需要增强(比如有没有 @Transactional)。
  • 如果需要,它会问:“这个 Bean 有接口吗?”
    • 有接口:调用 Proxy.newProxyInstance(…) 生成 JDK 代理。
    • 没接口:调用 CGLIB(或 ByteBuddy)生成子类代理。

在 Spring 的源码中,负责决定用哪种代理的类叫 DefaultAopProxyFactory。它的逻辑非常严谨,依然是围绕 JDK 和 CGLIB 展开的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public AopProxy createAopProxy(AdvisedSupport config) {
// 1. 如果设置了 optimize (优化) 或 proxyTargetClass (直接强制 CGLIB)
// 2. 或者目标类没有实现接口
if (config.isOptimize() || config.isProxyTargetClass() || hasNoUserSuppliedProxyInterfaces(config)) {
Class<?> targetClass = config.getTargetClass();
// 如果是接口或者是代理类,还是走 JDK
if (targetClass.isInterface() || Proxy.isProxyClass(targetClass)) {
return new JdkDynamicAopProxy(config);
}
// 否则,统统走 CGLIB
return new ObjenesisCglibAopProxy(config);
} else {
// 默认走 JDK
return new JdkDynamicAopProxy(config);
}
}

既然 CGLIB(或 ByteBuddy)能通过继承解决 “没接口” 的尴尬,JDK 动态代理这种“强制要求接口”的设计看似是一种枷锁,但 Spring 坚持使用它,主要基于以下几个深层次的原因:

  • 第一,面向接口编程。接口是契约,接口定义了 “做什么”,而实现类定义了 “怎么做”。如果一个 Bean 实现了接口,Spring 认为你应该通过接口来引用它。如果你之后想把本地实现换成远程 RPC 调用(如 Dubbo/gRPC),只要接口不变,调用方代码一行都不用改。
  • 第二,性能与原生优势,在 Java 的早期版本中,JDK 代理确实比 CGLIB 慢,但随着 JVM 的不断演进(尤其是 Java 8 之后),它是 JVM 原生支持的,生成的字节码非常简单,不需要像 CGLIB 那样生成一个庞大且复杂的子类;
  • 第三,规避“继承”带来的局限性。

    • Final 困境:CGLIB 无法代理 final 类或 final 方法,因为 Java 不允许继承或重写它们。
    • 构造函数问题:CGLIB 创建子类代理时,目标类的构造函数会被调用两次(一次是目标类本身,一次是子类代理对象)。如果构造函数里有复杂的逻辑,可能会出问题。
    • 私有方法无法切入:由于子类看不见父类的 private 方法,CGLIB 对私有方法无能为力(虽然 JDK 代理也不行,但这是设计上的统一)。
  • 第四,架构的稳定性。

    • 无需第三方依赖:JDK 代理不需要引入任何额外的 Jar 包。对于 Spring 这种顶级框架,减少对底层字节码库(如 ASM)的直接依赖,能极大降低版本冲突的概率。
    • 双保险策略:Spring 采用的是“首选 JDK,保底 CGLIB”的策略。这种设计让容器具有极强的兼容性。


Spring AOP 怎么实现 @Order

在 Spring AOP 中,当一个目标方法(Join Point)被多个切面(Aspect)匹配时,Spring 会形成一个拦截器链(Interceptor Chain)。@Order 的作用就是决定这个链条中各个拦截器的执行顺序。

实现 @Order 的核心原理可以概括为:在容器启动阶段进行逻辑排序,在运行阶段按序执行。


排序的核心规则

Spring 使用了一个专门的比较器:AnnotationAwareOrderComparator。 它的排序规则非常简单直观:

  • 数值越小,优先级越高(越靠外层)。
  • 默认值是 Ordered.LOWEST_PRECEDENCE(即 Integer.MAX_VALUE),优先级最低。
  • 如果没有标注 @Order,则排在最后。


源码层面的实现步骤

第一步:收集所有的切面 Bean

在 Spring 容器启动的 refresh() 过程中,AnnotationAwareAspectJAutoProxyCreator 会扫描所有的切面类(标注了 @Aspect 的 Bean)。

第二步:识别 Order 信息

Spring 会通过 OrderUtils 工具类去读取切面类上的注解:

  • 首先找 org.springframework.core.annotation.Order。
  • 如果没有,再找 Java 标准的 javax.annotation.Priority。
  • 如果该 Bean 实现了 Ordered 接口,则调用 getOrder() 方法。

第三步:对拦截器链进行排序

是最关键的一步。Spring 会将每个切面里的通知(Advice)转换成 MethodInterceptor。在将这些拦截器组合成链条之前,会调用:

1
AnnotationAwareOrderComparator.sort(advisors);

此时,所有的切面就按照 @Order 指定的顺序排列好了。排序后的执行逻辑就像一个 剥洋葱 的过程:

  • 进入时:Order 值小的切面先执行(最外层)。
  • 退出时:Order 值小的切面后执行(最后处理返回结果)。


典型业务示例

假设有两个切面处理同一个转账业务:LoggingAspect (日志) 和 TransactionAspect (事务)。

1
2
3
4
5
6
7
@Order(1) // 优先级高,在外层
@Aspect
public class LoggingAspect { ... }

@Order(2) // 优先级低,在内层
@Aspect
public class TransactionAspect { ... }

实际执行流:

  1. LoggingAspect 前置逻辑(记录开始)
  2. TransactionAspect 前置逻辑(开启事务)
  3. 目标业务方法执行
  4. TransactionAspect 后置/返回逻辑(提交事务)
  5. LoggingAspect 后置/返回逻辑(记录结束)