news 2026/9/20 1:37:23

从源码看 Spring AOP 如何生效:`<aop:aspectj-autoproxy/>` 标签解析与自动代理创建器注册机制

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
从源码看 Spring AOP 如何生效:`<aop:aspectj-autoproxy/>` 标签解析与自动代理创建器注册机制
  • 文档
  • 教程
  • 知识库

【免费下载链接】source-code-hunter

😱 从源码层面,剖析挖掘互联网行业主流技术的底层实现原理,为广大开发者 “提升技术深度” 提供便利。目前开放 Spring 全家桶,Mybatis、Netty、Dubbo 框架,及 Redis、Tomcat 中间件等

项目地址:https://gitcode.com/doocs/source-code-hunter
点击查看免费下载

在 Spring 框架中,只需要在 XML 配置文件里写上一行<aop:aspectj-autoproxy/>,基于注解的 Spring AOP 能力就会被激活。这背后隐藏着一条清晰的源码链路:XML 命名空间标签 → 标签解析器(BeanDefinitionParser)→ 自动代理创建器(AutoProxyCreator)的注册与属性装配。本文以 source-code-hunter 仓库中 Spring-Aop如何生效.md 为骨架,完整还原这条链路中的每一个关键类与方法,并结合仓库中 AOP源码实现及分析.md、JDK动态代理的实现原理解析.md 等姊妹文档,讲清楚“一行配置如何驱动整个 AOP 代理机制”。读完本文,你将掌握:如何从任意 XML 标签反查 Spring 源码解析入口、AspectJAutoProxyBeanDefinitionParser的完整解析流程、proxy-target-classexpose-proxy两个属性的底层去向,以及自动代理创建器注册背后的“注册或升级”策略。

一、起点:一行 XML 配置开启 Spring AOP

在使用 Spring AOP 时,XML 配置文件中会出现下面这段代码,用于开启 Spring 对 AspectJ 注解(@Aspect@Before@After等)的支持:

<aop:aspectj-autoproxy/>

这行配置位于<aop>命名空间之下,等价于在 Java 配置类上使用@EnableAspectJAutoProxy注解。二者的终点殊途同归——最终都会注册一个名为AnnotationAwareAspectJAutoProxyCreator的自动代理创建器 Bean,这一点在仓库的 Spring-Import.md 中也能看到注解路径的佐证:AspectJAutoProxyRegistrar实现了ImportBeanDefinitionRegistrar,通过AopConfigUtils完成自动代理创建器的注册。

但本文的主线是 XML 路径。问题来了:Spring 容器是如何解析<aop:aspectj-autoproxy/>这个自定义标签的?解析入口在哪里?

二、如何从 XML 标签反查源码解析入口

Spring 的自定义 XML 标签解析遵循一套固定机制:每个命名空间(namespace)对应一个NamespaceHandler,命名空间内的每个标签对应一个BeanDefinitionParser<aop:aspectj-autoproxy/>所属的<aop>命名空间,其处理器是org.springframework.aop.config.AopNamespaceHandler

在源码阅读过程中,定位某个标签解析方法的最高效手段就是直接搜索标签名。如上图所示,在 IDE 中对aspectj-autoproxy做全局搜索(搜索范围为*.java),可以立刻命中关键代码:

// AopNamespaceHandler 的初始化方法中 registerBeanDefinitionParser("aspectj-autoproxy", new AspectJAutoProxyBeanDefinitionParser());

也就是说,aspectj-autoproxy标签被注册给了AspectJAutoProxyBeanDefinitionParser这个解析器。搜索命中结果同时还出现在EnableAspectJAutoProxy.java、测试资源aspectj-autoproxy-config.xml等位置,印证了该标签在 XML 与注解两条路径中都处于核心位置。

三、AspectJAutoProxyBeanDefinitionParser:标签解析器本体

org.springframework.aop.config.AspectJAutoProxyBeanDefinitionParser实现了org.springframework.beans.factory.xml.BeanDefinitionParser接口(见上文类图),因此它的解析入口是parse方法:

@Override @Nullable public BeanDefinition parse(Element element, ParserContext parserContext) { // 注册 <aop:aspectj-autoproxy/> AopNamespaceUtils.registerAspectJAnnotationAutoProxyCreatorIfNecessary(parserContext, element); // 子类解析 extendBeanDefinition(element, parserContext); return null; }

parse方法做了两件事:

  1. 调用AopNamespaceUtils.registerAspectJAnnotationAutoProxyCreatorIfNecessary(...),这是核心动作——把AnnotationAwareAspectJAutoProxyCreator注册进容器;
  2. 调用extendBeanDefinition(...)交给子类做扩展解析(默认实现为空操作,供子类覆盖)。

注意parse返回null:该标签并不直接产出一个 BeanDefinition 作为解析结果,它的作用是“副作用式”地向容器注册自动代理创建器,这也解释了为什么标签解析器与普通 bean 的解析在返回语义上存在差异。

四、AopNamespaceUtils:注册自动代理创建器

parse方法调用的核心逻辑位于org.springframework.aop.config.AopNamespaceUtils

/** * 注册 <aop:aspectj-autoproxy/> * @param parserContext * @param sourceElement */ public static void registerAspectJAnnotationAutoProxyCreatorIfNecessary( ParserContext parserContext, Element sourceElement) { // 注册或者升级bean BeanDefinition beanDefinition = AopConfigUtils.registerAspectJAnnotationAutoProxyCreatorIfNecessary( parserContext.getRegistry(), parserContext.extractSource(sourceElement)); // proxy-target-class 和 expose-proxy 标签处理 useClassProxyingIfNecessary(parserContext.getRegistry(), sourceElement); // 注册组件并且交给监听器 registerComponentIfNecessary(beanDefinition, parserContext); }

该方法包含三个步骤:

  1. 注册或升级 Bean:委托给AopConfigUtils.registerAspectJAnnotationAutoProxyCreatorIfNecessary(...),真正的注册逻辑在这里;
  2. 标签属性处理:解析<aop:aspectj-autoproxy>元素上的proxy-target-classexpose-proxy两个属性(详见本文第六节);
  3. 组件登记registerComponentIfNecessary(...)将新注册的 BeanDefinition 登记为组件,并交给监听器(即ReaderEventListener)统一记录,这与 XML 解析过程中“组件注册 + 事件发布”的惯例保持一致。

五、AopConfigUtils:注册或升级AnnotationAwareAspectJAutoProxyCreator

真正的注册逻辑在org.springframework.aop.config.AopConfigUtils中:

@Nullable public static BeanDefinition registerAspectJAnnotationAutoProxyCreatorIfNecessary( BeanDefinitionRegistry registry, @Nullable Object source) { // 注册或者升级 AspectJ return registerOrEscalateApcAsRequired(AnnotationAwareAspectJAutoProxyCreator.class, registry, source); }

它把AnnotationAwareAspectJAutoProxyCreator.class作为目标类,交给registerOrEscalateApcAsRequired处理。这个私有方法是整条链路中最关键的一段逻辑:

/** * 注册或者升级 bean * @param cls 类 * @param registry 注册器 * @param source 源类 * @return */ @Nullable private static BeanDefinition registerOrEscalateApcAsRequired( Class<?> cls, BeanDefinitionRegistry registry, @Nullable Object source) { Assert.notNull(registry, "BeanDefinitionRegistry must not be null"); // 判断注册器是否包含org.springframework.aop.config.internalAutoProxyCreator if (registry.containsBeanDefinition(AUTO_PROXY_CREATOR_BEAN_NAME)) { // 获取注册器 BeanDefinition apcDefinition = registry.getBeanDefinition(AUTO_PROXY_CREATOR_BEAN_NAME); // 创建新的bean对象 if (!cls.getName().equals(apcDefinition.getBeanClassName())) { int currentPriority = findPriorityForClass(apcDefinition.getBeanClassName()); int requiredPriority = findPriorityForClass(cls); if (currentPriority < requiredPriority) { apcDefinition.setBeanClassName(cls.getName()); } } // 即将创建的Bean对象和当前的注册器相同返回null return null; } RootBeanDefinition beanDefinition = new RootBeanDefinition(cls); beanDefinition.setSource(source); // 设置加载顺序 beanDefinition.getPropertyValues().add("order", Ordered.HIGHEST_PRECEDENCE); beanDefinition.setRole(BeanDefinition.ROLE_INFRASTRUCTURE); // 注册bean定义 registry.registerBeanDefinition(AUTO_PROXY_CREATOR_BEAN_NAME, beanDefinition); return beanDefinition; }

这段代码包含了几个值得深挖的细节:

1. 固定 Bean 名称。自动代理创建器注册时使用的 Bean 名称是常量AUTO_PROXY_CREATOR_BEAN_NAME,即org.springframework.aop.config.internalAutoProxyCreator。Spring 通过这个固定名称实现“容器内只允许存在一个自动代理创建器”的约束——无论是 XML 路径还是@EnableAspectJAutoProxy注解路径,注册的都是同一个名称下的 Bean。

2. 注册或升级(escalate)策略。如果容器中已经存在名为internalAutoProxyCreator的 BeanDefinition,且其类与本次要注册的类不同,则通过findPriorityForClass比较两者的优先级:只有当新类优先级更高时,才会用apcDefinition.setBeanClassName(cls.getName())把原有 BeanDefinition 的类替换为新类。这就是“注册或升级”的含义——多个自动代理创建器竞争同一个名称时,优先级高的胜出;如果类相同或新类优先级不足,则直接返回null放弃注册。从源码结构可以推断,findPriorityForClass对不同自动代理创建器实现类(如InfrastructureAdvisorAutoProxyCreatorAspectJAwareAdvisorAutoProxyCreatorAnnotationAwareAspectJAutoProxyCreator)维护了一套优先级映射。

3. 基础设施角色的两个关键属性。

  • order = Ordered.HIGHEST_PRECEDENCE:把该 Bean 的order属性设为最高优先级,保证它在所有Ordered组件中被最先处理;
  • role = BeanDefinition.ROLE_INFRASTRUCTURE:标记这是一个基础设施 Bean(infrastructure bean),不属于用户业务 Bean 的范畴,在诸如自动扫描排除、@Configuration类后置处理等场景中会被特殊对待,用户也很难直接感知到它的存在。

4. 注册源信息。beanDefinition.setSource(source)会把 XML 元素的源信息(parserContext.extractSource(sourceElement))挂到 BeanDefinition 上,便于后续诊断报错时回溯配置来源。

至此,AnnotationAwareAspectJAutoProxyCreator已经作为一个基础设施 Bean 进入了 IoC 容器——这正是<aop:aspectj-autoproxy/>一行配置“生效”的实质。

六、proxy-target-classexpose-proxy属性处理

<aop:aspectj-autoproxy>标签还支持两个属性:proxy-target-classexpose-proxy。它们在AopNamespaceUtils.useClassProxyingIfNecessary中被解析:

/** * proxy-target-class 和 expose-proxy 标签处理 */ private static void useClassProxyingIfNecessary(BeanDefinitionRegistry registry, @Nullable Element sourceElement) { if (sourceElement != null) { // 处理 proxy-target-class boolean proxyTargetClass = Boolean.parseBoolean(sourceElement.getAttribute(PROXY_TARGET_CLASS_ATTRIBUTE)); if (proxyTargetClass) { AopConfigUtils.forceAutoProxyCreatorToUseClassProxying(registry); } // 处理 expose-proxy boolean exposeProxy = Boolean.parseBoolean(sourceElement.getAttribute(EXPOSE_PROXY_ATTRIBUTE)); if (exposeProxy) { AopConfigUtils.forceAutoProxyCreatorToExposeProxy(registry); } } }

处理逻辑很直白:从 XML 元素上读取两个属性(均为布尔值,默认false),只有显式声明为true时才触发对应的强制设置。

proxy-target-class的底层去向AopConfigUtils.forceAutoProxyCreatorToUseClassProxying实现:

public static void forceAutoProxyCreatorToUseClassProxying(BeanDefinitionRegistry registry) { if (registry.containsBeanDefinition(AUTO_PROXY_CREATOR_BEAN_NAME)) { BeanDefinition definition = registry.getBeanDefinition(AUTO_PROXY_CREATOR_BEAN_NAME); definition.getPropertyValues().add("proxyTargetClass", Boolean.TRUE); } }

它把proxyTargetClass=true作为属性写入自动代理创建器的 BeanDefinition。这个属性的消费点位于代理对象生成阶段:仓库的 AOP源码实现及分析.md 中DefaultAopProxyFactory.createAopProxy(...)展示了选择逻辑——当config.isProxyTargetClass()为真、或目标类没有用户提供的代理接口时,若目标类是接口则使用JdkDynamicAopProxy(JDK 动态代理),否则使用CglibAopProxy(CGLIB 生成子类代理)。换句话说,proxy-target-class="true"的作用是强制走 CGLIB 类代理路线,即使目标对象实现了接口也不例外。

expose-proxy的底层去向forceAutoProxyCreatorToExposeProxy实现,其操作与forceAutoProxyCreatorToUseClassProxying完全同构:同样是将读取到的布尔值放入 bean 对象作为一个属性存储,区别仅在于属性名是exposeProxy。该属性在运行时会被AopContext.setCurrentProxy(proxy)/AopContext.getCurrentProxy()消费(见 AOP源码实现及分析.md 中JdkDynamicAopProxy.invoke()CglibAopProxy.intercept()exposeProxy的判断)。它的典型使用场景是:当目标方法内部调用自身方法(this.xxx())时,通过AopContext.currentProxy()拿到代理对象,从而让内部调用也能被切面拦截。

完整的配置示例:

<!-- 开启 AspectJ 注解式 AOP,并强制使用 CGLIB 类代理、暴露代理对象到 AopContext --> <aop:aspectj-autoproxy proxy-target-class="true" expose-proxy="true"/>

七、注册之后的生效链路:从自动代理创建器到代理对象

AnnotationAwareAspectJAutoProxyCreator注册进容器后,AOP 的“生效”才刚刚开始。从 Spring AOP 的整体设计看,该创建器是一个BeanPostProcessor类型的自动代理创建器:容器在实例化每个 Bean 后,会经过它的后处理,凡是命中切点(Pointcut)的 Bean,都会被替换为代理对象。

而代理对象本身的生成与拦截调用,正是仓库 AOP源码实现及分析.md 所讲解的内容,与本文形成完整的阅读闭环:

  • 接口的设计Advice(通知,定义增强行为)、Pointcut(切点,定义被增强方法的集合)、Advisor(通知器,将 Pointcut 与 Advice 结合,如DefaultPointcutAdvisor);
  • 代理对象的生成:通过ProxyFactoryBean(或自动代理创建器内部持有的ProxyFactory)构造AopProxy。核心决策在DefaultAopProxyFactory.createAopProxy(...):目标类是接口则走JdkDynamicAopProxy(JDK 动态代理),否则走CglibAopProxy(CGLIB);
  • 拦截器链的调用:JDK 代理通过InvocationHandler.invoke()回调、CGLIB 通过MethodInterceptor.intercept()回调进入ReflectiveMethodInvocation.proceed(),沿interceptorsAndDynamicMethodMatchers链逐个执行拦截器,最后由AopUtils.invokeJoinpointUsingReflection(...)反射调用目标方法;
  • Advice 的适配DefaultAdvisorAdapterRegistry通过MethodBeforeAdviceAdapterAfterReturningAdviceAdapterThrowsAdviceAdapter等适配器,把不同类型的Advice适配为对应的MethodInterceptor(如MethodBeforeAdviceInterceptor先回调before()proceed())。

关于 JDK 动态代理本身的字节码级原理——生成的$Proxy0类如何继承Proxy、实现业务接口、并在每个方法中通过super.h.invoke(...)回调InvocationHandler——可进一步阅读仓库的 JDK动态代理的实现原理解析.md,其中给出了用ProxyGenerator.generateProxyClass导出并反编译代理类的完整实验代码。

八、总结

回到最初的问题:<aop:aspectj-autoproxy/>到底是如何让 Spring AOP 生效的?完整的调用链可以归纳为:

  1. <aop:aspectj-autoproxy/>AopNamespaceHandler分发到AspectJAutoProxyBeanDefinitionParser
  2. parse()调用AopNamespaceUtils.registerAspectJAnnotationAutoProxyCreatorIfNecessary(...)
  3. 该工具方法调用AopConfigUtils.registerAspectJAnnotationAutoProxyCreatorIfNecessary(...),最终经registerOrEscalateApcAsRequired(...)以固定名称org.springframework.aop.config.internalAutoProxyCreator注册AnnotationAwareAspectJAutoProxyCreator,并设置order=HIGHEST_PRECEDENCErole=ROLE_INFRASTRUCTURE
  4. useClassProxyingIfNecessary(...)proxy-target-classexpose-proxy属性写入该 BeanDefinition;
  5. 该基础设施 Bean 在容器运行期作为自动代理创建器介入 Bean 实例化过程,通过 JDK 动态代理或 CGLIB 为目标 Bean 生成代理并织入拦截器链。

从方法论层面看,本文也印证了一条可复用的 Spring 源码阅读规律:实现org.springframework.beans.factory.xml.BeanDefinitionParser接口的类,多用于对 XML 标签的解析,并且入口为parse方法;如果解析结果是一个 bean 对象,通常会和 Spring 监听器(如ReaderEventListener)一起出现。掌握了“从标签名反查NamespaceHandler注册代码 → 跟踪BeanDefinitionParser→ 追踪工具类注册逻辑”这条路径,面对任何 Spring 自定义 XML 标签都能快速定位其解析实现。

延伸阅读

本文内容整理自仓库中的源码阅读笔记,建议按以下顺序联动阅读:

  • Spring-Aop如何生效.md:本文的原始笔记,聚焦标签解析链路;
  • AOP源码实现及分析.md:AOP 代理生成与拦截器链调用的完整源码分析;
  • JDK动态代理的实现原理解析.md:JDK 动态代理字节码层面的实现剖析;
  • Spring-Import.md:注解路径@EnableAspectJAutoProxy背后的ImportBeanDefinitionRegistrar机制;
  • BeanFactoryPostProcessor.md:理解容器刷新阶段 Bean 定义的后置处理时机。
  • 文档
  • 教程
  • 知识库

【免费下载链接】source-code-hunter

😱 从源码层面,剖析挖掘互联网行业主流技术的底层实现原理,为广大开发者 “提升技术深度” 提供便利。目前开放 Spring 全家桶,Mybatis、Netty、Dubbo 框架,及 Redis、Tomcat 中间件等

项目地址:https://gitcode.com/doocs/source-code-hunter
点击查看免费下载
上一篇:在 Vercel 生产环境实测 Next.js 性能:bench/vercel 生产基准测试工具全解
下一篇:Windows 11精简终极指南:使用Tiny11Builder打造轻量级系统

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/20 1:36:48

MATLAB直接序列扩频DSSS仿真:处理增益与干扰容限分析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/20 1:34:28

使用 Fleet 按漏洞过滤软件:按严重级别与已知利用优先修复补丁

使用 Fleet 按漏洞过滤软件&#xff1a;按严重级别与已知利用优先修复补丁 【免费下载链接】fleet Open device management 项目地址: https://gitcode.com/GitHub_Trending/fl/fleet Fleet 从 4.56 版本开始提供按漏洞过滤软件的能力&#xff0c;允许管理员在软件清单中…

作者头像 李华
网站建设 2026/9/20 1:33:40

计算机测配色从建库到配方:K/S值、色差公式与盲打命中率

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华