aop\动态代理什么时候失效?为什么失效?
aop\动态代理什么时候失效?为什么失效?
动态代理失效
一、JDK 动态代理的硬性限制
1. 目标对象没有实现任何接口
public class UserService { // 没有实现任何接口
public void save() { ... }
}
// 调用 Proxy.newProxyInstance(classLoader, interfaces, handler)
// ❌ 报错:接口数组为空,无法生成代理
原因:JDK 动态代理生成的代理类会实现传入的接口数组,代理类本身是 java.lang.reflect.Proxy 的子类。如果没有接口,就没有可以“实现”的契约,代理类无法定义任何方法。
结论:没有接口 → JDK 动态代理彻底失效,只能改用 CGLIB。
2. 接口中的方法被 final 或 static 修饰(逻辑上无法拦截)
虽然编译期就会报错(接口方法不能是 final),但这里提一下:
- 接口方法默认
public abstract,JDK 代理可以正常拦截。 - 但代理对象无法拦截
static方法(因为静态方法属于类,不属于实例)。
public interface UserMapper {
static void staticMethod() { ... } // 代理无法拦截
}
原因:InvocationHandler.invoke() 只拦截实例方法调用,静态方法通过 接口名.方法名() 调用,不经过代理对象。
二、CGLIB 动态代理的硬性限制
3. 目标类是 final 的
public final class UserService { // final 类
public void save() { ... }
}
// Enhancer.create() ❌ 报错:无法继承 final 类
原因:CGLIB 通过继承目标类生成子类(代理类)。final 类不能被继承,所以代理创建失败。
4. 目标方法是 final 的
public class UserService {
public final void save() { ... } // final 方法
}
// 代理对象可以创建成功,但调用 save() 时无法拦截
原因:CGLIB 通过覆写(Override)父类方法来实现拦截。final 方法无法被覆写,所以代理会直接调用父类原方法,MethodInterceptor 不会被触发。
注意:这种情况不算“完全失效”,而是“部分失效”——代理对象存在,但该方法上的增强逻辑不执行。
5. 目标方法是 private 的
public class UserService {
private void save() { ... } // private 方法
}
原因:子类无法继承 private 方法,更不可能覆写它,所以 CGLIB 无法拦截。
三、JDK 和 CGLIB 共同失效的场景
6. 目标对象本身就是代理对象(代理套代理)
虽然技术上可以嵌套代理,但如果处理不当(比如死循环、类加载器冲突),会导致 Proxy.newProxyInstance 或 Enhancer.create() 抛出异常。
7. 类加载器不一致(ClassLoader 问题)
JDK 动态代理要求代理类和目标类使用相同的 ClassLoader(或代理类的 ClassLoader 能访问到目标类的接口)。如果传入的 ClassLoader 无法加载目标类,会抛出 IllegalArgumentException。
8. InvocationHandler 或 MethodInterceptor 中未调用 invoke() / intercept()
这不是“代理失效”,而是“增强逻辑失效”——但如果你在拦截器中什么也不做,直接返回 null,代理对象调用方法会返回 null,看起来就像“失效”了。
面试一句话总结
“动态代理失效的本质,是代理技术的前提条件被破坏:JDK 代理要求目标有接口,CGLIB 代理要求目标类和目标方法不能被 final 修饰。任何绕过代理对象的直接调用(如
this.调用)也属于代理失效。其他情况如类加载器不一致、目标方法是 private/static,都会导致代理无法创建或无法拦截。”
AOP 失效
一、场景一:类内部方法调用(最常见!)
@Service
public class UserService {
public void methodA() {
System.out.println("A执行");
this.methodB(); // ❌ 这里直接调用内部方法
}
@Transactional
public void methodB() {
System.out.println("B执行");
}
}
现象:调用 methodA() 时,methodB 上的 @Transactional 不生效。
原因(底层原理):
- Spring AOP 生成的是代理对象(假设叫
UserService$$Proxy)。 - 外部调用
userService.methodA()时,走的是代理对象 → 代理逻辑生效。 - 但代理对象内部调用
methodB()时,this指向的是原始目标对象(真实实例),而不是代理对象。 - 所以
methodB()直接被原始对象调用,绕过了代理,事务等增强自然失效。
解决方案:
@Autowired
private UserService self; // 注入自己(代理对象)
public void methodA() {
self.methodB(); // 走代理,事务生效
}
或者:
((UserService) AopContext.currentProxy()).methodB();
二、场景二:方法不是 public 的
@Transactional // ❌ 不生效
private void updatePrivate() { ... }
@Transactional // ❌ 不生效(除非特殊配置)
protected void updateProtected() { ... }
原因(底层原理):
- Spring 默认使用 JDK 动态代理(基于接口)或 CGLIB(基于继承子类)。
- 无论哪种,代理都只能拦截
public方法。 - 为什么?因为 JDK 代理只能代理接口中的
public方法;CGLIB 虽然可以代理protected,但 Spring 默认配置下为了统一,只拦截public。 - 更深层:
@Transactional等注解的解析逻辑在AbstractFallbackTransactionAttributeSource中,默认忽略非public方法。
解决方案:将方法改为 public,或启用 proxy-target-class=true + 配置 AspectJ 编译期织入(不推荐)。
三、场景三:代理方式不匹配(接口 vs 实现类)
// Service 实现了接口
public interface UserService { void save(); }
@Service
public class UserServiceImpl implements UserService { ... }
// 但 AOP 配置强制使用 CGLIB,而类又是 final 的
@EnableAspectJAutoProxy(proxyTargetClass = true)
现象:Spring 容器启动失败,报错 “无法创建代理”。
原因:
- 强制 CGLIB 时,Spring 会尝试生成目标类的子类。
- 如果目标类是
final的,CGLIB 无法继承 → 代理创建失败。 - 如果目标方法被
final修饰,CGLIB 无法覆盖 → 该方法上的 AOP 不生效。
解决方案:避免将 Bean 类或方法声明为 final。
四、场景四:循环依赖 + AOP 导致代理失效(罕见但致命)
@Service
public class A {
@Autowired private B b;
@Transactional public void aMethod() { ... }
}
@Service
public class B {
@Autowired private A a;
@Transactional public void bMethod() { ... }
}
现象:可能启动失败,或事务偶尔不生效。
原因(底层原理):
- Spring 解决循环依赖时,会使用三级缓存提前暴露
ObjectFactory。 - 如果 Bean 被 AOP 增强,Spring 需要在创建早期就生成代理对象(通过
EarlyBeanReference)。 - 如果 AOP 的代理对象和最终注入的不是同一个实例(比如
@Async和@Transactional混用),可能导致某个切面失效。
解决方案:使用 @Lazy 延迟注入,或重构代码避免循环依赖。
五、场景五:使用 new 关键字创建对象=
UserService service = new UserService(); // ❌ 不是 Spring Bean
service.save(); // AOP 完全不生效
原因:对象没有经过 Spring 容器管理,没有代理过程,直接就是原始对象。
解决方案:所有需要 AOP 增强的 Bean,必须通过 Spring 容器获取(@Autowired 或 ApplicationContext.getBean())。
六、场景六:目标类没有被 Spring 管理
// 虽然加了 @Service,但包扫描没扫到
// 或者 @ComponentScan 没有包含该包路径
现象:Bean 根本没有注册到容器,自然没有代理。
解决方案:检查包扫描路径,或显式使用 @Import / @Bean 注册。
面试终极总结(背下来)
“AOP 失效的本质,是目标方法的调用没有经过代理对象。常见原因有 6 类:
- 内部调用:
this调用绕过了代理;- 非 public 方法:代理默认只拦截
public;- 代理方式冲突:
final类/方法无法被 CGLIB 代理;- 循环依赖:代理对象提前暴露导致切面错乱;
- 手动 new 对象:脱离 Spring 容器管理;
- 包扫描遗漏:Bean 未被注册。
解决思路就是:确保所有增强方法走代理对象,且满足代理技术的前提条件。 ”
浙公网安备 33010602011771号