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

写在最前

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信息。

@Overridepublic void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {// 注册AnnotationAwareAspectJAutoProxyCreator// bean的名称为 org.springframework.aop.config.internalAutoProxyCreatorAopConfigUtils.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功能。

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

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

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

vscode不显示横滚动条处理

最近发现vscode打开本地文件不显示水平的滚动条,但是打开一个临时文件是有水平滚动条的。 解决方案 可以一个个试 vscode配置 左下角设置–设置–搜索Scrollbar: Horizontal auto 自动visible 一直展示hidden 一直隐藏 拖动底部状态栏 发现是有的,但是…

Linux系统之file命令的基本使用

Linux系统之file命令的基本使用 一、file命令介绍1.1 Linux简介1.2 file命令简介 二、file命令的使用帮助2.1 file命令的help帮助信息2.2 file命令的语法解释2.3 file命令的man手册 三、文件类型介绍四、file命令的基本使用4.1 查询file版本4.2 显示文件类型4.3 输出时不显示文…

【Linux】部署单机OA项目及搭建spa前后端分离项目

目录 部署OA项目 ​编辑 搭建spa前后端分离项目 后端 前端 配置坏境变量 部署OA项目 在虚拟机中,将项目打包成war文件放置到tomcat根目录下的webapps文件目录下 再在主机数据库中连接数据库,并定义数据库名导入相关的表 继续进入tomcat目录下双击点…

三、【常用的几种抠图方式二】

文章目录 橡皮擦魔术橡皮擦背景橡皮擦选择被遮住(调整边缘)主体抠图 橡皮擦 直接擦除图片的像素,或者填充背景色,适用于要求不高的图片。 魔术橡皮擦 擦出颜色相近的内容,适用于主体跟背景颜色相差较大的情况&#x…

JVM进阶(2)

一)方法区: java虚拟机中有一个方法区,该区域被所有的java线程都是共享,虚拟机一启动,运行时数据区就被开辟好了,官网上说了方法区可以不压缩还可以不进行GC,JAVA虚拟机就相当于是接口,具体的HotSpot就是虚…

JVM(Java Virtual Machine)G1收集器篇

前言 本文参考《深入理解Java虚拟机》,本文主要介绍G1收集器的收集思想和具体过程(填上一篇文章留下的坑) 本系列其他文章链接: JVM(Java Virtual Machine)内存模型篇 JVM(Java Virtual Machi…

网络协议--广播和多播

12.1 引言 在第1章中我们提到有三种IP地址:单播地址、广播地址和多播地址。本章将更详细地介绍广播和多播。 广播和多播仅应用于UDP,它们对需将报文同时传往多个接收者的应用来说十分重要。TCP是一个面向连接的协议,它意味着分别运行于两主…

Git撤销已经push到远程分支的commit

有时想要撤销已经push到远程仓库的commit,将代码还原为commit之前的样子,应该如何做呢? 如果只有自己使用的分支:可以使用git reset命令 git log 查看需要还原的commitId,如下截图, 我们需要撤销ffe4a的…

JVM虚拟机:对象在内存中的存储布局

本文重点 在前面的过程中,我们学习了对象创建过程,那么一个对象在内存中的布局是什么样的呢? 对象在内存中的存储布局 普通对象 当我们创建一个对象的时候,它由三部分组成,分别为对象头(MarkWord+class指针(指向class对象)),实例数据(对象的成员变量),填充。如果…

Linux MMC子系统 - 2.eMMC 5.1总线协议浅析

By: Ailson Jack Date: 2023.10.27 个人博客:http://www.only2fire.com/ 本文在我博客的地址是:http://www.only2fire.com/archives/161.html,排版更好,便于学习,也可以去我博客逛逛,兴许有你想要的内容呢。…

酷开科技 | 酷开系统大屏电视,打造精彩家庭场景

在信息资讯不发达的年代,电视机一直都是个人及家庭重要的信息获取渠道和家庭娱乐中心,是每个家庭必不可少的大家电之一!在快节奏的现代生活中,受手机和平板的冲击,电视机这个曾经的客厅“霸主”一度失去了“主角光环”…

安卓恶意应用识别(四)(特征处理与分类模型构建)——终结

前言 前面三章将数据初步整理出来: 1.安卓恶意应用识别(一)(Python批量爬取下载安卓应用) 2.安卓恶意应用识别(二)(安卓APK反编译) 3.安卓恶意应用识别(三&a…