[Spring]@EnableAspectJAutoProxy原理解析

@EnableAspectJAutoProxy

@EnableAspectJAutoProxy注解是Spring AOP开启的标志,在启动类标记此注解,即可加载对应的切面类逻辑.此注解的ElementTypeTYPE,表示标记在类上。同时用@Retention(RetentionPolicy.RUNTIME)声明了注解在运行时得到保留。此外最重要的是使用了@Import(AspectJAutoProxyRegistrar.class)来注册AOP的

proxyTargetClass

@EnableAspectJAutoProxy支持处理标有AspectJ的@Aspect批注的组件,用户可以主动声明proxyTargetClass来指定Spring AOP使用哪种动态代理方式来创建代理类(默认使用基于实现接口的JDK动态代理方式).

  • 使用CGLIB动态代理来创建代理类
   @Configuration
   @EnableAspectJAutoProxy(proxyTargetClass=true)
   @ComponentScan("com.foo")
   public class AppConfig {
       // ...
   }

exposeProxy

为了解决一些由于代理引发的切面失效问题,Spring AOP在Spring 4.3.1后引入了AopContext类来将代理类的引用存储在ThreadLocal中,通过AopContext可以快速获取当前类的代理类.
默认为不支持,如果声明为true,即可使用AopContext获取代理类.

同时,为了使用AspectJ,需要确保当前jar仓库存在aspectjweaver.

通过@Import注册AspectJAutoProxyRegistrar

通常情况下,我们的启动类本身也是一个Bean,Spring支持使用@Import来导入一个没有标记任何Spring 注解的类来将该Java类注册成Spring的Bean.
@EnableAspectJAutoProxy注解正是通过@Import的方式来将AspectJAutoProxyRegistrar类注册成Spring的Bean,以便在容器解析切面类时派上用场.
那么AspectJAutoProxyRegistrar类的作用是什么?
我们从JavaDoc中可以看到这样一句话:

Registers an AnnotationAwareAspectJAutoProxyCreator against the current BeanDefinitionRegistry as appropriate based on a given @EnableAspectJAutoProxy annotation.
根据当前BeanDefinitionRegistry在适当的位置注册AnnotationAwareAspectJAutoProxyCreator。

UML

UML
  • ImportBeanDefinitionRegistrar: 用来导入一些特殊的BeanDefinition,Spring在处理@Configuration时,会去扫描是否有通过@Import标签导入的类,对ImportBeanDefinitionRegistrar这类接口,还会执行其中的registerBeanDefinitions方法.
  • AspectJAutoProxyRegistrar: 实现了ImportBeanDefinitionRegistrar接口,用来注册AspectJAnnotationAutoProxyCreator,也就是支持注解驱动(同时兼容XML)解析的AspectJ自动代理创建器.

AspectJAutoProxyRegistrar

class AspectJAutoProxyRegistrar implements ImportBeanDefinitionRegistrar {

    @Override
    public void registerBeanDefinitions(
            AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
        // 向容器注册AspectJAnnotationAutoProxyCreator
        AopConfigUtils.registerAspectJAnnotationAutoProxyCreatorIfNecessary(registry);

        AnnotationAttributes enableAspectJAutoProxy =
                AnnotationConfigUtils.attributesFor(importingClassMetadata, EnableAspectJAutoProxy.class);
        // 如果@EnableAspectJAutoProxy上存在标签内容
        if (enableAspectJAutoProxy != null) {
            // 如果proxyTargetClass为true,则强制指定AutoProxyCreator使用CGLIB进行代理
            if (enableAspectJAutoProxy.getBoolean("proxyTargetClass")) {
                AopConfigUtils.forceAutoProxyCreatorToUseClassProxying(registry);
            }
            // 是否开启exposeProxy特性
            if (enableAspectJAutoProxy.getBoolean("exposeProxy")) {
                AopConfigUtils.forceAutoProxyCreatorToExposeProxy(registry);
            }
        }
    }

}

这里关键的地方就是通过AopConfigUtils.registerAspectJAnnotationAutoProxyCreatorIfNecessary(registry);向容器注册AspectJAnnotationAutoProxyCreator.
随后解析@EnableAspectJAutoProxy注解上的元数据来决定是否开启上述我们讲到的proxyTargetClassexposeProxy特性.
为了了解registerBeanDefinitions方法的执行链路和调用时机,我们使用IDE的debug来查看调用栈分析执行流程.

  • refresh中激活后置处理器ConfigurationClassPostProcessor加载@Configuration上的元数据
refresh
  1. 首先容器会加载refresh方法.
  2. 执行invokeBeanFactoryPostProcessors(beanFactory);激活工厂级别的后置处理器.
  3. 由于启动类都是被@Configuration标记的,Spring会使用ConfigurationClassPostProcessor来解析被@Configuration的类.
  4. 使用ConfigurationClassBeanDefinitionReader来加载配置类解析成BeanDefinition.
  5. org.springframework.context.annotation.ConfigurationClassBeanDefinitionReader#loadBeanDefinitionsForConfigurationClass中,会执行loadBeanDefinitionsFromRegistrars(configClass.getImportBeanDefinitionRegistrars());这行代码,从代码的语义上我们可以大致可以猜出来,这是解析当前配置类上是否存在通过@Import导入的实现了ImportBeanDefinitionRegistrar的类,
AspectJAutoProxyRegistrar
  1. 回调registrar的registerBeanDefinitions方法.
  2. 执行AspectJAutoProxyRegistrar#registerBeanDefinitions方法.
  • AopConfigUtils#registerAspectJAnnotationAutoProxyCreatorIfNecessary

使用IDE跳进AopConfigUtils.registerAspectJAnnotationAutoProxyCreatorIfNecessary(registry);中,向下跳2层,来到AopConfigUtils#registerAspectJAnnotationAutoProxyCreatorIfNecessary方法中.

  • org.springframework.aop.config.AopConfigUtils#registerAspectJAnnotationAutoProxyCreatorIfNecessary(org.springframework.beans.factory.support.BeanDefinitionRegistry, java.lang.Object)
@Nullable
public static BeanDefinition registerAspectJAnnotationAutoProxyCreatorIfNecessary(
        BeanDefinitionRegistry registry, @Nullable Object source) {

    return registerOrEscalateApcAsRequired(AnnotationAwareAspectJAutoProxyCreator.class, registry, source);
}

这里我们发现了一个新的角色-AnnotationAwareAspectJAutoProxyCreator,该类就是Spring用来处理应用上下文中被@AspectJ注解标记的类的.继续进入registerOrEscalateApcAsRequired方法中看看注册流程.

  • org.springframework.aop.config.AopConfigUtils#registerOrEscalateApcAsRequired
private static BeanDefinition registerOrEscalateApcAsRequired(
        Class<?> cls, BeanDefinitionRegistry registry, @Nullable Object source) {

    Assert.notNull(registry, "BeanDefinitionRegistry must not be null");
    // 当前容器是否包含 org.springframework.aop.config.internalAutoProxyCreator
    if (registry.containsBeanDefinition(AUTO_PROXY_CREATOR_BEAN_NAME)) {
        BeanDefinition apcDefinition = registry.getBeanDefinition(AUTO_PROXY_CREATOR_BEAN_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;
    }
    // 将传入的class包装成BeanDefinition,然后注册到容器中,并讲其order的执行顺序调整为最优  
    // 在aop中,这里会注册AnnotationAwareAspectJAutoProxyCreator.class这个类
    RootBeanDefinition beanDefinition = new RootBeanDefinition(cls);
    beanDefinition.setSource(source);
    beanDefinition.getPropertyValues().add("order", Ordered.HIGHEST_PRECEDENCE);
    beanDefinition.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
    registry.registerBeanDefinition(AUTO_PROXY_CREATOR_BEAN_NAME, beanDefinition);
    return beanDefinition;
}

这里的逻辑较为清晰.
首先查看当前容器中是否包含org.springframework.aop.config.internalAutoProxyCreator的BeanDefiniton.
如果没有,将传入的class(在此处传入了AnnotationAwareAspectJAutoProxyCreator.class)包装成RootBeanDefinition,然后注册到容器中.

  • 设置proxyTargetClass与exposeProxy

这个也是比较清晰的,这里简单讲一下.我们看一下如何设置proxyTargetClass即可,大体上设置proxyTargetClass与exposeProxy的逻辑都是相通的.

// 如果@EnableAspectJAutoProxy上存在标签内容
if (enableAspectJAutoProxy != null) {
    // 如果proxyTargetClass为true,则强制指定AutoProxyCreator使用CGLIB进行代理
    if (enableAspectJAutoProxy.getBoolean("proxyTargetClass")) {
        AopConfigUtils.forceAutoProxyCreatorToUseClassProxying(registry);
    }
    // 是否开启exposeProxy特性
    if (enableAspectJAutoProxy.getBoolean("exposeProxy")) {
        AopConfigUtils.forceAutoProxyCreatorToExposeProxy(registry);
    }
}

进入AopConfigUtils.forceAutoProxyCreatorToUseClassProxying(registry);

  • AopConfigUtils#forceAutoProxyCreatorToUseClassProxying
public static void forceAutoProxyCreatorToUseClassProxying(BeanDefinitionRegistry registry) {  
    // 如果容器中包含 org.springframework.aop.config.internalAutoProxyCreator
    if (registry.containsBeanDefinition(AUTO_PROXY_CREATOR_BEAN_NAME)) {  
        // 取出 org.springframework.aop.config.internalAutoProxyCreator的BeanDefinition
        BeanDefinition definition = registry.getBeanDefinition(AUTO_PROXY_CREATOR_BEAN_NAME);  
        // 设置proxyTargetClass为true
        definition.getPropertyValues().add("proxyTargetClass", Boolean.TRUE);
    }
}

如果容器中包含名为org.springframework.aop.config.internalAutoProxyCreator的BeanDefinition,那么取出该BeanDefinition,设置proxyTargetClasstrue.
这里随便提及一下,Spring为了兼容不同的BeanDefinition持有不同的属性值,将它们都抽象成了MutablePropertyValues,所以definition.getPropertyValues().add("proxyTargetClass", Boolean.TRUE);就跟我们平时JavaBean中的set方法是一样的.

简单理解@Import和ImportBeanDefinitionRegistrar

下面我们通过两个用例来理解@ImportImportBeanDefinitionRegistrar

通过@Import导入类让Spring进行管理

public class NeedImportBean {

    public void doSomething(){
        Logger.getGlobal().info("Through @Import registry to a bean ");
    }

}
  • 在启动类中将NeedImportBean导入
/**
 * @author jaymin
 * 2020/11/30 20:13
 */
@Configuration
@ComponentScan(value = "com.xjm")
@Import(NeedImportBean.class)
@EnableAspectJAutoProxy
public class ApplicationConfig {
    public static AnnotationConfigApplicationContext getApplicationContext() {
        return new AnnotationConfigApplicationContext(ApplicationConfig.class);
    }
}
  • 对NeedImportBean做getBean
public class BeanFactoryDemo {
    public static void main(String[] args) throws Exception {
        AnnotationConfigApplicationContext applicationContext = ApplicationConfig.getApplicationContext();
        NeedImportBean needImportBean = applicationContext.getBean(NeedImportBean.class);
    }
}
  • 输出结果
result

可以看到,通过@Import可以将没有被Spring注解标记的类进行注册,注册后即可当成普通的Bean来使用。

实现ImportBeanDefinitionRegistrar

现在我们换一种方式,让NeedImportBean实现ImportBeanDefinitionRegistrar接口,然后验证两个点:

  1. 是否会回调registerBeanDefinitions方法。
  2. 通过getBean是否能获取NeedImportBean
public class NeedImportBean implements ImportBeanDefinitionRegistrar{

    @Override
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
        Logger.getGlobal().info("Through implements ImportBeanDefinitionRegistrar and @Import to callback me.");
    }

    public void doSomething(){
        Logger.getGlobal().info("Through @Import registry to a bean ");
    }

}
  • Result

测试的方法于上面一致.这里不重复贴了.

1
2

这里,我们验证了:

  1. 实现了ImportBeanDefinitionRegistrar接口后会执行回调
  2. 实现了ImportBeanDefinitionRegistrar接口后,通过@Import并不会注册成Bean.

扩展阅读

在搜寻资料的过程中,发现此文章对ImportBeanDefinitionRegistrar解析的比较透彻.有兴趣可以阅读一下:
点我前往

总结

总结
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 156,907评论 4 360
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 66,546评论 1 289
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 106,705评论 0 238
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,624评论 0 203
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 51,940评论 3 285
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,371评论 1 210
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,672评论 2 310
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,396评论 0 195
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,069评论 1 238
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,350评论 2 242
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,876评论 1 256
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,243评论 2 251
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,847评论 3 231
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,004评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,755评论 0 192
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,378评论 2 269
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,266评论 2 259

推荐阅读更多精彩内容

  • 1、AOP核心概念 1)、横切关注点(对哪些方法进行切入) 对哪些方法进行拦截,拦截后怎么处理,这些关注点称之为横...
    steven_it阅读 263评论 0 0
  • 1、Spring AOP样例 简单介绍怎么样基于Spring实现AOP编程(注解方式在目标对象方法中织入通知方法)...
    小王学java阅读 138评论 0 0
  • AOP的核心思想就是“将应用程序中的商业逻辑同对其提供支持的通用服务进行分离。” 实现AOP的技术,主要分为两大类...
    边学边记阅读 292评论 0 0
  • Spring 源码解析 第一章为源码解析。 第二章为实现一个简单的 IOC 容器。 第三章进阶 Spring 插件...
    当年明月_3025阅读 1,915评论 0 2
  • 推荐指数: 6.0 书籍主旨关键词:特权、焦点、注意力、语言联想、情景联想 观点: 1.统计学现在叫数据分析,社会...
    Jenaral阅读 5,658评论 0 5