本文分享自华为云社区《Spring高手之路20——深入理解@EnableAspectJAutoProxy的力量》,作者: 砖业洋__。

1. 初始调试代码

面向切面编程(AOP)是一种编程范式,用于增强软件模块化,通过将横切关注点(如事务管理、安全等)分离出业务逻辑。Spring AOPSpring框架中实现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 注解的BeanAspectBean)。这些Bean定义了AOP的切面,如通知方法(advice),指定在某些方法执行前后或抛出异常时执行。

7、请求创建代理:

当识别到@Aspect注解的Bean时,这些Bean会向 AspectJAutoProxyCreator 发出请求,要求创建相应的代理。

8、调用创建代理:

AspectJAutoProxyCreator 收到创建代理的请求后,调用代理工厂 (ProxyFactory) 来构建具体的代理实例。

9、构建代理Bean:

代理工厂 根据AspectJAutoProxyCreator的指示,为@Aspect注解的Bean创建代理。这些代理将封装原Bean,并在调用原Bean的方法时,按照@Aspect定义执行相应的前置、后置或异常通知。

10、注册代理Bean:

创建完成的代理BeanProxyBean)被注册回 ApplicationContext,替换或增加到原有的Bean配置中。

11、完成Bean加载和初始化:

所有Bean,包括新注册的代理Bean,都被加载和初始化后,ApplicationContext 向应用 (App) 发送消息,表示Bean加载和初始化工作已完成,应用可以开始执行。

来看看源码,这里可以看到@Import 导入了一个注册器AspectJAutoProxyRegistrar

@EnableAspectJAutoProxy注解启用Spring的自动代理机制,该注解有两个重要的属性配置:proxyTargetClassexposeProxyproxyTargetClass属性默认为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);
}
}
}

这个方法的两个入参说明一下:

  1. importingClassMetadataAnnotationMetadata类型的实例,它持有关于当前正在被处理的类的注解信息。这里用来检索有关@EnableAspectJAutoProxy注解的信息,这些信息决定了如何配置AOP代理的行为(是否使用CGLIB代理以及是否暴露代理对象)。

  2. registryBeanDefinitionRegistry类型的实例,它是一个用于注册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等)和切点表达式。在SpringAOP实现中,代理创建器负责实际的代理对象创建工作,而用户定义的切面提供了应用于这些代理对象的通知逻辑。具体而言,它描述了如何在SpringApplicationContext中检查并可能更新或注册一个新的自动代理创建器(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的更多相关文章

  1. 正确理解Spring AOP中的Around advice

    Spring AOP中,有Before advice和After advice,这两个advice从字面上就可以很容易理解,但是Around advice就有点麻烦了. 乍一看好像是Before ad ...

  2. spring aop中pointcut表达式完整版

    spring aop中pointcut表达式完整版 本文主要介绍spring aop中9种切入点表达式的写法 execute within this target args @target @with ...

  3. 轻松理解 Spring AOP

    目录 Spring AOP 简介 Spring AOP 的基本概念 面向切面编程 AOP 的目的 AOP 术语和流程 术语 流程 五大通知执行顺序 例子 图例 实际的代码 使用 Spring AOP ...

  4. Spring AOP中的动态代理

    0  前言 1  动态代理 1.1 JDK动态代理 1.2 CGLIB动态代理 1.2.1 CGLIB的代理用法 1.2.2 CGLIB的过滤功能 2  Spring AOP中的动态代理机制 2.1  ...

  5. Spring AOP中的JDK和CGLib动态代理哪个效率更高?

    一.背景 今天有小伙伴面试的时候被问到:Spring AOP中JDK 和 CGLib动态代理哪个效率更高? 二.基本概念 首先,我们知道Spring AOP的底层实现有两种方式:一种是JDK动态代理, ...

  6. 深入理解Spring AOP之二代理对象生成

    深入理解Spring AOP之二代理对象生成 spring代理对象 上一篇博客中讲到了Spring的一些基本概念和初步讲了实现方法,当中提到了动态代理技术,包含JDK动态代理技术和Cglib动态代理 ...

  7. 转:Spring AOP中的动态代理

    原文链接:Spring AOP中的动态代理 0  前言 1  动态代理 1.1 JDK动态代理 1.2 CGLIB动态代理 1.2.1 CGLIB的代理用法 1.2.2 CGLIB的过滤功能 2  S ...

  8. spring 理解Spring AOP 一个简单的约定游戏

    应该说AOP原理是Spring技术中最难理解的一个部分,而这个约定游戏也许会给你很多的帮助,通过这个约定游戏,就可以理解Spring AOP的含义和实现方法,也能帮助读者更好地运用Spring AOP ...

  9. Spring AOP中定义切点(PointCut)和通知(Advice)

    如果你还不熟悉AOP,请先看AOP基本原理,本文的例子也沿用了AOP基本原理中的例子.切点表达式 切点的功能是指出切面的通知应该从哪里织入应用的执行流.切面只能织入公共方法.在Spring AOP中, ...

  10. Spring AOP高级——源码实现(2)Spring AOP中通知器(Advisor)与切面(Aspect)

    本文例子完整源码地址:https://github.com/yu-linfeng/BlogRepositories/tree/master/repositories/Spring%20AOP%E9%A ...

随机推荐

  1. Mysql带条件取多条随机记录

    有个文章段落表part,有两种类型的段落,即part_type取1或2,要从表中随机取多条任意类型的段落,比如3条. 方法一 ORDER BY后接RAND() select * from part w ...

  2. notepad运行python代码的步骤

    notepad运行python代码的步骤: 1.用notepad++打开python文件.或者新建文件,保存为.py格式. 2.在菜单栏上面有一个运行,我们点击运行->运行,或者使用快捷键F5. ...

  3. 9.prometheus监控--监控springboot2.x(Java)

    一.环境部署 yum search java | grep jdk yum install -y java-11-openjdk-devel 二.监控java应用(tomcat/jar) JMX ex ...

  4. Rancher管理K8s集群(14)

    一.Rancher介绍 1.1 Rancher简介 Rancher 是一个开源的企业级多集群 Kubernetes 管理平台,实现了 Kubernetes 集群在混合云+本地 数据中心的集中部署与管理 ...

  5. C语言实验1

    #include<stdio.h> #include<stdlib.h> int main() { printf(" o\n"); printf(" ...

  6. The instance of entity type 'Model' cannot be tracked because another instance with the same key value for {'Id'} is already being tracked.

    The instance of entity type 'Model' cannot be tracked because another instance with the same key val ...

  7. vue-公共组件的注册

    注册公共组件,在每个需要的页面直接输入文件名(<g-table/>)即可引用该组件 步骤: 1.新建components/global文件夹,以及components/global/g-t ...

  8. 批量解压上传SAP Note

    最近在做印度GST相关的东西,需要手动给系统实施上百个SAP Note,十分繁琐. 标准事务代码SNOTE只支持每次上传一个Note,逐个上传大量Note会很麻烦,为此摸索出一个批量解压上传的流程,下 ...

  9. MySQL优化方向

    MySQL优化手段 数据库设计层面 范式设计 减少数据冗余 提高数据一致性 索引策略 选择合适的索引类型 (BTREE, HASH) 覆盖索引 索引选择性 表结构优化 使用合适的数据类型 避免使用NU ...

  10. hutool QrCodeUtil解析二维码出现NotFoundException

    解析部分二维码时出现com.google.zxing.NotFoundException:null,解析失败的二维码手机扫是能正常打开的,后面发现这个问题是因为原二维码图片太大了,将图片缩小后正常解析 ...