深入理解Spring AOP中的@EnableAspectJAutoProxy
本文分享自华为云社区《Spring高手之路20——深入理解@EnableAspectJAutoProxy的力量》,作者: 砖业洋__。
1. 初始调试代码
面向切面编程(AOP)是一种编程范式,用于增强软件模块化,通过将横切关注点(如事务管理、安全等)分离出业务逻辑。Spring AOP是Spring框架中实现AOP的一种方式,它通过代理机制在运行时向对象动态地添加增强。AspectJ是一种更强大的AOP实现,它通过编译时和加载时织入,提供了比Spring AOP更丰富的增强选项。本文将探索如何通过Spring AOP进行简单的AOP配置和实现。
后续源码分析就用这个前置通知的代码调试
package com.example.demo.aspect; import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component; @Aspect
@Component
public class MyAspect {
@Before("execution(* com.example.demo.service.MyService.performAction(..))")
public void beforeAdvice(JoinPoint joinPoint) {
System.out.println("Before method: " + joinPoint.getSignature().getName());
}
}
package com.example.demo.configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy; @Configuration
@EnableAspectJAutoProxy
public class AppConfig {
}
package com.example.demo.service; import org.springframework.stereotype.Service; // 一个简单的服务类
@Service
public class MyService {
public void performAction() {
System.out.println("Performing an action");
}
}
package com.example.demo; import com.example.demo.service.MyService;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.ComponentScan; //主应用类
@ComponentScan(basePackages = "com.example.demo")
public class DemoApplication { public static void main(String[] args) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(DemoApplication.class);
MyService myService = context.getBean(MyService.class);
myService.performAction(); // 调用方法,触发AOP增强
}
}
2. 源码跟踪分析
2.1 初探@EnableAspectJAutoProxy
上面代码中,AppConfig配置类里有个@EnableAspectJAutoProxy注解,前面说过,@EnableAspectJAutoProxy注解告诉Spring框架去寻找带有@Aspect注解的类,Spring AOP通过读取@EnableAspectJAutoProxy注解的属性来配置代理的行为。
下面用时序图来展示通过@EnableAspectJAutoProxy注解启用面向切面编程(AOP)的过程。
解读:
1、启动ApplicationContext:
应用 (App) 向 ApplicationContext 发送消息以启动Spring的应用上下文。这是Spring应用的初始化阶段,负责设置Spring的核心功能,包括Bean的加载和管理。
2、加载配置类:
ApplicationContext 接着加载 配置类 (ConfigClass)。这个配置类包含了应用的配置信息,如Bean定义和AOP支持的相关注解等。
3、检测@EnableAspectJAutoProxy:
配置类完成加载后,检查是否包含 @EnableAspectJAutoProxy 注解。此注解是启用Spring AOP代理的关键,它指示Spring框架自动为符合条件的Bean创建AOP代理。
4、注册AspectJAutoProxyCreator:
一旦检测到@EnableAspectJAutoProxy注解,ApplicationContext 会注册 AspectJAutoProxyCreator (APC)。这个组件是一个BeanPostProcessor,它在Spring容器的bean初始化阶段介入,自动检测容器中所有带有@Aspect注解的类,并为这些类创建代理。这个代理创建过程不仅包括实现通知逻辑的织入,还涉及对被代理对象的调用进行拦截,确保在执行目标方法前后能够执行相应的通知(advice)。
5、扫描和注册Beans:
ApplicationContext 继续扫描应用中的其他 Bean,并将它们注册到Spring容器中。这包括普通的Bean和那些可能成为AOP代理目标的Bean。
6、识别@Aspect注解:
在Bean的扫描过程中,识别出带有 @Aspect 注解的Bean(AspectBean)。这些Bean定义了AOP的切面,如通知方法(advice),指定在某些方法执行前后或抛出异常时执行。
7、请求创建代理:
当识别到@Aspect注解的Bean时,这些Bean会向 AspectJAutoProxyCreator 发出请求,要求创建相应的代理。
8、调用创建代理:
AspectJAutoProxyCreator 收到创建代理的请求后,调用代理工厂 (ProxyFactory) 来构建具体的代理实例。
9、构建代理Bean:
代理工厂 根据AspectJAutoProxyCreator的指示,为@Aspect注解的Bean创建代理。这些代理将封装原Bean,并在调用原Bean的方法时,按照@Aspect定义执行相应的前置、后置或异常通知。
10、注册代理Bean:
创建完成的代理Bean(ProxyBean)被注册回 ApplicationContext,替换或增加到原有的Bean配置中。
11、完成Bean加载和初始化:
所有Bean,包括新注册的代理Bean,都被加载和初始化后,ApplicationContext 向应用 (App) 发送消息,表示Bean加载和初始化工作已完成,应用可以开始执行。
来看看源码,这里可以看到@Import 导入了一个注册器AspectJAutoProxyRegistrar。
@EnableAspectJAutoProxy注解启用Spring的自动代理机制,该注解有两个重要的属性配置:proxyTargetClass和exposeProxy。proxyTargetClass属性默认为false,此时Spring使用JDK动态代理来代理接口。如果设置为true,则Spring将使用CGLIB来代理类,这在目标对象没有实现接口时特别有用。exposeProxy属性默认为false,如果设置为true,允许通过AopContext类访问当前的代理对象,这在需要在目标对象内部方法调用自身被代理的方法时非常有用。
2.2 registerBeanDefinitions方法和时序图分析
本节源码都基于5.3.16分析。
这段代码主要涉及2.1节时序图中的“加载配置类”和“注册AspectJAutoProxyCreator”这两个步骤。
在AspectJAutoProxyRegistrar类的registerBeanDefinitions方法打上断点调试。
这个方法主要负责根据@EnableAspectJAutoProxy注解的设置来配置Spring AOP的行为,包括是否使用CGLIB进行类代理而不是基于接口的JDK代理,以及是否允许在被代理的对象内部通过AopContext访问代理对象。这两个设置对于控制Spring AOP的行为至关重要,特别是在处理复杂的代理场景和高级AOP功能时。
代码提出来分析:
// 注册Bean定义的方法,通过读取注解元数据和操作Bean定义注册表进行配置
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
// 检查是否已经注册了AspectJ自动代理创建器,如果没有,则进行注册
AopConfigUtils.registerAspectJAnnotationAutoProxyCreatorIfNecessary(registry); // 从导入的类的注解元数据中获取@EnableAspectJAutoProxy注解的属性
AnnotationAttributes enableAspectJAutoProxy = AnnotationConfigUtils.attributesFor(importingClassMetadata, EnableAspectJAutoProxy.class); // 检查是否成功获取@EnableAspectJAutoProxy注解的属性
if (enableAspectJAutoProxy != null) {
// 检查@EnableAspectJAutoProxy注解的proxyTargetClass属性是否为true
if (enableAspectJAutoProxy.getBoolean("proxyTargetClass")) {
// 如果proxyTargetClass为true,则强制AOP代理创建器使用CGLIB来进行类代理
AopConfigUtils.forceAutoProxyCreatorToUseClassProxying(registry);
} // 检查@EnableAspectJAutoProxy注解的exposeProxy属性是否为true
if (enableAspectJAutoProxy.getBoolean("exposeProxy")) {
// 如果exposeProxy为true,则强制AOP代理创建器暴露代理对象,使其能在被代理的对象内部通过AopContext访问
AopConfigUtils.forceAutoProxyCreatorToExposeProxy(registry);
}
}
}
这个方法的两个入参说明一下:
importingClassMetadata是AnnotationMetadata类型的实例,它持有关于当前正在被处理的类的注解信息。这里用来检索有关@EnableAspectJAutoProxy注解的信息,这些信息决定了如何配置AOP代理的行为(是否使用CGLIB代理以及是否暴露代理对象)。registry是BeanDefinitionRegistry类型的实例,它是一个用于注册Bean定义的接口。通过这个注册表,可以在运行时向Spring应用上下文添加新的Bean定义或修改现有的Bean定义。这里用于实际调整AOP配置,如注册AOP代理创建器,以及设置代理创建器的行为(根据@EnableAspectJAutoProxy的属性值)。这些操作直接影响了Spring AOP如何在运行时创建和管理AOP代理。
如果流程太抽象,那么用时序图补充
这个时序图展示了Spring AOP配置的完整流程,从检查和注册自动代理创建器,到根据@EnableAspectJAutoProxy注解的设置调整Spring的代理行为。此过程确保了应用的AOP配置能够根据给定的注解属性正确地执行,无论是使用更高性能的CGLIB代理,还是暴露代理以供内部访问。
完整的时序图解释
1. 方法调用开始
调用者 (Caller)触发 registerBeanDefinitions 方法(RBD),这通常发生在应用的配置阶段。
2. 检查并注册自动代理创建器
registerBeanDefinitions 向 AopConfigUtils (AopCU)发起调用,检查是否已注册AspectJ自动代理创建器,或者是否需要注册新的或更新现有的代理创建器。
3. 自动代理创建器的注册和更新
AopConfigUtils向Registry(Reg)执行实际的注册或更新操作。Registry完成更新后反馈给AopConfigUtils。AopConfigUtils然后将结果返回给registerBeanDefinitions。
4. 获取@EnableAspectJAutoProxy注解的属性
registerBeanDefinitions 接着从 AnnotationConfigUtils (ACU)获取@EnableAspectJAutoProxy注解的相关属性,这些属性决定代理的行为。
5. 根据属性设置代理方式
- 如果注解的
proxyTargetClass属性为真,意味着需要使用CGLIB来进行类代理而不是基于接口的代理。 registerBeanDefinitions要求AopConfigUtils强制使用CGLIB代理。AopConfigUtils更新Registry中相关Bean定义的设置以使用CGLIB。Registry确认设置已更新,然后AopConfigUtils通知registerBeanDefinitions配置完成。
6. 设置是否暴露代理
- 如果注解的
exposeProxy属性为真,意味着需要暴露代理,允许通过AopContext访问当前代理。 registerBeanDefinitions要求AopConfigUtils强制暴露代理。AopConfigUtils在Registry中进行相应设置更新。Registry确认设置已更新,然后AopConfigUtils通知registerBeanDefinitions配置完成。
7. 配置流程完成
一旦所有设置完成,registerBeanDefinitions 向调用者报告配置流程已完成。
2.3 registerOrEscalateApcAsRequired方法和时序图分析
看到刚刚第一句注册后置处理器,我们来详细看看

这段代码主要与2.1节时序图中的“注册AspectJAutoProxyCreator”步骤相对应。AspectJAutoProxyCreator是由Spring内部管理的一个自动代理创建器,用于基于AspectJ的注解来创建AOP代理。它与用户定义的切面(使用@Aspect注解的类)相区分,后者指定了具体的通知(如@Before, @AfterReturning等)和切点表达式。在Spring的AOP实现中,代理创建器负责实际的代理对象创建工作,而用户定义的切面提供了应用于这些代理对象的通知逻辑。具体而言,它描述了如何在Spring的ApplicationContext中检查并可能更新或注册一个新的自动代理创建器(AspectJAutoProxyCreator)。
直接分析registerOrEscalateApcAsRequired方法
// 定义一个用于注册或升级自动代理创建器的静态方法
private static BeanDefinition registerOrEscalateApcAsRequired(Class<?> cls, BeanDefinitionRegistry registry, @Nullable Object source) {
// 断言,确保传入的registry不为空
Assert.notNull(registry, "BeanDefinitionRegistry must not be null"); // 检查容器是否已经包含名为"org.springframework.aop.config.internalAutoProxyCreator"的Bean定义
if (registry.containsBeanDefinition("org.springframework.aop.config.internalAutoProxyCreator")) {
// 获取已存在的自动代理创建器的Bean定义
BeanDefinition apcDefinition = registry.getBeanDefinition("org.springframework.aop.config.internalAutoProxyCreator"); // 检查当前注册的自动代理创建器类名是否与传入的cls类名不同
if (!cls.getName().equals(apcDefinition.getBeanClassName())) {
// 找到当前自动代理创建器的优先级
int currentPriority = findPriorityForClass(apcDefinition.getBeanClassName());
// 找到需要注册的自动代理创建器的优先级
int requiredPriority = findPriorityForClass(cls); // 比较两个优先级,若已注册的优先级低,则更新为新的自动代理创建器类
if (currentPriority < requiredPriority) {
apcDefinition.setBeanClassName(cls.getName());
}
} // 若已存在自动代理创建器且不需要升级,则返回null
return null;
} else {
// 若未注册自动代理创建器,则创建一个新的RootBeanDefinition实例
RootBeanDefinition beanDefinition = new RootBeanDefinition(cls); // 设置bean定义的来源
beanDefinition.setSource(source); // 设置bean定义的属性,这里设置"order"属性为最小整数值,表示最高优先级
beanDefinition.getPropertyValues().add("order", Integer.MIN_VALUE); // 设置bean定义的角色,通常ROLE_INFRASTRUCTURE表示框架内部使用的组件
beanDefinition.setRole(2); // 在注册表中注册名为"org.springframework.aop.config.internalAutoProxyCreator"的新自动代理创建器Bean定义
registry.registerBeanDefinition("org.springframework.aop.config.internalAutoProxyCreator", beanDefinition); // 返回新创建的Bean定义
return beanDefinition;
}
}
这个方法主要用于控制Spring AOP框架中的自动代理创建器(AutoProxyCreator)的注册与优先级升级,确保AOP功能按预期工作,特别是在有多个自动代理创建器可能存在时确保正确的配置和行为优先级。
自动代理创建器(AutoProxyCreator)是一个核心组件,根据配置(如注解、XML配置或程序的指定)识别需要增强的Bean,并自动为这些Bean创建代理。这些代理可以在方法调用前后添加额外的行为,而不修改原有代码的基础上,实现如安全检查、事务管理、日志记录等横切关注点。
如果流程太抽象,那么用时序图补充
这个时序图展示了 registerOrEscalateApcAsRequired 方法如何根据已存在的自动代理创建器Bean定义的情况来决定执行的操作。通过检查、比较和可能的更新或创建操作,它确保了最适合的类被用于自动代理创建器。如果当前注册的自动代理创建器足够适合,不会进行更改;如果不适合,会进行更新或创建新的Bean定义,以保证系统配置的最优化。
1. 开始调用
调用者发起对 registerOrEscalateApcAsRequired 方法的调用。该方法接收三个参数:类(cls),注册表(registry)和源信息(source)。
2. 检查Bean定义是否存在
registerOrEscalateApcAsRequired 向 BeanDefinitionRegistry 查询是否已存在名为 “internalAutoProxyCreator” 的Bean定义。
3. 处理已存在的Bean定义
- 如果
BeanDefinitionRegistry确认Bean定义已存在(返回true),registerOrEscalateApcAsRequired从BeanDefinitionRegistry请求获取该Bean定义。 BeanDefinitionRegistry将BeanDefinition返回给registerOrEscalateApcAsRequired。registerOrEscalateApcAsRequired使用返回的BeanDefinition检查并比较当前Bean的类与新传入的类cls的优先级。
4. 决定是否更新Bean定义
- 如果新类
cls的优先级更高,registerOrEscalateApcAsRequired会在BeanDefinition中更新类名为新类cls.getName()。 - 更新操作完成后,
BeanDefinition通知BeanDefinitionRegistry更新已完成。 - 如果当前已注册的类的优先级足够高或相同,不需要进行更新,
registerOrEscalateApcAsRequired直接返回null给调用者。
5. 处理不存在的Bean定义
- 如果
BeanDefinitionRegistry确认没有找到名为 “internalAutoProxyCreator” 的Bean定义(返回false),registerOrEscalateApcAsRequired将创建一个新的BeanDefinition。 - 新创建的
BeanDefinition被注册到BeanDefinitionRegistry。 - 注册完成后,
BeanDefinitionRegistry确认新的BeanDefinition已注册。 registerOrEscalateApcAsRequired最终将新创建的BeanDefinition返回给调用者。
深入理解Spring AOP中的@EnableAspectJAutoProxy的更多相关文章
- 正确理解Spring AOP中的Around advice
Spring AOP中,有Before advice和After advice,这两个advice从字面上就可以很容易理解,但是Around advice就有点麻烦了. 乍一看好像是Before ad ...
- spring aop中pointcut表达式完整版
spring aop中pointcut表达式完整版 本文主要介绍spring aop中9种切入点表达式的写法 execute within this target args @target @with ...
- 轻松理解 Spring AOP
目录 Spring AOP 简介 Spring AOP 的基本概念 面向切面编程 AOP 的目的 AOP 术语和流程 术语 流程 五大通知执行顺序 例子 图例 实际的代码 使用 Spring AOP ...
- Spring AOP中的动态代理
0 前言 1 动态代理 1.1 JDK动态代理 1.2 CGLIB动态代理 1.2.1 CGLIB的代理用法 1.2.2 CGLIB的过滤功能 2 Spring AOP中的动态代理机制 2.1 ...
- Spring AOP中的JDK和CGLib动态代理哪个效率更高?
一.背景 今天有小伙伴面试的时候被问到:Spring AOP中JDK 和 CGLib动态代理哪个效率更高? 二.基本概念 首先,我们知道Spring AOP的底层实现有两种方式:一种是JDK动态代理, ...
- 深入理解Spring AOP之二代理对象生成
深入理解Spring AOP之二代理对象生成 spring代理对象 上一篇博客中讲到了Spring的一些基本概念和初步讲了实现方法,当中提到了动态代理技术,包含JDK动态代理技术和Cglib动态代理 ...
- 转:Spring AOP中的动态代理
原文链接:Spring AOP中的动态代理 0 前言 1 动态代理 1.1 JDK动态代理 1.2 CGLIB动态代理 1.2.1 CGLIB的代理用法 1.2.2 CGLIB的过滤功能 2 S ...
- spring 理解Spring AOP 一个简单的约定游戏
应该说AOP原理是Spring技术中最难理解的一个部分,而这个约定游戏也许会给你很多的帮助,通过这个约定游戏,就可以理解Spring AOP的含义和实现方法,也能帮助读者更好地运用Spring AOP ...
- Spring AOP中定义切点(PointCut)和通知(Advice)
如果你还不熟悉AOP,请先看AOP基本原理,本文的例子也沿用了AOP基本原理中的例子.切点表达式 切点的功能是指出切面的通知应该从哪里织入应用的执行流.切面只能织入公共方法.在Spring AOP中, ...
- Spring AOP高级——源码实现(2)Spring AOP中通知器(Advisor)与切面(Aspect)
本文例子完整源码地址:https://github.com/yu-linfeng/BlogRepositories/tree/master/repositories/Spring%20AOP%E9%A ...
随机推荐
- 【GUI软件】小红书评论采集v4.0升级版:自动采集1w多条,含二级评论!
目录 一.爬取目标 1.1 效果截图 1.2 演示视频 1.3 软件说明 二.代码讲解 2.1 爬虫采集模块 2.2 软件界面模块 2.3 日志模块 三.获取源码及软件 一.爬取目标 您好!我是@马哥 ...
- 以对象的方式访问html中的标签,比正则表达式更好用的方式获取html中的内容,linq方式直接获取所有的链接,更加先进的c#版本爬虫开源库
这是我本人自己写的一个开源库,现已经发布到nuget,可以直接在vs的nuget包管理中搜索到,或者可以到nuget官网下载:https://www.nuget.org/packages/ZmjCon ...
- scp本地服务器和远程服务器拷贝文件
上传本地文件到服务器 scp 本地路径 用户名@远程服务器ip:远程路径 下载文件 scp 用户名@远程服务器ip:远程路径 本地路径 -r 是上传下载本地目录到远程 远程文件
- VueJS和Javascript实现文字上下滚动效果
一提到文字上下滚动,我们就会想到用不同的程序去实现,而且页面中有文字滚动会增加这个网页的互动和可信度. 1.Js最简单的方法是控制盒子的高度,使不断的重复添加 <html> <bod ...
- Android开发环境配置 JDK及SDK
已经搭建过无数次开发环境,今天把搭建环境记录下,下次不用去搜索别人博客,有些博客都是复制粘贴,有些关键信息都缺失了. 1.首先第一步:下载JDK,配置JDK环境变量.JDK可以在Oracle官网下载, ...
- CICD详解之gitlab,Jenkins
持续集成概念 持续集成Continuous Integration 持续交付Continuous Delivery 持续部署Continuous Deployment 什么是持续集成: 持续集成是指开 ...
- vue多页面应用
多页面应用本身和单页面应用没什么差别,无非是多了几个入口点. 入口点多的话,还可以写个函数扫描路径取添加入口点. 比较让人好奇的是路径的问题.我们要开发的时候要体现目录层级接口,所以入口文件是一层套一 ...
- gin+MySQL简单实现数据库查询
利用 gin 项目搭建一个简易的后端系统. 一个简易的 HTTP 响应接口 首先在 go 工作区的终端输入这条指令: go get -u github.com/gin-gonic/gin 将 gin ...
- centos6 chkconfig的原理 和添加开机自启动的办法
当我们使用 chkconfig --list的时候 都会又 123456 这样的级别. 当某个级别是 on 他就会开机启动,当他是off 的时候他就不会开机自启动. 那么这是什么原因呢?他的 原理是什 ...
- 【论文笔记】YOLO系列
[深度学习]总目录 YOLOv1:<You Only Look Once: Unified, Real-Time Object Detection>one-stage的开山之作,将目标检测 ...