1. 为什么我们需要深入理解Spring AOP
Spring框架中的AOP(面向切面编程)是Java开发者日常工作中最常接触的核心技术之一。记得我第一次在项目中尝试使用@Transactional注解时,发现事务并没有按照预期回滚。经过通宵调试才发现,原来是因为对Spring AOP的代理机制理解不透彻,导致方法调用绕过了代理对象。这个惨痛教训让我意识到,只有深入源码层面理解AOP实现原理,才能真正掌握这项技术的精髓。
Spring AOP绝不仅仅是在方法上加个注解那么简单。它涉及到JDK动态代理与CGLIB的选择策略、代理对象的创建时机、切面执行的顺序控制等复杂机制。当我们在生产环境遇到性能问题或功能异常时,如果只停留在表面使用层面,往往会陷入无解的困境。本文将带你深入Spring AOP源码,揭示那些官方文档中没有明确说明的实现细节。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. Spring AOP核心架构解析
2.1 代理模式的选择策略
Spring AOP底层主要依赖两种代理实现方式:JDK动态代理和CGLIB。在DefaultAopProxyFactory类的createAopProxy方法中,我们可以清晰地看到选择逻辑:
java复制public AopProxy createAopProxy(AdvisedSupport config) {
if (config.isOptimize() || config.isProxyTargetClass() || hasNoUserSuppliedProxyInterfaces(config)) {
Class<?> targetClass = config.getTargetClass();
if (targetClass == null) {
throw new AopConfigException("TargetSource cannot determine target class");
}
if (targetClass.isInterface() || Proxy.isProxyClass(targetClass)) {
return new JdkDynamicAopProxy(config);
}
return new ObjenesisCglibAopProxy(config);
}
return new JdkDynamicAopProxy(config);
}
这段代码揭示了几个关键判断条件:
- 当目标对象实现了接口时,默认使用JDK动态代理
- 当配置了proxyTargetClass=true或目标类没有实现接口时,会强制使用CGLIB
- 对已经是代理类的对象会继续使用JDK动态代理
实际开发中常见的坑:在同一个类中调用被@Transactional注解标记的方法时事务失效,就是因为方法调用绕过了代理对象。理解这个机制后,我们可以通过将方法拆分到不同类中,或者通过AopContext.currentProxy()获取当前代理对象来解决问题。
2.2 拦截器链的构建过程
Spring AOP的核心执行逻辑体现在拦截器链的构建和执行上。在JdkDynamicAopProxy的invoke方法中,我们可以看到完整的处理流程:
- 获取目标方法的拦截器链(List
) - 如果拦截器链为空,直接反射调用目标方法
- 否则创建MethodInvocation并执行proceed()方法
拦截器链的构建发生在DefaultAdvisorChainFactory的getInterceptorsAndDynamicInterceptionAdvice方法中。这里会将所有Advisor适配为MethodInterceptor,并根据@Order注解或Ordered接口实现进行排序。
java复制public List<Object> getInterceptorsAndDynamicInterceptionAdvice(
Advised config, Method method, Class<?> targetClass) {
AdvisorAdapterRegistry registry = GlobalAdvisorAdapterRegistry.getInstance();
Advisor[] advisors = config.getAdvisors();
List<Object> interceptorList = new ArrayList<>(advisors.length);
for (Advisor advisor : advisors) {
if (advisor instanceof PointcutAdvisor) {
PointcutAdvisor pointcutAdvisor = (PointcutAdvisor) advisor;
if (pointcutAdvisor.getPointcut().getClassFilter().matches(targetClass)) {
MethodMatcher mm = pointcutAdvisor.getPointcut().getMethodMatcher();
if (mm.matches(method, targetClass)) {
MethodInterceptor[] interceptors = registry.getInterceptors(advisor);
if (mm.isRuntime()) {
interceptorList.add(new InterceptorAndDynamicMethodMatcher(interceptors[0], mm));
} else {
interceptorList.addAll(Arrays.asList(interceptors));
}
}
}
}
}
return interceptorList;
}
3. AOP执行流程深度剖析
3.1 代理对象的创建时机
Spring AOP代理对象的创建发生在Bean生命周期的多个阶段。关键点在于AbstractAutoProxyCreator这个后置处理器:
- 在Bean初始化后(postProcessAfterInitialization)会检查是否需要创建代理
- 通过shouldSkip方法判断当前Bean是否需要被代理
- 使用createProxy方法创建最终的代理对象
java复制public Object postProcessAfterInitialization(@Nullable Object bean, String beanName) {
if (bean != null) {
Object cacheKey = getCacheKey(bean.getClass(), beanName);
if (this.earlyProxyReferences.remove(cacheKey) != bean) {
return wrapIfNecessary(bean, beanName, cacheKey);
}
}
return bean;
}
protected Object wrapIfNecessary(Object bean, String beanName, Object cacheKey) {
if (StringUtils.hasLength(beanName) && this.targetSourcedBeans.contains(beanName)) {
return bean;
}
if (Boolean.FALSE.equals(this.advisedBeans.get(cacheKey))) {
return bean;
}
if (isInfrastructureClass(bean.getClass()) || shouldSkip(bean.getClass(), beanName)) {
this.advisedBeans.put(cacheKey, Boolean.FALSE);
return bean;
}
Object[] specificInterceptors = getAdvicesAndAdvisorsForBean(bean.getClass(), beanName, null);
if (specificInterceptors != DO_NOT_PROXY) {
this.advisedBeans.put(cacheKey, Boolean.TRUE);
Object proxy = createProxy(
bean.getClass(), beanName, specificInterceptors, new SingletonTargetSource(bean));
this.proxyTypes.put(cacheKey, proxy.getClass());
return proxy;
}
this.advisedBeans.put(cacheKey, Boolean.FALSE);
return bean;
}
3.2 切面执行顺序的奥秘
Spring AOP中切面的执行顺序由两个因素决定:
- 通过@Order注解或Ordered接口指定的优先级
- 切面类型(@Around, @Before, @After等)
执行顺序遵循以下规则:
- 优先级高的切面在外层(先执行)
- 同优先级下,@Around最先执行,@After最后执行
- 对于@Before和@AfterReturning/@AfterThrowing,@Before先执行
在AspectJPrecedenceComparator类中可以看到具体的比较逻辑:
java复制public int compare(Advisor o1, Advisor o2) {
int advisorPrecedence1 = getAspectPrecedence(o1);
int advisorPrecedence2 = getAspectPrecedence(o2);
if (advisorPrecedence1 == advisorPrecedence2) {
int adviceOrder1 = getAdviceOrder(o1);
int adviceOrder2 = getAdviceOrder(o2);
if (adviceOrder1 < adviceOrder2) {
return -1;
}
else if (adviceOrder1 == adviceOrder2) {
String aspectName1 = getAspectName(o1);
String aspectName2 = getAspectName(o2);
int compareTo = aspectName1.compareTo(aspectName2);
if (compareTo == 0) {
Method m1 = ((AbstractAspectJAdvice) getAdvice(o1)).getAspectJAdviceMethod();
Method m2 = ((AbstractAspectJAdvice) getAdvice(o2)).getAspectJAdviceMethod();
compareTo = m1.toString().compareTo(m2.toString());
}
return compareTo;
}
else {
return 1;
}
}
else {
return advisorPrecedence1 < advisorPrecedence2 ? -1 : 1;
}
}
4. 性能优化与常见问题排查
4.1 AOP性能优化实践
Spring AOP虽然方便,但不合理使用会导致性能问题。以下是几个优化建议:
- 减少切点表达式复杂度:复杂的切点表达式会在每次方法调用时执行匹配,应该尽量简化
java复制// 不推荐 - 复杂表达式
@Before("execution(* com.example.service.*.*(..)) && args(id) && @annotation(org.springframework.transaction.annotation.Transactional)")
// 推荐 - 简化表达式
@Before("execution(* com.example.service.UserService.get*(Long)) && args(id)")
-
避免在切面中执行耗时操作:切面逻辑应该尽量轻量,特别是@Around切面
-
合理使用proxyTargetClass:CGLIB代理的创建成本比JDK动态代理高,但调用效率更好
-
缓存代理对象:通过配置scoped-proxy可以避免重复创建代理对象
4.2 典型问题排查指南
问题1:切面未生效
- 检查Bean是否被Spring管理
- 确认切面类有@Aspect和@Component注解
- 检查切点表达式是否匹配目标方法
问题2:循环依赖导致代理失败
java复制// 错误示例 - 循环依赖
@Service
public class ServiceA {
@Autowired private ServiceB serviceB;
@Transactional
public void methodA() {}
}
@Service
public class ServiceB {
@Autowired private ServiceA serviceA;
}
解决方案:
- 使用setter注入代替字段注入
- 在@Autowired字段上添加@Lazy注解
- 重构代码消除循环依赖
问题3:代理对象方法调用内部方法导致切面失效
java复制@Service
public class OrderService {
public void placeOrder() {
validate(); // 这里的@Transactional不会生效
}
@Transactional
public void validate() {}
}
解决方案:
- 将方法拆分到不同类中
- 通过AopContext获取当前代理对象:
java复制((OrderService) AopContext.currentProxy()).validate();
5. 高级特性与扩展点
5.1 自定义Advice类型
除了Spring提供的@Before、@After等标准Advice类型,我们还可以通过实现MethodInterceptor接口创建自定义Advice:
java复制public class ExecutionTimeInterceptor implements MethodInterceptor {
@Override
public Object invoke(MethodInvocation invocation) throws Throwable {
long start = System.nanoTime();
try {
return invocation.proceed();
} finally {
long end = System.nanoTime();
System.out.println("Method " + invocation.getMethod().getName() +
" executed in " + (end - start) + " ns");
}
}
}
// 注册方式
@Bean
public Advisor executionTimeAdvisor() {
AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut();
pointcut.setExpression("execution(* com.example.service.*.*(..))");
return new DefaultPointcutAdvisor(pointcut, new ExecutionTimeInterceptor());
}
5.2 使用LoadTimeWeaving实现类加载期织入
Spring还支持在类加载期进行切面织入(LTW),这种方式不需要创建代理对象,性能更好但配置更复杂:
- 添加JVM参数:
code复制-javaagent:/path/to/spring-instrument.jar
- 配置类上添加注解:
java复制@EnableLoadTimeWeaving
public class AppConfig {}
- 在META-INF/aop.xml中定义切面:
xml复制<aspectj>
<aspects>
<aspect name="com.example.aspect.ProfilingAspect"/>
</aspects>
<weaver options="-verbose -showWeaveInfo">
<include within="com.example.service..*"/>
</weaver>
</aspectj>
5.3 整合AspectJ注解风格
虽然Spring AOP功能强大,但相比完整的AspectJ还是有限制。我们可以通过以下方式结合两者优势:
java复制@Aspect
public class CombinedAspect {
// Spring AOP风格的切面
@Before("execution(* com.example.service.*.*(..))")
public void logBefore(JoinPoint jp) {
System.out.println("Before " + jp.getSignature());
}
// AspectJ风格的切面(需要编译期织入)
@Around("call(* java.io.PrintStream.println(String)) && args(message)")
public Object interceptSystemOut(ProceedingJoinPoint pjp, String message) throws Throwable {
return pjp.proceed(new Object[]{"Intercepted: " + message});
}
}
理解Spring AOP源码的实现原理,不仅能帮助我们更好地使用这项技术,还能在遇到复杂问题时快速定位原因。从代理机制到拦截器链,从执行顺序到性能优化,每个环节都蕴含着Spring框架设计者的智慧。建议读者在掌握这些原理后,尝试自己实现一个简化版的AOP框架,这将是检验理解深度的最好方式。
