jjzjj

Spring AOP原理分析(二)--@EnableAspectJAutoProxy功能分析

每天都要进步一点点 2023-06-04 原文

目录

一、概述

二、@EnableAspectJAutoProxy原理

三、总结


一、概述

前面一篇文章,我们介绍了Spring AOP中一些相关术语,并演示了如何利用AOP。我们大致了解到,要开启注解版的Spring AOP功能的话,我们可以在配置类上添加@EnableAspectJAutoProxy注解,如下:

@Configuration
// @EnableAspectJAutoProxy用于开启注解版的Spring AOP功能,类似使用XML方式的<aop:aspectj-autoproxy>
@EnableAspectJAutoProxy
@ComponentScan("com.wsh")
public class AopConfig {

}

所以要分析Spring AOP原理,自然从@EnableAspectJAutoProxy注解开始,下面我们就来看看@EnableAspectJAutoProxy内部到底做了什么工作?

二、@EnableAspectJAutoProxy原理

先来看下@EnableAspectJAutoProxy注解的源码,如下所示:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
// 引入AspectJAutoProxyRegistrar,利用AspectJAutoProxyRegistrar注册一些Bean
@Import(AspectJAutoProxyRegistrar.class)
public @interface EnableAspectJAutoProxy {

	/**
	 * 控制是基于subclass-based的CGLIB动态代理还是使用基于接口的JDK动态代理
	 * 默认值为false,即默认使用JDK动态代理方式
	 */
	boolean proxyTargetClass() default false;

	/**
	 * 控制代理的暴露方式,解决类内部方法之间调用不能使用代理的场景
	 * 默认值为false,如果设置为true的话,我们就可以通过AopContext.currentProxy()很容易拿到当前代理对象
	 */
	boolean exposeProxy() default false;

}

可以看到,@EnableAspectJAutoProxy注解中有两个属性:

  • proxyTargetClass:控制是基于子类继承的CGLIB动态代理还是使用基于接口的JDK动态代理,proxyTargetClass默认是false,即默认使用JDK动态代理方式,如果设置为true的话,则表示强制使用Cglib动态代理;
  • exposeProxy:控制代理的暴露方式,默认为false,如果设置为true的话,我们就可以通过AopContext.currentProxy()很容易拿到当前代理对象,这在解决Spring事务失效的方法自调用场景下很有用;

从源码可以看出,@EnableAspectJAutoProxy注解上面标注了@Import(AspectJAutoProxyRegistrar.class),使用@Import注解引入了AspectJAutoProxyRegistrar组件,然后利用AspectJAutoProxyRegistrar注册了一些Bean。

@Import注解:可以引入一个类,将这个类注入到Spring IOC容器中被当前Spring管理。

我们继续点击到AspectJAutoProxyRegistrar类的源码中,如下所示:

class AspectJAutoProxyRegistrar implements ImportBeanDefinitionRegistrar {

	/**
	 * Register, escalate, and configure the AspectJ auto proxy creator based on the value
	 * of the @{@link EnableAspectJAutoProxy#proxyTargetClass()} attribute on the importing
	 * {@code @Configuration} class.
	 */
	@Override
	public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {

		// 1.向IOC容器中注入AnnotationAwareAspectJAutoProxyCreator组件的bean定义信息(基于注解的自动代理创建器), 并且beanName = "org.springframework.aop.config.internalAutoProxyCreator"
		AopConfigUtils.registerAspectJAnnotationAutoProxyCreatorIfNecessary(registry);

		// 2.获取@EnableAspectJAutoProxy注解的属性信息
		AnnotationAttributes enableAspectJAutoProxy =
				AnnotationConfigUtils.attributesFor(importingClassMetadata, EnableAspectJAutoProxy.class);
		if (enableAspectJAutoProxy != null) {
			// 3.设置使用Cglib动态代理
			if (enableAspectJAutoProxy.getBoolean("proxyTargetClass")) {
				// 强制使用CGLIB动态代理,实际上是设置beanName = "org.springframework.aop.config.internalAutoProxyCreator"这个bean的proxyTargetClass属性值为true
				AopConfigUtils.forceAutoProxyCreatorToUseClassProxying(registry);
			}

			// 4.设置代理的暴露方式,即强制暴露Bean的代理对象到AopContext中
			if (enableAspectJAutoProxy.getBoolean("exposeProxy")) {
				// 实际上是设置beanName = "org.springframework.aop.config.internalAutoProxyCreator"这个bean的exposeProxy属性值为true
				AopConfigUtils.forceAutoProxyCreatorToExposeProxy(registry);
			}
		}
	}

}

可以看到AspectJAutoProxyRegistrar实现了ImportBeanDefinitionRegistrar接口,ImportBeanDefinitionRegistrar接口提供了registerBeanDefinitions()方法允许我们向IOC容器中注入一些bean定义信息的功能。

AspectJAutoProxyRegistrar#registerBeanDefinitions()方法的核心就是:

AopConfigUtils.registerAspectJAnnotationAutoProxyCreatorIfNecessary(registry);

我们看看其源码:

// org.springframework.aop.config.AopConfigUtils#registerAspectJAnnotationAutoProxyCreatorIfNecessary(org.springframework.beans.factory.support.BeanDefinitionRegistry)
public static BeanDefinition registerAspectJAnnotationAutoProxyCreatorIfNecessary(BeanDefinitionRegistry registry) {
    return registerAspectJAnnotationAutoProxyCreatorIfNecessary(registry, null);
}

public static BeanDefinition registerAspectJAnnotationAutoProxyCreatorIfNecessary(
    BeanDefinitionRegistry registry, @Nullable Object source) {
    // 注册AnnotationAwareAspectJAutoProxyCreator组件
    return registerOrEscalateApcAsRequired(AnnotationAwareAspectJAutoProxyCreator.class, registry, source);
}

private static BeanDefinition registerOrEscalateApcAsRequired(
    Class<?> cls, BeanDefinitionRegistry registry, @Nullable Object source) {

    Assert.notNull(registry, "BeanDefinitionRegistry must not be null");

    // 判断IOC容器中是否已经存在beanName="org.springframework.aop.config.internalAutoProxyCreator"的自动代理创建器(即用户自定义的AnnotationAwareAspectJAutoProxyCreator)
    if (registry.containsBeanDefinition(AUTO_PROXY_CREATOR_BEAN_NAME)) {
        BeanDefinition apcDefinition = registry.getBeanDefinition(AUTO_PROXY_CREATOR_BEAN_NAME);
        // 如果已经存在自动代理创建器,需判断cls对象的name和apcDefinition的beanClassName是否相等,如果不相等的话,则获取apcDefinition和cls的优先级,如果apcDefinition的优先级小于cls的优先级,则将apcDefinition的beanClassName设置为cls的name值
        if (!cls.getName().equals(apcDefinition.getBeanClassName())) {
            int currentPriority = findPriorityForClass(apcDefinition.getBeanClassName());
            int requiredPriority = findPriorityForClass(cls);
            if (currentPriority < requiredPriority) {
                apcDefinition.setBeanClassName(cls.getName());
            }
        }
        return null;
    }

    // 将现在要注册的AnnotationAwareAspectJAutoProxyCreator封装成RootBeanDefinition
    RootBeanDefinition beanDefinition = new RootBeanDefinition(cls);
    beanDefinition.setSource(source);
    // 拥有最高优先级
    beanDefinition.getPropertyValues().add("order", Ordered.HIGHEST_PRECEDENCE);
    beanDefinition.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
    // 往IOC容器中注入AnnotationAwareAspectJAutoProxyCreator,name为"org.springframework.aop.config.internalAutoProxyCreator"
    registry.registerBeanDefinition(AUTO_PROXY_CREATOR_BEAN_NAME, beanDefinition);
    return beanDefinition;
}

从源码可以看出,registerOrEscalateApcAsRequired()大致处理过程如下:

  1. 判断IOC容器中是否已经存在beanName="org.springframework.aop.config.internalAutoProxyCreator"的自动代理创建器(即用户自定义的AnnotationAwareAspectJAutoProxyCreator);
  2. 如果已经存在自动代理创建器,需判断cls对象的name和apcDefinition的beanClassName是否相等,如果不相等的话,则获取apcDefinition和cls的优先级,如果apcDefinition的优先级小于cls的优先级,则将apcDefinition的beanClassName设置为cls的name值;
  3. 将现在要注册的AnnotationAwareAspectJAutoProxyCreator封装成RootBeanDefinition;
  4. 往IOC容器中注入AnnotationAwareAspectJAutoProxyCreator组件,并且name为"org.springframework.aop.config.internalAutoProxyCreator";

分析到这里,其实我们已经了解@EnableAspectJAutoProxy注解的功能:

通过@Import导入AspectJAutoProxyRegistrar,AspectJAutoProxyRegistrar又继承了ImportBeanDefinitionRegistrar,利用AspectJAutoProxyRegistrar往IOC容器中注册了AnnotationAwareAspectJAutoProxyCreator定义信息,并且名称为name为"org.springframework.aop.config.internalAutoProxyCreator"。

三、总结

通过前面的分析,我们大致了解到,@EnableAspectJAutoProxy注解最终往容器中注入了AnnotationAwareAspectJAutoProxyCreator组件,它其实就是AOP的核心了,AOP相关功能都是围绕AnnotationAwareAspectJAutoProxyCreator展开的。

下面我们看下AnnotationAwareAspectJAutoProxyCreator的类图:

 从类图可以看到,AnnotationAwareAspectJAutoProxyCreator有几个特点:

  • 实现了一系列Aware的接口,如BeanFactoryAware、BeanClassLoaderAware;
  • 间接实现了BeanPostProcessor接口,它允许我们在bean初始化前、后对bean进行修改,或产生一个bean的代理对象等,我们需要重点关注AnnotationAwareAspectJAutoProxyCreator以及其各个父类中postProcessBeforeInitialization()、postProcessAfterInitialization()方法的处理过程;
  • 间接实现了InstantiationAwareBeanPostProcessor接口,所以我们也需要重点关注AnnotationAwareAspectJAutoProxyCreator以及其各个父类中postProcessBeforeInstantiation()、postProcessAfterInstantiation()方法的实现;

下面通过一张图,简述一下@EnableAspectJAutoProxy的处理过程:

有关Spring AOP原理分析(二)--@EnableAspectJAutoProxy功能分析的更多相关文章

  1. ruby-on-rails - Cucumber 是否只是 rspec 的包装器以帮助将测试组织成功能? - 2

    只是想确保我理解了事情。据我目前收集到的信息,Cucumber只是一个“包装器”,或者是一种通过将事物分类为功能和步骤来组织测试的好方法,其中实际的单元测试处于步骤阶段。它允许您根据事物的工作方式组织您的测试。对吗? 最佳答案 有点。它是一种组织测试的方式,但不仅如此。它的行为就像最初的Rails集成测试一样,但更易于使用。这里最大的好处是您的session在整个Scenario中保持透明。关于Cucumber的另一件事是您(应该)从使用您的代码的浏览器或客户端的角度进行测试。如果您愿意,您可以使用步骤来构建对象和设置状态,但通常您

  2. ruby-on-rails - rails 功能测试 - 2

    在Rails自动生成的功能测试(test/functional/products_controller_test.rb)中,我看到以下代码:classProductsControllerTest我的问题是:方法调用products()在哪里/如何定义?products(:one)到底是什么意思?看代码,大概意思是“创建一个产品”,但是它是如何工作的呢?注意我是Ruby/Rails的新手,如果这些是微不足道的问题,我深表歉意。 最佳答案 如果您查看test/fixtures文件夹,您会看到一个products.yml文件。这是在您创建

  3. ruby-on-rails - 功能测试 Authlogic? - 2

    在我的一些Controller中,我有一个before_filter检查用户是否登录?用于CRUD操作。application.rbdeflogged_in?unlesscurrent_userredirect_toroot_pathendendprivatedefcurrent_user_sessionreturn@current_user_sessionifdefined?(@current_user_session)@current_user_session=UserSession.findenddefcurrent_userreturn@current_userifdefine

  4. 建模分析 | 平面2R机器人(二连杆)运动学与动力学建模(附Matlab仿真) - 2

    目录0专栏介绍1平面2R机器人概述2运动学建模2.1正运动学模型2.2逆运动学模型2.3机器人运动学仿真3动力学建模3.1计算动能3.2势能计算与动力学方程3.3动力学仿真0专栏介绍?附C++/Python/Matlab全套代码?课程设计、毕业设计、创新竞赛必备!详细介绍全局规划(图搜索、采样法、智能算法等);局部规划(DWA、APF等);曲线优化(贝塞尔曲线、B样条曲线等)。?详情:图解自动驾驶中的运动规划(MotionPlanning),附几十种规划算法1平面2R机器人概述如图1所示为本文的研究本体——平面2R机器人。对参数进行如下定义:机器人广义坐标

  5. 网站日志分析软件--让网站日志分析工作变得更简单 - 2

    网站的日志分析,是seo优化不可忽视的一门功课,但网站越大,每天产生的日志就越大,大站一天都可以产生几个G的网站日志,如果光靠肉眼去分析,那可能看到猴年马月都看不完,因此借助网站日志分析工具去分析网站日志,那将会使网站日志分析工作变得更简单。下面推荐两款网站日志分析软件。第一款:逆火网站日志分析器逆火网站日志分析器是一款功能全面的网站服务器日志分析软件。通过分析网站的日志文件,不仅能够精准的知道网站的访问量、网站的访问来源,网站的广告点击,访客的地区统计,搜索引擎关键字查询等,还能够一次性分析多个网站的日志文件,让你轻松管理网站。逆火网站日志分析器下载地址:https://pan.baidu.

  6. ABB-IRB-1200运动学分析MATLAB RVC工具分析+Simulink-Adams联合仿真 - 2

    一、机器人介绍        此处是基于MATLABRVC工具箱,对ABB-IRB-1200型号的微型机械臂进行正逆向运动学分析,并利Simulink工具实现对机械臂进行具有动力学参数的末端轨迹规划仿真,最后根据机械模型设计Simulink-Adams联合仿真。 图1.ABBIRB 1200尺寸参数示意图ABBIRB 1200提供的两种型号广泛适用于各作业,且两者间零部件通用,两种型号的工作范围分别为700 mm 和 900 mm,大有效负载分别为 7 kg 和5 kg。 IRB 1200 能够在狭小空间内能发挥其工作范围与性能优势,具有全新的设计、小型化的体积、高效的性能、易于集成、便捷的接

  7. 关于Qt程序打包后运行库依赖的常见问题分析及解决方法 - 2

    目录一.大致如下常见问题:(1)找不到程序所依赖的Qt库version`Qt_5'notfound(requiredby(2)CouldnotLoadtheQtplatformplugin"xcb"in""eventhoughitwasfound(3)打包到在不同的linux系统下,或者打包到高版本的相同系统下,运行程序时,直接提示段错误即segmentationfault,或者Illegalinstruction(coredumped)非法指令(4)ldd应用程序或者库,查看运行所依赖的库时,直接报段错误二.问题逐个分析,得出解决方法:(1)找不到程序所依赖的Qt库version`Qt_5'

  8. ruby - Ruby 中允许 "p *1..10"打印出数字 1-10 的功能是什么? - 2

    require'pp'p*1..10这会打印出1-10。为什么这么简洁?您还可以用它做什么? 最佳答案 它是“splat”运算符。它可用于分解数组和范围并在赋值期间收集值。这里收集赋值中的值:a,*b=1,2,3,4=>a=1b=[2,3,4]在此示例中,内部数组([3,4])中的值被分解并收集到包含数组中:a=[1,2,*[3,4]]=>a=[1,2,3,4]您可以定义将参数收集到数组中的函数:deffoo(*args)pargsendfoo(1,2,"three",4)=>[1,2,"three",4]

  9. ruby - 现代计算机的功能是否不足以处理字符串而无需使用符号(在 Ruby 中) - 2

    我读过的关于Ruby符号的每一篇文章都在谈论符号相对于字符串的效率。但是,这不是1970年代。我的电脑可以处理一些额外的垃圾收集。我错了吗?我拥有最新最好的奔腾双核处理器和4GBRAM。我认为这应该足以处理一些字符串。 最佳答案 您的计算机可能能够处理“一点点额外的垃圾收集”,但是当“一点点”发生在运行数百万次的内部循环中时呢?如果它在内存有限的嵌入式系统上运行呢?有很多地方你可以随意使用字符串,但在某些地方你不能。这完全取决于上下文。 关于ruby-现代计算机的功能是否不足以处理字符串

  10. ruby - 如何在 Cucumber 的功能名称中使用空格 - 2

    我正在使用Windows并尝试运行一个现有的功能包,该功能包最初是在MacOS上构建的,这允许他们通过使用带空格的"\"来解决问题。我正在使用Ruby2.2.3和Cucumber。功能名称包含空格,我无法更改它。我尝试使用""和''来绕过空白,但每次都有同样的问题。这是问题的一个例子。如果我运行:cucumberfeatures/'Namecontainingwhitespaces.feature'它工作正常。但是当我运行时:cucumber-pmy_profile和cucumber.yml包含:my_profile:features/'Namecontainingwhitespace

随机推荐