SpringAOP源码解析之基础设施注册(一)

news2024/9/26 5:23:07

写在最前

AspectJ和Spring AOP是两种实现AOP(面向切面编程)的不同方式,它们在实现机制和使用方式上存在一些区别。

  1. AspectJ是一种独立的AOP框架,它提供了比Spring AOP更强大和更灵活的功能。AspectJ可以在编译时或者运行时织入切面,它使用自己的切点表达式语言来定义切点和通知,并且可以实现更细粒度的切面编程。AspectJ支持静态织入和动态织入,以及多种织入方式(编译时织入、类加载时织入、运行时织入等)。

  2. Spring AOP是Spring框架提供的一种轻量级AOP解决方案,它集成在Spring框架中,使用动态代理实现AOP功能。Spring AOP提供了基于代理的运行时织入,它使用AspectJ的切点表达式语言来定义切点,并支持常见的通知类型(前置通知、后置通知、环绕通知等)。相比于AspectJ,Spring AOP更注重于简化配置和集成,提供了更方便的声明式AOP编程方式。

下面是一些AspectJ和Spring AOP之间的区别:

  • 功能强大程度:AspectJ提供了更多的AOP功能和更细粒度的控制,例如引入(introduction)和复杂的切点定义。而Spring AOP提供了基本的AOP功能,适用于大多数常见的AOP需求,但相对更简单和易于使用。

  • 织入方式:AspectJ支持静态织入和动态织入,可以在编译时或者运行时将切面织入目标代码。而Spring AOP使用动态代理,在运行时通过代理对象实现切面功能。

  • 集成与配置:AspectJ是一个独立的AOP框架,需要单独配置和使用。而Spring AOP与Spring框架集成在一起,可以直接使用Spring的IoC容器和其他功能,通过简单的配置即可使用AOP功能。

  • 性能:AspectJ的织入是在编译时或者类加载时完成的,因此在性能上通常比Spring AOP更高效。Spring AOP的运行时代理会引入额外的开销,但对于大多数应用场景来说,性能差异可能并不显著。

综上所述,AspectJ适用于需要更高级别、更精细控制的AOP需求,而Spring AOP适用于那些希望在Spring应用程序中使用简单、轻量级AOP的场景。选择使用哪种方式取决于你的具体需求和项目背景。

AOP概念

让我们首先定义一些核心的AOP概念和术语。这些术语不是Spring特有的,所以SpringAOP并没有单独定义一套自己的术语,而是使用的通用的AOP术语。

  • 切面(Aspect):跨越多个类的关注点的模块化。事务管理是企业级Java应用程序中一个典型的横切关注点。在Spring AOP中,切面可以通过普通类(基于Schema的方式)或带有@Aspect注解的普通类(@AspectJ风格)来实现。

  • 连接点(Join point):程序执行过程中的一个点,例如方法的执行或异常的处理。在Spring AOP中,连接点始终代表方法的执行。

  • 通知(Advice):切面在特定连接点上执行的操作。不同类型的通知包括"around"、"before"和"after"通知(后面会讨论通知类型)。许多AOP框架,包括Spring,在模型中将通知视为拦截器,并维护围绕连接点的拦截器链。

  • 切点(Pointcut):用于匹配连接点的谓词。通知与切点表达式相关联,并在与切点匹配的任何连接点上运行(例如,执行具有特定名称的方法)。连接点与切点表达式的匹配是AOP的核心概念,Spring默认使用AspectJ的切点表达式语言。

  • 引入(Introduction):代表类型声明附加方法或字段。Spring AOP允许您为任何被通知的对象引入新的接口(以及相应的实现)。例如,您可以使用引入使一个Bean实现IsModified接口,以简化缓存操作(在AspectJ社区中,引入被称为inter-type声明)。

  • 目标对象(Target object):被一个或多个切面通知的对象。也称为"被通知对象"。由于Spring AOP是通过运行时代理实现的,因此这个对象总是一个被代理的对象。

  • AOP代理(AOP proxy):AOP框架创建的对象,用于实现切面的契约(例如,通知方法的执行)。在Spring框架中,AOP代理可以是JDK动态代理或CGLIB代理。

  • 织入(Weaving):将切面与其他应用程序类型或对象连接起来,创建一个被通知的对象。这可以在编译时(例如使用AspectJ编译器)、加载时或运行时进行。Spring AOP与其他纯Java AOP框架一样,在运行时进行织入。

下面是一个使用Spring AOP的示例,以更清楚地说明这些概念:

@Aspect
@Component
public class LoggingAspect {
    @Pointcut("execution(* com.example.*.*(..))")
    public void loggableMethods() {}
    
    @Before("loggableMethods()")
    public void beforeMethodExecution(JoinPoint joinPoint) {
        System.out.println("Before method execution: " + joinPoint.getSignature().getName());
    }
    
    @After("loggableMethods()")
    public void afterMethodExecution(JoinPoint joinPoint) {
        System.out.println("After method execution: " + joinPoint.getSignature().getName());
    }
}

在上述示例中:

  • LoggingAspect是一个切面,通过@Aspect注解标识。
  • loggableMethods()是一个切点,通过@Pointcut注解定义。它匹配所有com.example包下的方法。
  • beforeMethodExecution()afterMethodExecution()是通知,分别在loggableMethods()切点匹配的方法执行前和执行后执行。
  • JoinPoint是连接点,它表示方法的调用或执行的具体位置。

总结:

  • 切面定义了在何处以及何时应用通知。
  • 连接点是程序执行过程中的特定点,例如方法调用或执行。
  • 通知是切面在连接点上执行的具体行为,可以是前置通知、后置通知、返回通知、异常通知或环绕通知。
  • 切点定义了在应用程序中哪些连接点上应用通知,使用切点表达式进行匹配。

Spring AOP包括以下类型的通知:

1、前置通知(Before advice):在连接点之前执行的通知,但它无法阻止执行流程继续到连接点(除非抛出异常)。

2、返回通知(After returning advice):在连接点正常完成后执行的通知,例如方法返回而没有抛出异常。

3、异常通知(After throwing advice):在连接点通过抛出异常而退出时执行的通知。

4、最终通知(After (finally) advice):无论连接点以哪种方式退出(正常返回或异常返回),都会执行的通知。

5、环绕通知(Around advice):环绕连接点(例如方法调用)的通知。这是最强大的通知类型。环绕通知可以在方法调用之前和之后执行自定义行为。它还负责选择是继续到连接点执行还是通过返回自己的返回值或抛出异常来终止被通知方法的执行。

源码分析

@EnableAspectJAutoProxy

@EnableAspectJAutoProxy用于启用处理使用AspectJ的@Aspect注解标记的组件的支持,类似于Spring的aop:aspectj-autoproxy XML元素的功能。

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(AspectJAutoProxyRegistrar.class)
public @interface EnableAspectJAutoProxy {

	/**
	 * Indicate whether subclass-based (CGLIB) proxies are to be created as opposed
	 * to standard Java interface-based proxies. The default is {@code false}.
	 * 用于指定使用CGLIB代理还是JDK动态代理,如果目标对象没有实现任何接口,则必须使用CGLIB代理,否则会抛出异常。如果目标对象实现了
	 * 接口,则默认使用JDK动态代理。如果设置为true,则强制使用CGLIB动态代理
	 */
	boolean proxyTargetClass() default false;

	/**
	 * Indicate that the proxy should be exposed by the AOP framework as a {@code ThreadLocal}
	 * for retrieval via the {@link org.springframework.aop.framework.AopContext} class.
	 * Off by default, i.e. no guarantees that {@code AopContext} access will work.
	 * @since 4.3.1
	 * 是否将代理对象暴露给ThreadLocal中的AopContext.currentProxy()方法。默认为false。如果设置为true
	 * 则可以通过方法获取到当前的代理对象。
	 * 需要注意的是,将exposeProxy设置为true的时候可能会带来性能和内存方面的开销,并且可能导致AopContext被意外的暴露到非信任的代码中。
	 */
	boolean exposeProxy() default false;

}

@Import(AspectJAutoProxyRegistrar.class)

AspectJAutoProxyRegistrar实现了ImportBeanDefinitionRegistrar。

@EnableAspectJAutoProxy
@ComponentScan(value = {"com.qhyu.cloud.**"})
public class AopConfig {

当AopConfig类上加了这个注解的时候,AopConfig配置类在ConfigurationClassPostProcessor被调用postProcessBeanDefinitionRegistry方法的时候查看是否有实现ImportBeanDefinitionRegistrars,毫无疑问AspectJAutoProxyRegistrar实现了ImportBeanDefinitionRegistrar,所以将会调用AspectJAutoProxyRegistrar的registerBeanDefinitions方法。

这里我需要讲一个题外话,ConfigurationClassParser在解析候选配置类的时候会处理@Import注解,在Spring中,处理@Import注解的逻辑涉及到不同的方式和时机。

在这里插入图片描述

上面这个截图位置已经非常清楚了。

  1. 基本的@Import注解处理:
    当解析配置类时,Spring会检查其中是否存在@Import注解。如果存在,它将解析注解值并加载被导入的类或配置类。这些被导入的类或配置类将成为应用上下文中的Bean,可以用于依赖注入和组件扫描。

  2. ImportSelector接口:
    如果@Import注解的值是实现了ImportSelector接口的类,那么Spring将调用该类的selectImports()方法。ImportSelector接口允许根据特定条件选择要导入的类或配置类。selectImports()方法返回一个字符串数组,其中包含要导入的类或配置类的全限定名。

  3. ImportBeanDefinitionRegistrar接口:
    如果@Import注解的值是实现了ImportBeanDefinitionRegistrar接口的类,那么Spring将调用该类的registerBeanDefinitions()方法。ImportBeanDefinitionRegistrar接口允许以编程方式向Spring容器注册更多的Bean定义。

  4. 配置类处理:
    如果@Import注解的值既不是ImportSelector的实现类,也不是ImportBeanDefinitionRegistrar的实现类,那么Spring将将其视为普通的配置类,并对其进行处理。这意味着被导入的类或配置类将被加载到应用上下文中,成为可用的Bean。

可以根据不同的情况选择使用不同的@Import注解方式,以实现不同的导入逻辑和条件选择。这样可以更灵活地配置Spring应用程序的组件和依赖关系。

回到正题,这个@Import主要是注册这个类的beanDefinition信息。

@Override
	public void registerBeanDefinitions(
			AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
		// 注册AnnotationAwareAspectJAutoProxyCreator
		// bean的名称为 org.springframework.aop.config.internalAutoProxyCreator
		AopConfigUtils.registerAspectJAnnotationAutoProxyCreatorIfNecessary(registry);

		// 用于获取@EnableAspectJAutoProxy的注解属性值
		AnnotationAttributes enableAspectJAutoProxy =
				AnnotationConfigUtils.attributesFor(importingClassMetadata, EnableAspectJAutoProxy.class);
		if (enableAspectJAutoProxy != null) {
			if (enableAspectJAutoProxy.getBoolean("proxyTargetClass")) {
				AopConfigUtils.forceAutoProxyCreatorToUseClassProxying(registry);
			}
			if (enableAspectJAutoProxy.getBoolean("exposeProxy")) {
				AopConfigUtils.forceAutoProxyCreatorToExposeProxy(registry);
			}
		}
	}

AbstractAutoProxyCreator

AbstractAutoProxyCreator是一个抽象类,实现了Spring框架中的BeanPostProcessor接口。BeanPostProcessor接口定义了在Bean初始化过程中的前后处理方法,其中包括postProcessAfterInitialization方法。

AbstractAutoProxyCreator的作用是在Bean初始化完成之后,通过postProcessAfterInitialization方法创建AOP代理对象。它会检查目标Bean是否符合AOP代理的条件,例如是否标记了特定的注解或者实现了特定的接口。如果目标Bean满足条件,AbstractAutoProxyCreator会使用适当的代理工具(如JDK动态代理或CGLIB)创建代理对象,并将其替换原始的目标Bean。

通过代理对象,AbstractAutoProxyCreator能够在目标Bean的方法执行前后插入额外的逻辑,例如执行切面的通知方法。这样可以实现AOP的功能,例如方法拦截、事务管理等。

需要注意的是,AbstractAutoProxyCreator是一个抽象类,具体的AOP代理创建逻辑由其子类实现。常见的子类包括AnnotationAwareAspectJAutoProxyCreator和InfrastructureAdvisorAutoProxyCreator等。

总结起来,AbstractAutoProxyCreator的作用是在Bean初始化完成后,通过postProcessAfterInitialization方法创建AOP代理对象,以实现AOP功能。

@Override
	public Object postProcessAfterInitialization(@Nullable Object bean, String beanName) {
		if (bean != null) {
			//根据给定的bean的class和name构建出一个key,格式beanclassName_beanName
			Object cacheKey = getCacheKey(bean.getClass(), beanName);
			if (this.earlyProxyReferences.remove(cacheKey) != bean) {
				// 真正进行处理的地方,里面有代码很明显是用来创建代理对象的
				return wrapIfNecessary(bean, beanName, cacheKey);
			}
		}
		return bean;
	}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1130783.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

ICMP权限许可和访问控制漏洞处理(CVE-1999-0524)

一、问题描述 某次例行安全扫描,发现:ICMP权限许可和访问控制漏洞,编号:CVE-1999-0524,危险级别:低风险。利用该漏洞,远程主机会回复ICMP_TIMESTAMP查询并返回它们系统的当前时间,I…

浅谈安科瑞ASJ继电器在马尔代夫环岛水上排屋的应用

摘要:对电气线路进行接地故障保护,方式接地故障电流引起的设备和电气火灾事故越来越成为日常所需。针对用户侧主要的用能节点,设计安装剩余电流继电器,实时监控各用能回路的剩余电流状态。通过实时监控用能以及相关电力参数、提高…

asp.net乡村旅游管理系统VS开发sqlserver数据库web结构c#编程Microsoft Visual Studio

一、源码特点 asp.net乡村旅游管理系统是一套完善的web设计管理系统系统,系统具有完整的源代码和数据库,系统主要采用B/S模式开发。开发环境为vs2010,数据库为sqlserver2008,使用c# 语言开发 asp.net乡村旅游管理系统 二、…

2018年亚太杯APMCM数学建模大赛B题人才与城市发展求解全过程文档及程序

2018年亚太杯APMCM数学建模大赛 B题 人才与城市发展 原题再现 招贤纳士是过去几年来许多城市的亮点之一。北京、上海、武汉、成都、西安、深圳,实际上都在用各种吸引人的政策来争夺人才。人才代表着城市创新发展的动力,因为他们能够在更短的时间内学习…

Linux国产系统无法连接身份证读卡器USB权限解决办法

​ 如上图:连接身份证读卡器失败 接入读卡器后,运行output,读卡报错:libusb open device with pid vid: Resource temporarily unavailable,连接读卡器失败,t_nRe -1 ,这是由于未接入读卡器或…

JavaEE 网络原理——TCP的工作机制(末篇 其余TCP特点)

文章目录 一、滑动窗口二、流量控制三、拥堵控制四、延时应答五、捎带应答六、面向字节流七、异常情况八、总结 其余相关文章: JavaEE 网络原理——TCP的工作机制(中篇 三次握手和四次挥手) 本篇文章衔接的是前面两篇文章的内容,在这里继续解释 TCP 的内…

GO语言代码示例

首先,我们需要安装 rod 库,这是一个用于构建网络爬虫的 Go 语言库。 使用 go get 命令安装 rod 库:go get -u github.com/gofiber/rod 创建一个新的 Go 程序文件,例如:main.go 在 main.go 文件中,导入 r…

(免费领源码)java#Springboot#mysql 大学生兼职平台94598-计算机毕业设计项目选题推荐

摘 要 当今人类社会已经进入信息全球化和全球信息化、网络化的高速发展阶段。丰富的网络信息已经成为人们工作、生活、学习中不可缺少的一部分。人们正在逐步适应和习惯于网上贸易、网上购物、网上支付、网上服务和网上娱乐等活动,人类的许多社会活动正在向网络化发…

认识环境变量和进程替换,实现一个简易的shell

文章目录 一、环境变量1.什么是环境变量2.环境变量的分类3.查看环境变量4.设置环境变量5.获取环境变量 二、进程控制1.进程终止2.进程等待3.进程替换 三、实现一个简单的shell 一、环境变量 1.什么是环境变量 首先,在百度百科中,环境变量的解释是这样的…

第1篇 目标检测概述 —(3)目标检测评价指标

前言:Hello大家好,我是小哥谈。目标检测评价指标是用来衡量目标检测算法性能的指标,主要包括几个指标:精确率(Precision)、召回率(Recall)、交并比(IoU)、平均…

ES SearchAPI----Query DSL语言

文章目录 Getting Startedmatch_all查询全部sort排序from\size分页_source指定字段 match匹配查询match_phrase短语匹配multi_match多字段匹配range范围查询bool复合查询must必须匹配,可贡献得分must_not必须不匹配,可贡献得分should可有可无&#xff0c…

GIS 数据结构BSP树

BSP树(Binary Space Partitioning Tree,二值空间划分树)的主要思想是将空间分割成更小的部分,从而使空间查询和其他运算更加高效。每个节点在BSP树中代表一个空间区域,这个区域可以进一步被子节点所分割。 一旦把凹形区…

C# 读取文件类型

获取示例 代码 using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Runtime.InteropServices; using System.Text; using System.Threading.Tasks;namespace ConsoleApp6filetype {internal class Program{static void Main(…

使用openlayers加载离线瓦片地图

一、需求背景 我们现在的项目就说使用openlayer geoServer自己发布的矢量地图,是和公安合作的项目,由于政府大都使用的是天地图,所以需要将geoServer的矢量地图改为天地图,搭配openlayers使用,openlayers还可以切换不…

spi的基本知识、软件spi、硬件spi(ADXL362为测试对象)

一、spi基础知识 1.1、spi基础知识一 SPI(Serial Peripheral Interface,串行外设接口)是由摩托罗拉(Motorola)在1980前后提出的一种全双工同步串行通信接口,它用于MCU与各种外围设备以串行方式进行通…

纹波类型及纹波抑制措施

纹波主要有这几种类型:输入低频纹波、高频纹波、寄生参数引起的共模纹波噪声、功率器件开关过程中产生的超高频谐振噪声和闭环调节控制引起的纹波噪声。 1、低频纹波 低频纹波是与输出电路的滤波电容容量相关。电容的容量不可能无限制地增加,导致输出低…

视频号视频提取小程序,快速下载视频号视频

​视频号提取小程序可以帮助用户方便地从视频号视频平台获取到自己喜欢的视频号内容。通过这个小程序,你可以快速搜索并提取出视频号,并进行相关的操作。 据悉视频下载bot小程序目前已经更名为【提取下载小助手】 使用视频号提取小程序有以下几个步骤&…

postgresql group by之后对string字段进行拼接

数据如下 想要的效果 sql 如下 SELECT company_id, string_agg(employee, , ORDER BY employee) FROM mytable GROUP BY company_id;PostgreSQL STRING_AGG()函数是一个聚合函数,用于连接字符串列表并在它们之间放置分隔符。该函数不会在字符串的末尾添加分隔符。…

C语言猜数字小游戏(也包含python实现的用法)

猜数字小游戏 前言1. C语言中随机数生成1.1 rand1.2 srand1.3 time1.4 设置随机数的范围 2. 猜数字游戏实现C语言实现猜数字小游戏python方法实现猜数字小游戏 前言 本文基于VS2022、pycharm和前面的知识,写一个凭借分支与循环的小游戏,比如&#xff1a…

C# 使用using报错CS1674解决办法

Severity Code Description Project File Line Suppression State Error CS1674 ‘MutipleThreadResetEvent’: type used in a using statement must be implicitly convertible to ‘System.IDisposable’. 第一种解决方法:工具 -.NuGet下载EntityFramework是否能…