Proxy代理能拦截方法调用,是因为它生成新类(JDK)或子类(CGLIB),将调用重定向至InvocationHandler或MethodInterceptor;@Transactional等注解仅对public外部调用生效,内部this调用绕过代理。

Proxy 代理对象为什么能拦截方法调用
因为 Proxy 创建的代理对象,本质是实现了目标接口的新类(JDK 动态代理)或继承目标类的子类(CGLIB),所有方法调用都会先经过 InvocationHandler 或 MethodInterceptor 的统一入口。这不是“在原方法里加代码”,而是把调用链重定向了。
常见错误现象:ClassCastException —— 试图把代理对象强转成具体实现类(如 (UserServiceImpl) proxy),而它只实现了接口;或 CGLIB 代理时目标类被 final 修饰,导致 net.sf.cglib.core.CodeGenerationException。
- JDK 代理要求目标类至少实现一个接口,否则会抛
IllegalArgumentException: target must implement at least one interface - CGLIB 代理不要求接口,但无法代理
final类、final方法,且需添加cglib-nodep依赖(Spring Boot 2.2+ 默认已含) - Spring AOP 默认优先用 JDK 代理;若目标类无接口,则自动 fallback 到 CGLIB —— 这个切换对开发者透明,但调试时要注意代理类型差异
为什么 @Transactional 和 @Cacheable 必须写在 public 方法上
因为 Proxy 拦截的是「外部对代理对象的调用」,而非内部 this 调用。private / protected / package-private 方法不会进入代理逻辑,相当于绕过了整个切面。
使用场景:你在 serviceA.doWork() 里直接调用 this.doInternal(),哪怕 doInternal() 上标了 @Transactional,事务也不会生效——此时执行的是目标对象自身的方法,没经过 InvocationHandler。
- Spring 的
ProxyFactory只对代理对象暴露的 public 接口方法做增强 - 想在类内调用也触发切面?只能通过
ApplicationContext获取代理对象再调用,或用AopContext.currentProxy()(需开启exposeProxy=true配置) - 这个限制不是 Proxy 的 bug,而是设计使然:代理模型天然不覆盖 self-invocation
如何验证当前对象是不是代理对象、用了哪种代理
别猜,直接查。Spring 提供了 AopProxyUtils 和 Advised 接口来 inspect 代理结构。
实操建议:
- 判断是否为代理:
AopUtils.isAopProxy(bean)返回 true 即说明被 Spring AOP 增强过 - 区分代理类型:
AopUtils.isJdkDynamicProxy(bean)或AopUtils.isCglibProxy(bean) - 获取原始目标对象(慎用):
Advised advised = (Advised) bean; Object target = advised.getTargetSource().getTarget();—— 仅用于调试,生产中不应依赖目标实例 - 注意:
toString()或日志打印代理对象时,常看到类似com.sun.proxy.$Proxy42(JDK)或UserService$$EnhancerBySpringCGLIB$$a1b2c3d4(CGLIB),这是最直观的线索
为什么有时候 @Aspect 注解的切面不生效
不是代理没起作用,很可能是切点表达式没匹配到实际调用路径,或者代理对象根本没被 Spring 容器管理。
典型问题:
- 切点写成
@Pointcut("execution(* com.example.service.UserService.*(..))"),但实际调用的是UserServiceImpl的方法——JDK 代理下,代理对象类型是接口,必须按接口路径写切点,否则不匹配 - 手动 new 出的对象(
new UserServiceImpl())不受 Spring 管理,自然没有代理,切面无效 - 切面类没加
@Component或没被@EnableAspectJAutoProxy扫描到,@Aspect类本身没注册进容器 - 同一个 Bean 内部方法调用,如前所述,绕过代理,切面不触发
最易被忽略的一点:切点表达式中的包名、方法名拼写错误,或用了通配符但未考虑重载/泛型擦除,导致匹配失败——建议在切面里加日志,确认 joinPoint.getSignature().toShortString() 输出是否符合预期。


















