Spring 源码阅读 46:@EnableAspectJAutoProxy 注解分析

简介: 本文分析了通过@EnableAspectJAutoProxy注解开启 Spring AOP 支持的原理。

基于 Spring Framework v5.2.6.RELEASE

前置知识:Spring AOP 扫盲 概念篇 & 应用篇

概述

Spring 源码阅读系列,从本篇开始分析 Spring AOP 相关的重要源码,IOC 和 AOP 通常被认为是 Spring 框架的两大特性,但其实我认为 Spring 最主要的特性只有 IOC,在 Spring 中,AOP 是一个基于 IOC 实现的,并且 AOP 在 Spring 应用中发挥的作用本质上都是为了更好地组织和维护 IOC 容器中的 Bean。但这并不代表 AOP 特性的重要性不如 IOC,在绝大多数 Spring 应用中,AOP 特性都发挥着巨大的作用。

在目前的 Spring 应用开发中,Spring 推荐使用 Aspect 注解来配置切面,并通过在配置类上添加@EnableAspectJAutoProxy注解来开启 Spring 对 Aspect 注解的支持。因此,我们的代码分析从@EnableAspectJAutoProxy注解开始。

@EnableAspectJAutoProxy 注解分析

我们先看一下@EnableAspectJAutoProxy注解的定义。

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented@Import(AspectJAutoProxyRegistrar.class)
public@interfaceEnableAspectJAutoProxy {
booleanproxyTargetClass() defaultfalse;
booleanexposeProxy() defaultfalse;
}

在这个注解的源码注释中,对它进行了非常简洁精确的介绍。

Enables support for handling components marked with AspectJ's @Aspect annotation, similar to functionality found in Spring's  XML element

翻译过来就是,开启了 Spring 对 AspectJ 的@Aspect注解的组件的处理,类似于 Spring XML 配置文件中的

在注解的定义中,有一个@Import元注解,并在其value属性给了AspectJAutoProxyRegistrar.class这个值,关于 AspectJAutoProxyRegistrar 这个类型,会在之后分析道。

在使用@EnableAspectJAutoProxy注解的时候,需要将其标记在一个配置类上,也就是与@Configuration一起使用,像这样:

@Configuration@EnableAspectJAutoProxypublicclassSpringConfig {
}

下面我们先看一下@EnableAspectJAutoProxy是如何被 Spring 解析到的。

@EnableAspectJAutoProxy 配置的解析过程

@EnableAspectJAutoProxy注解的解析,涉及到 Spring 上下文创建时对所有配置类的解析过程,之前已经写过了两篇相关的源码分析文章,以下是链接:

@Configuration 配置的解析和处理:第一篇第二篇

下面,我们将与本文的内容相关的部分再摘取出来进行介绍。

基于注解配置的 Spring 上下文在启动的时候,会注册一个 ConfigurationClassPostProcessor 后处理器,它是一个 BeanFactory 的后处理器,在它的postProcessBeanDefinitionRegistry方法中,会从已经注册到容器中的所有 BeanDefinition 中,找出其对应的 Bean 类型被@Configuration注解标记的 BeanDefinition,并将其类中的信息封装成 ConfigurationClass 类型的对象,这些 ConfigurationClass 对象就是所有的配置类的元信息。

然后,Spring 会通过一个配置类解析器对这些配置类的元信息进行解析,解析的过程比较复杂,其中,与本文内容相关的逻辑,我们可以找到 ConfigurationClassParser 的doProcessConfigurationClass方法,这个方法是对单个配置类信息的解析,其中有这样一行代码:

// Process any @Import annotationsprocessImports(configClass, sourceClass, getImports(sourceClass), filter, true);

从注释中可以看到,这行代码的作用是处理配置类信息configClass上所有的@Import注解,我们从这儿入手开始看。我们找到这个方法的定义。

privatevoidprocessImports(ConfigurationClassconfigClass, SourceClasscurrentSourceClass,
Collection<SourceClass>importCandidates, Predicate<String>exclusionFilter,
booleancheckForCircularImports) {
// ...}

在源码中,这个方法没有参数的注释,不过大概可以从名字中分析出来,其中,Collection importCandidates就是当前配置类通过@Import注解导入的其他类型的信息,这个参数对应到调用方法的源码中,就是getImports(sourceClass)

接下来,我们找到getImports方法。

// org.springframework.context.annotation.ConfigurationClassParser#getImportsprivateSet<SourceClass>getImports(SourceClasssourceClass) throwsIOException {
Set<SourceClass>imports=newLinkedHashSet<>();
Set<SourceClass>visited=newLinkedHashSet<>();
collectImports(sourceClass, imports, visited);
returnimports;
}

方法中主要的逻辑由collectImports方法执行,我们接着进入collectImports方法。

// org.springframework.context.annotation.ConfigurationClassParser#collectImportsprivatevoidcollectImports(SourceClasssourceClass, Set<SourceClass>imports, Set<SourceClass>visited)
throwsIOException {
if (visited.add(sourceClass)) {
for (SourceClassannotation : sourceClass.getAnnotations()) {
StringannName=annotation.getMetadata().getClassName();
if (!annName.equals(Import.class.getName())) {
collectImports(annotation, imports, visited);
         }
      }
imports.addAll(sourceClass.getAnnotationAttributes(Import.class.getName(), "value"));
   }
}

方法的逻辑比较简单,遍历当前配置类的类型信息中所有的注解,对于除@Import以外的注解,递归调用collectImports方法,并将当前类型信息中@Import注解的value值中所有的类型添加到imports集合中,imports集合就是getImports方法最终的结果。

对应到我们要分析的@EnableAspectJAutoProxy注解,虽然它不是@Import,但是它有一个@Import元注解,并且其中配置了 AspectJAutoProxyRegistrar 类型作为@Importvalue属性。因此,被上述的getImports处理之后,AspectJAutoProxyRegistrar 类型信息就会被添加到imports集合中。

我们再回到处理配置类的@Import注解信息的方法processImports,进入方法的源码。

image.png

方法比较长,重要的流程就是中间的for循环部分,通过遍历参数传入的imports集合,对其中元素的类型,根据不同的类型做不同的处理。

单独针对我们要分析的@EnableAspectJAutoProxy注解, 这里的imports集合中,会包含它的@Import元注解中配置的 AspectJAutoProxyRegistrar 类型。 我们找到这个类。

image.png

以上就是它的全部继承关系,因此,当它被遍历到的时候,执行的是下面这段逻辑。

elseif (candidate.isAssignable(ImportBeanDefinitionRegistrar.class)) {
// Candidate class is an ImportBeanDefinitionRegistrar ->// delegate to it to register additional bean definitionsClass<?>candidateClass=candidate.loadClass();
ImportBeanDefinitionRegistrarregistrar=ParserStrategyUtils.instantiateClass(candidateClass, ImportBeanDefinitionRegistrar.class,
this.environment, this.resourceLoader, this.registry);
configClass.addImportBeanDefinitionRegistrar(registrar, currentSourceClass.getMetadata());
}

这里的逻辑非常简单。首先,加载到类型信息;然后通过 ParserStrategyUtils 的instantiateClass方法,创建一个实例,其中除了通过反射创建对象之外,还执行了感知接口的方法;最后,将创建好的实例创建到当前正在解析的配置类元信息的importBeanDefinitionRegistrars集合中。

到这儿,针对@Import注解导入的类型就解析完了。

AspectJAnnotationAutoProxyCreator 的注册

再说回到上一小节最初提到的 ConfigurationClassPostProcessor 后处理器,以上的解析过程都是在后处理器执行处理方法的时候完成的,在解析完所有扫描到的配置类之后,Spring 还会将之前扫描到的配置类中通过注解配置的 Bean 信息注册到容器中,比如通过@Bean注解配置 Bean 信息的方法,这其中也包括在配置类上通过@Import注解导入的类。注意,这里所说的@Import注解,同时包括标记了@Import元注解的注解,比如我们分析的@EnableAspectJAutoProxy注解。

对这些 Bean 的注册,是通过调用 ConfigurationClassBeanDefinitionReader 的loadBeanDefinitions方法完成的,我们找到这个方法。

// org.springframework.context.annotation.ConfigurationClassBeanDefinitionReader#loadBeanDefinitionspublicvoidloadBeanDefinitions(Set<ConfigurationClass>configurationModel) {
TrackedConditionEvaluatortrackedConditionEvaluator=newTrackedConditionEvaluator();
for (ConfigurationClassconfigClass : configurationModel) {
loadBeanDefinitionsForConfigurationClass(configClass, trackedConditionEvaluator);
   }
}

这里对所有需要进行处理的配置类进行了遍历,并通过loadBeanDefinitionsForConfigurationClass方法分别处理。

// org.springframework.context.annotation.ConfigurationClassBeanDefinitionReader#loadBeanDefinitionsForConfigurationClassprivatevoidloadBeanDefinitionsForConfigurationClass(
ConfigurationClassconfigClass, TrackedConditionEvaluatortrackedConditionEvaluator) {
if (trackedConditionEvaluator.shouldSkip(configClass)) {
StringbeanName=configClass.getBeanName();
if (StringUtils.hasLength(beanName) &&this.registry.containsBeanDefinition(beanName)) {
this.registry.removeBeanDefinition(beanName);
      }
this.importRegistry.removeImportingClass(configClass.getMetadata().getClassName());
return;
   }
if (configClass.isImported()) {
registerBeanDefinitionForImportedConfigurationClass(configClass);
   }
for (BeanMethodbeanMethod : configClass.getBeanMethods()) {
loadBeanDefinitionsForBeanMethod(beanMethod);
   }
loadBeanDefinitionsFromImportedResources(configClass.getImportedResources());
loadBeanDefinitionsFromRegistrars(configClass.getImportBeanDefinitionRegistrars());
}

在上一小节的配置解析原理分析中,我们知道,我们分析的@EnableAspectJAutoProxy注解中导入的类 AspectJAutoProxyRegistrar 的信息,被添加到了配置类元信息的importBeanDefinitionRegistrars集合中。而上述方法的源码中最后一行代码,正是通过loadBeanDefinitionsFromRegistrars方法来处理这个集合中的信息的。我们进入方法的源码。

// org.springframework.context.annotation.ConfigurationClassBeanDefinitionReader#loadBeanDefinitionsFromRegistrarsprivatevoidloadBeanDefinitionsFromRegistrars(Map<ImportBeanDefinitionRegistrar, AnnotationMetadata>registrars) {
registrars.forEach((registrar, metadata) ->registrar.registerBeanDefinitions(metadata, this.registry, this.importBeanNameGenerator));
}

这个方法的逻辑非常简单,就是遍历这个集合中的所有元素,调用他们的registerBeanDefinitions方法。因为这个集合里存放的元素都是实现了 ImportBeanDefinitionRegistrar 接口的对象,而registerBeanDefinitions正式这个接口中定义的方法。

我们现在去到 AspectJAutoProxyRegistrar 类的源码中,看看它的registerBeanDefinitions方法中,都执行了什么。

// org.springframework.context.annotation.AspectJAutoProxyRegistrarclassAspectJAutoProxyRegistrarimplementsImportBeanDefinitionRegistrar {
/*** 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.*/@OverridepublicvoidregisterBeanDefinitions(
AnnotationMetadataimportingClassMetadata, BeanDefinitionRegistryregistry) {
AopConfigUtils.registerAspectJAnnotationAutoProxyCreatorIfNecessary(registry);
AnnotationAttributesenableAspectJAutoProxy=AnnotationConfigUtils.attributesFor(importingClassMetadata, EnableAspectJAutoProxy.class);
if (enableAspectJAutoProxy!=null) {
if (enableAspectJAutoProxy.getBoolean("proxyTargetClass")) {
AopConfigUtils.forceAutoProxyCreatorToUseClassProxying(registry);
         }
if (enableAspectJAutoProxy.getBoolean("exposeProxy")) {
AopConfigUtils.forceAutoProxyCreatorToExposeProxy(registry);
         }
      }
   }
}

其中,最重要的一行代码就是:

AopConfigUtils.registerAspectJAnnotationAutoProxyCreatorIfNecessary(registry);

我们进入这个方法,并顺着方法调用,一直找到执行具体逻辑的位置。

// org.springframework.aop.config.AopConfigUtils#registerAspectJAnnotationAutoProxyCreatorIfNecessary(org.springframework.beans.factory.support.BeanDefinitionRegistry)@NullablepublicstaticBeanDefinitionregisterAspectJAnnotationAutoProxyCreatorIfNecessary(BeanDefinitionRegistryregistry) {
returnregisterAspectJAnnotationAutoProxyCreatorIfNecessary(registry, null);
}
// org.springframework.aop.config.AopConfigUtils#registerAspectJAnnotationAutoProxyCreatorIfNecessary(org.springframework.beans.factory.support.BeanDefinitionRegistry, java.lang.Object)@NullablepublicstaticBeanDefinitionregisterAspectJAnnotationAutoProxyCreatorIfNecessary(
BeanDefinitionRegistryregistry, @NullableObjectsource) {
returnregisterOrEscalateApcAsRequired(AnnotationAwareAspectJAutoProxyCreator.class, registry, source);
}
// org.springframework.beans.factory.config.BeanDefinition@NullableprivatestaticBeanDefinitionregisterOrEscalateApcAsRequired(
Class<?>cls, BeanDefinitionRegistryregistry, @NullableObjectsource) {
Assert.notNull(registry, "BeanDefinitionRegistry must not be null");
if (registry.containsBeanDefinition(AUTO_PROXY_CREATOR_BEAN_NAME)) {
BeanDefinitionapcDefinition=registry.getBeanDefinition(AUTO_PROXY_CREATOR_BEAN_NAME);
if (!cls.getName().equals(apcDefinition.getBeanClassName())) {
intcurrentPriority=findPriorityForClass(apcDefinition.getBeanClassName());
intrequiredPriority=findPriorityForClass(cls);
if (currentPriority<requiredPriority) {
apcDefinition.setBeanClassName(cls.getName());
         }
      }
returnnull;
   }
RootBeanDefinitionbeanDefinition=newRootBeanDefinition(cls);
beanDefinition.setSource(source);
beanDefinition.getPropertyValues().add("order", Ordered.HIGHEST_PRECEDENCE);
beanDefinition.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
registry.registerBeanDefinition(AUTO_PROXY_CREATOR_BEAN_NAME, beanDefinition);
returnbeanDefinition;
}

从以上三个方法的逻辑中可以看出,这里主要执行的是一个 BeanDefinition 的注册,它的类型是 AnnotationAwareAspectJAutoProxyCreator。要了解这个类型的作用,就从它的继承关系入手。

image.png

可以看到,它其实是一个 BeanPostProcessor,可以在 Bean 的生命周期的一些特定节点执行处理方法。而且,从它的父类 AbstractAutoProxyCreator 的类注释中,可以看到一句介绍:

org.springframework.beans.factory.config.BeanPostProcessor implementation that wraps each eligible bean with an AOP proxy, delegating to specified interceptors before invoking the bean itself.

可以看出,它的作用是通过 AOP 代理包装符合条件的 Bean,使得对 Bean 本身的调用被委托给特定的拦截器。而且从这句话中可以知道,Spring AOP 是通过代理来实现的。

至此,通过@EnableAspectJAutoProxy注解开启 Spring AOP 支持的原理就分析完了。

总结

本文分析了通过@EnableAspectJAutoProxy注解开启 Spring AOP 支持的原理。其中包括了配置类的解析,过程中对@EnableAspectJAutoProxy的处理,以及通过使用注解导入的 AspectJAutoProxyRegistrar 向容器中注册负责创建 AOP 代理的后处理器 AnnotationAwareAspectJAutoProxyCreator 的过程。

关于 AnnotationAwareAspectJAutoProxyCreator 的工作原理,会在之后的源码分析文章中作详细的分析。

目录
相关文章
|
6天前
|
运维 Java 程序员
Spring5深入浅出篇:基于注解实现的AOP
# Spring5 AOP 深入理解:注解实现 本文介绍了基于注解的AOP编程步骤,包括原始对象、额外功能、切点和组装切面。步骤1-3旨在构建切面,与传统AOP相似。示例代码展示了如何使用`@Around`定义切面和执行逻辑。配置中,通过`@Aspect`和`@Around`注解定义切点,并在Spring配置中启用AOP自动代理。 进一步讨论了切点复用,避免重复代码以提高代码维护性。通过`@Pointcut`定义通用切点表达式,然后在多个通知中引用。此外,解释了AOP底层实现的两种动态代理方式:JDK动态代理和Cglib字节码增强,默认使用JDK,可通过配置切换到Cglib
|
1天前
|
Java 应用服务中间件 测试技术
深入探索Spring Boot Web应用源码及实战应用
【5月更文挑战第11天】本文将详细解析Spring Boot Web应用的源码架构,并通过一个实际案例,展示如何构建一个基于Spring Boot的Web应用。本文旨在帮助读者更好地理解Spring Boot的内部工作机制,以及如何利用这些机制优化自己的Web应用开发。
8 2
|
2天前
|
JSON 前端开发 Java
【JAVA进阶篇教学】第七篇:Spring中常用注解
【JAVA进阶篇教学】第七篇:Spring中常用注解
|
4天前
|
存储 前端开发 Java
Spring Boot自动装配的源码学习
【4月更文挑战第8天】Spring Boot自动装配是其核心机制之一,其设计目标是在应用程序启动时,自动配置所需的各种组件,使得应用程序的开发和部署变得更加简单和高效。下面是关于Spring Boot自动装配的源码学习知识点及实战。
13 1
|
5天前
|
JavaScript Java 开发者
Spring Boot中的@Lazy注解:概念及实战应用
【4月更文挑战第7天】在Spring Framework中,@Lazy注解是一个非常有用的特性,它允许开发者控制Spring容器的bean初始化时机。本文将详细介绍@Lazy注解的概念,并通过一个实际的例子展示如何在Spring Boot应用中使用它。
17 2
|
5天前
|
传感器 人工智能 前端开发
JAVA语言VUE2+Spring boot+MySQL开发的智慧校园系统源码(电子班牌可人脸识别)Saas 模式
智慧校园电子班牌,坐落于班级的门口,适合于各类型学校的场景应用,班级学校日常内容更新可由班级自行管理,也可由学校统一管理。让我们一起看看,电子班牌有哪些功能呢?
47 4
JAVA语言VUE2+Spring boot+MySQL开发的智慧校园系统源码(电子班牌可人脸识别)Saas 模式
|
6天前
|
前端开发 Java
SpringBoot之自定义注解参数校验
SpringBoot之自定义注解参数校验
16 2
|
12天前
|
Java Spring
springboot自带的@Scheduled注解开启定时任务
springboot自带的@Scheduled注解开启定时任务
|
13天前
|
设计模式 安全 Java
【初学者慎入】Spring源码中的16种设计模式实现
以上是威哥给大家整理了16种常见的设计模式在 Spring 源码中的运用,学习 Spring 源码成为了 Java 程序员的标配,你还知道Spring 中哪些源码中运用了设计模式,欢迎留言与威哥交流。
|
15天前
|
XML JSON Java
【SpringBoot】springboot常用注解
【SpringBoot】springboot常用注解