代理从哪来
第 1 篇立了认知:容器里住的是代理。本篇看代理的真身。动态代理不是 Spring 的发明,是 Java 层面的能力,Spring 用了两条路实现:JDK 动态代理与 CGLIB。
JDK 动态代理:接口的影子
运行时生成一个实现目标接口的代理类(类名形如 $Proxy0),所有方法调用统一转发给 InvocationHandler——手写二十行看穿本质:
UserService target = new UserServiceImpl();
UserService proxy = (UserService) Proxy.newProxyInstance(
target.getClass().getClassLoader(),
target.getClass().getInterfaces(),
(p, method, args) -> {
long start = System.currentTimeMillis();
Object result = method.invoke(target, args); // 反射调用原方法
System.out.println(method.getName() + " 耗时:" + (System.currentTimeMillis() - start));
return result;
});
proxy.query(); // 调的是代理 → 计时逻辑生效;target.query() 则完全不会硬性前提:目标必须实现接口——代理类的骨架是 implements,没有接口就没地方安放方法转发。
CGLIB:子类的偷天换日
没有接口怎么办?CGLIB 运行时生成目标类的子类,重写方法织入拦截器(MethodInterceptor)。也正因为靠继承,它代理不了 final 类和 final 方法(无法继承与重写),private 方法同样不在拦截范围。两个常见误区顺便纠正:其一,「有接口用 JDK、没接口用 CGLIB」是老版 Spring 的分流逻辑,Spring Boot 2.x 起 spring.aop.proxy-target-class 默认 true,全程默认 CGLIB,接口有无不再是分水岭;其二,CGLIB 用 FastClass 机制(给方法建索引直接调用,绕开反射)把性能做了上去,现代版本两者差距可忽略,选型不必纠结。
从原理看懂自调用失效
现在可以用原理重述第 1 篇的事故:拦截的入口在代理对象的方法调用上(InvocationHandler 或 MethodInterceptor)。类内部 this.audit() 里的 this 是原始对象而不是代理,调用根本没经过拦截器入口——增强自然消失。JDK 代理与 CGLIB 代理一视同仁。原理懂了,失效场景不用背,推都推得出来。
两条代理的路铺好了。但 Spring AOP 的关键词还有切点、通知、织入——@Around 怎么圈住目标方法、一个切面类怎么被翻译成代理逻辑,下一篇拆。
评论 (0)