Spring AOP底层是怎么织入的?——ProxyFactory、JDK Proxy vs CGLIB与编译期织入

发布时间:2026/7/12 19:11:39
Spring AOP底层是怎么织入的?——ProxyFactory、JDK Proxy vs CGLIB与编译期织入 很多人以为Spring AOP就是动态代理但当你发现Transactional在某些场景下失效或者想在不重启的情况下动态添加切面时才发现自己对AOP的理解只停留在表面。这篇从ProxyFactory开始讲清楚Spring是如何在运行时决定用JDK Proxy还是CGLIB的以及AspectJ编译期织入和Spring AOP的本质区别。一、Spring AOP的两种实现方式1.1 JDK动态代理// 只能代理接口publicinterfaceUserService{voidsave(Useruser);}ServicepublicclassUserServiceImplimplementsUserService{publicvoidsave(Useruser){...}}// Spring生成的代理类简化class$Proxy0implementsUserService{privateUserServicetarget;publicvoidsave(Useruser){// 前置通知before();try{target.save(user);// 调用目标方法}finally{// 后置通知after();}}}限制只能代理实现了接口的类。如果类没有实现任何接口JDK Proxy无能为力。1.2 CGLIB代理// 可以代理具体类ServicepublicclassOrderService{// 没有实现接口publicvoidcreate(Orderorder){...}}// Spring生成的代理类简化classOrderService$$EnhancerByCGLIBextendsOrderService{publicvoidcreate(Orderorder){MethodInterceptorinterceptor...;interceptor.intercept(this,method,args,proxy);}}特点通过继承实现所以不能代理final类或final方法。二、Spring如何选择代理方式核心逻辑在DefaultAopProxyFactorypublicAopProxycreateAopProxy(AdvisedSupportconfig)throwsAopConfigException{if(config.isOptimize()||config.isProxyTargetClass()||hasNoUserSuppliedProxyInterfaces(config)){Class?targetClassconfig.getTargetClass();if(targetClassnull){thrownewAopConfigException(TargetSource cannot determine target class);}// 如果是接口或已经是代理类 → JDK Proxyif(targetClass.isInterface()||Proxy.isProxyClass(targetClass)){returnnewJdkDynamicAopProxy(config);}// 否则 → CGLIBreturnnewObjenesisCglibAopProxy(config);}else{// 默认如果有接口就用JDK ProxyreturnnewJdkDynamicAopProxy(config);}}决策树1. 配置了 proxyTargetClasstrue → 是 → CGLIB 2. 目标类是接口 → 是 → JDK Proxy 3. 目标类没有实现任何用户定义的接口 → 是 → CGLIB 4. 默认 → JDK Proxy2.1 强制使用CGLIBspring:aop:proxy-target-class:true# 全局强制CGLIB或者在特定Bean上ConfigurationEnableAspectJAutoProxy(proxyTargetClasstrue)publicclassAppConfig{...}三、ProxyFactoryAOP的核心工厂// 手动创建AOP代理ProxyFactoryfactorynewProxyFactory(newUserServiceImpl());factory.addAdvice(newTransactionInterceptor());factory.addAdvisor(newDefaultPointcutAdvisor(pointcut,advice));UserServiceproxy(UserService)factory.getProxy();关键组件组件作用TargetSource提供目标对象可以是单例、原型、ThreadLocal等Advisor切点通知的组合Pointcut匹配哪些方法需要被拦截Advice拦截后要执行的逻辑四、Aspect解析流程4.1 自动代理创建器Spring Boot启动时AnnotationAwareAspectJAutoProxyCreator会被注册为BeanPostProcessorComponentpublicclassLoggingAspect{Around(annotation(org.springframework.transaction.annotation.Transactional))publicObjectlogTransaction(ProceedingJoinPointpjp)throwsThrowable{log.info(Starting transaction);try{returnpjp.proceed();}finally{log.info(Ending transaction);}}}解析步骤扫描Aspect注解的类解析Pointcut表达式转换为Pointcut对象解析Before/After/Around等通知转换为Advice对象创建Advisor将Pointcut和Advice组合注册到BeanFactory在Bean初始化时应用4.2 Bean初始化时的代理创建// AbstractAutoProxyCreator.postProcessAfterInitialization()publicObjectpostProcessAfterInitialization(Objectbean,StringbeanName){if(this.advisedBeans.containsKey(beanName)){returnbean;}// 检查是否需要代理if(isInfrastructureClass(bean.getClass())||shouldSkip(bean.getClass(),beanName)){this.advisedBeans.put(beanName,Boolean.FALSE);returnbean;}// 获取Advisor列表Object[]specificInterceptorsgetAdvicesAndAdvisorsForBean(...);if(specificInterceptors!DO_NOT_PROXY){// 创建代理ObjectproxycreateProxy(bean.getClass(),beanName,specificInterceptors,newSingletonTargetSource(bean));this.proxyTypes.put(cacheKey,proxy.getClass());returnproxy;}this.advisedBeans.put(beanName,Boolean.FALSE);returnbean;}五、AspectJ编译期织入 vs Spring AOP维度Spring AOPAspectJ编译期/加载期实现方式动态代理运行时字节码修改编译期/加载期性能略低每次调用走代理高直接织入字节码功能仅支持方法拦截支持字段访问、构造器调用等自调用不支持绕过代理支持直接织入复杂度低Spring内置高需要ajc编译器或Java Agent5.1 AspectJ加载期织入LTW!-- aop.xml --aspectjaspectsaspectnamecom.example.LoggingAspect//aspectsweaveroptions-verboseincludewithincom.example..*//weaver/aspectj# 启动时指定Java Agent-javaagent:path/to/aspectjweaver.jar适用场景需要对第三方库进行增强且无法修改源码时。六、常见陷阱6.1 自调用失效ServicepublicclassUserService{Transactionalpublicvoidsave(Useruser){this.validate(user);// ❌ 不走事务}Transactionalpublicvoidvalidate(Useruser){...}}原因this.validate()是直接调用绕过了代理对象。解决注入自己Autowired private UserService self; self.validate(user);使用AopContext.currentProxy()重构代码把validate移到另一个Service6.2 final方法无法代理ServicepublicclassOrderService{publicfinalvoidprocess(){...}// ❌ CGLIB无法代理final方法}解决去掉final修饰符。6.3 私有方法无法拦截ServicepublicclassUserService{TransactionalprivatevoidinternalSave(){...}// ❌ AOP无法拦截私有方法}原因JDK Proxy和CGLIB都基于方法重写/接口实现私有方法不可见。七、总结Spring AOP基于动态代理——JDK Proxy接口或CGLIB类默认优先JDK Proxy除非配置proxyTargetClasstrue或目标类无接口ProxyFactory是核心负责组装TargetSource、Advisor、PointcutAspect解析由AnnotationAwareAspectJAutoProxyCreator完成在Bean初始化后创建代理AspectJ编译期/加载期织入功能更强但复杂度更高自调用、final方法、私有方法是AOP的三大盲区