很多人以为Spring AOP就是"动态代理",但当你发现
@Transactional在某些场景下失效,或者想在不重启的情况下动态添加切面时,才发现自己对AOP的理解只停留在表面。这篇从ProxyFactory开始,讲清楚Spring是如何在运行时决定用JDK Proxy还是CGLIB的,以及AspectJ编译期织入和Spring AOP的本质区别。
一、Spring AOP的两种实现方式
1.1 JDK动态代理
java
// 只能代理接口
public interface UserService {
void save(User user);
}
@Service
public class UserServiceImpl implements UserService {
public void save(User user) { ... }
}
// Spring生成的代理类(简化)
class $Proxy0 implements UserService {
private UserService target;
public void save(User user) {
// 前置通知
before();
try {
target.save(user); // 调用目标方法
} finally {
// 后置通知
after();
}
}
}
限制:只能代理实现了接口的类。如果类没有实现任何接口,JDK Proxy无能为力。
1.2 CGLIB代理
java
// 可以代理具体类
@Service
public class OrderService { // 没有实现接口
public void create(Order order) { ... }
}
// Spring生成的代理类(简化)
class OrderService$$EnhancerByCGLIB extends OrderService {
public void create(Order order) {
MethodInterceptor interceptor = ...;
interceptor.intercept(this, method, args, proxy);
}
}
特点:通过继承实现,所以不能代理final类或final方法。
二、Spring如何选择代理方式?
核心逻辑在DefaultAopProxyFactory
java
public AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException {
if (config.isOptimize() || config.isProxyTargetClass()
|| hasNoUserSuppliedProxyInterfaces(config)) {
Class<?> targetClass = config.getTargetClass();
if (targetClass == null) {
throw new AopConfigException("TargetSource cannot determine target class");
}
// 如果是接口或已经是代理类 → JDK Proxy
if (targetClass.isInterface() || Proxy.isProxyClass(targetClass)) {
return new JdkDynamicAopProxy(config);
}
// 否则 → CGLIB
return new ObjenesisCglibAopProxy(config);
} else {
// 默认:如果有接口就用JDK Proxy
return new JdkDynamicAopProxy(config);
}
}
决策树:
1. 配置了 proxyTargetClass=true? → 是 → CGLIB
2. 目标类是接口? → 是 → JDK Proxy
3. 目标类没有实现任何用户定义的接口? → 是 → CGLIB
4. 默认 → JDK Proxy
2.1 强制使用CGLIB
yaml
spring:
aop:
proxy-target-class: true # 全局强制CGLIB
或者在特定Bean上:
java
@Configuration
@EnableAspectJAutoProxy(proxyTargetClass = true)
public class AppConfig { ... }
三、ProxyFactory:AOP的核心工厂
java
// 手动创建AOP代理
ProxyFactory factory = new ProxyFactory(new UserServiceImpl());
factory.addAdvice(new TransactionInterceptor());
factory.addAdvisor(new DefaultPointcutAdvisor(pointcut, advice));
UserService proxy = (UserService) factory.getProxy();
关键组件:
| 组件 | 作用 |
|---|---|
| TargetSource | 提供目标对象(可以是单例、原型、ThreadLocal等) |
| Advisor | 切点+通知的组合 |
| Pointcut | 匹配哪些方法需要被拦截 |
| Advice | 拦截后要执行的逻辑 |
四、@Aspect解析流程
4.1 自动代理创建器
Spring Boot启动时,AnnotationAwareAspectJAutoProxyCreator会被注册为BeanPostProcessor:
java
@Component
public class LoggingAspect {
@Around("@annotation(org.springframework.transaction.annotation.Transactional)")
public Object logTransaction(ProceedingJoinPoint pjp) throws Throwable {
log.info("Starting transaction");
try {
return pjp.proceed();
} finally {
log.info("Ending transaction");
}
}
}
解析步骤:
- 扫描@Aspect注解的类
- 解析@Pointcut表达式,转换为Pointcut对象
- 解析@Before/@After/@Around等通知,转换为Advice对象
- 创建Advisor,将Pointcut和Advice组合
- 注册到BeanFactory,在Bean初始化时应用
4.2 Bean初始化时的代理创建
java
// AbstractAutoProxyCreator.postProcessAfterInitialization()
public Object postProcessAfterInitialization(Object bean, String beanName) {
if (this.advisedBeans.containsKey(beanName)) {
return bean;
}
// 检查是否需要代理
if (isInfrastructureClass(bean.getClass()) || shouldSkip(bean.getClass(), beanName)) {
this.advisedBeans.put(beanName, Boolean.FALSE);
return bean;
}
// 获取Advisor列表
Object[] specificInterceptors = getAdvicesAndAdvisorsForBean(...);
if (specificInterceptors != DO_NOT_PROXY) {
// 创建代理
Object proxy = createProxy(bean.getClass(), beanName, specificInterceptors, new SingletonTargetSource(bean));
this.proxyTypes.put(cacheKey, proxy.getClass());
return proxy;
}
this.advisedBeans.put(beanName, Boolean.FALSE);
return bean;
}
五、AspectJ编译期织入 vs Spring AOP
| 维度 | Spring AOP | AspectJ(编译期/加载期) |
|---|---|---|
| 实现方式 | 动态代理(运行时) | 字节码修改(编译期/加载期) |
| 性能 | 略低(每次调用走代理) | 高(直接织入字节码) |
| 功能 | 仅支持方法拦截 | 支持字段访问、构造器调用等 |
| 自调用 | 不支持(绕过代理) | 支持(直接织入) |
| 复杂度 | 低(Spring内置) | 高(需要ajc编译器或Java Agent) |
5.1 AspectJ加载期织入(LTW)
xml
<!-- aop.xml -->
<aspectj>
<aspects>
<aspect name="com.example.LoggingAspect"/>
</aspects>
<weaver options="-verbose">
<include within="com.example..*"/>
</weaver>
</aspectj>
bash
# 启动时指定Java Agent
-javaagent:path/to/aspectjweaver.jar
适用场景:需要对第三方库进行增强,且无法修改源码时。
六、常见陷阱
6.1 自调用失效
java
@Service
public class UserService {
@Transactional
public void save(User user) {
this.validate(user); // ❌ 不走事务
}
@Transactional
public void validate(User user) { ... }
}
原因:this.validate()是直接调用,绕过了代理对象。
解决:
- 注入自己:@Autowired private UserService self; self.validate(user);
- 使用AopContext.currentProxy()
- 重构代码,把validate移到另一个Service
6.2 final方法无法代理
java
@Service
public class OrderService {
public final void process() { ... } // ❌ CGLIB无法代理final方法
}
解决:去掉final修饰符。
6.3 私有方法无法拦截
java
@Service
public class UserService {
@Transactional
private void internalSave() { ... } // ❌ AOP无法拦截私有方法
}
原因:JDK Proxy和CGLIB都基于方法重写/接口实现,私有方法不可见。
七、总结
- Spring AOP基于动态代理------JDK Proxy(接口)或CGLIB(类)
- 默认优先JDK Proxy,除非配置proxyTargetClass=true或目标类无接口
- ProxyFactory是核心,负责组装TargetSource、Advisor、Pointcut
- @Aspect解析由AnnotationAwareAspectJAutoProxyCreator完成,在Bean初始化后创建代理
- AspectJ编译期/加载期织入功能更强,但复杂度更高
- 自调用、final方法、私有方法是AOP的三大盲区