常见的三种动态代理
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))); System.out.println(proxy.getClass() + ": " + Integer.toHexString(System.identityHashCode(proxy)));
Object result; try { System.out.println("【前置通知】准备执行 " + method.getName()); result = method.invoke(target, args); 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 中,最核心的两个角色是:
MethodInterceptor(方法拦截器):相当于 JDK 的 InvocationHandler。
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() {
@Override public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable { System.out.println("目标对象:" + target.getClass() + ": " + Integer.toHexString(System.identityHashCode(target))); System.out.println("代理对象:" + obj.getClass() + ": " + Integer.toHexString(System.identityHashCode(obj)));
Object result; try { System.out.println("【CGLIB 前置通知】准备执行 " + method.getName());
result = method.invoke(target, 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) { if (config.isOptimize() || config.isProxyTargetClass() || hasNoUserSuppliedProxyInterfaces(config)) { Class<?> targetClass = config.getTargetClass(); if (targetClass.isInterface() || Proxy.isProxyClass(targetClass)) { return new JdkDynamicAopProxy(config); } return new ObjenesisCglibAopProxy(config); } else { 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 { ... }
|
实际执行流:
- LoggingAspect 前置逻辑(记录开始)
- TransactionAspect 前置逻辑(开启事务)
- 目标业务方法执行
- TransactionAspect 后置/返回逻辑(提交事务)
- LoggingAspect 后置/返回逻辑(记录结束)