SpringBoot之集成SpringAOP分析
Posted 木叶之荣
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了SpringBoot之集成SpringAOP分析相关的知识,希望对你有一定的参考价值。
我们在之前的文章中简单的分析过SpringAOP和Spring的整合过程(Spring系列之Spring框架和SpringAOP集成过程分析(十)),我们在这篇文章中简单的分析一下SpringBoot整个SpringAOP的过程。
如果我们要在SpringBoot中使用SpringAOP我们需要哪些准备步骤呢?就一步:在你的应用中引入SpringBoot提供的aop-starter即可:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
为什么我们这样操作之后就可以在SpringBoot中畅快的使用SpringAOP了呢?我们在说SpringBoot的时候,总会念念不忘它自动注入功能的强大,而我们之所以可以在SpringBoot中如果简单的使用SpringAOP,也是依赖于此特性。
在SpringBoot的autoconfigure中有这样一个类:AopAutoConfiguration。从这个类的名字我们可以推测出这个类应该是用来完成AOP自动注入的。我们来看一下这个类的内容:
@Configuration
@ConditionalOnClass( EnableAspectJAutoProxy.class, Aspect.class, Advice.class,
AnnotatedElement.class )
@ConditionalOnProperty(prefix = "spring.aop", name = "auto", havingValue = "true", matchIfMissing = true)
public class AopAutoConfiguration
@Configuration
@EnableAspectJAutoProxy(proxyTargetClass = false)
@ConditionalOnProperty(prefix = "spring.aop", name = "proxy-target-class", havingValue = "false", matchIfMissing = false)
public static class JdkDynamicAutoProxyConfiguration
@Configuration
@EnableAspectJAutoProxy(proxyTargetClass = true)
@ConditionalOnProperty(prefix = "spring.aop", name = "proxy-target-class", havingValue = "true", matchIfMissing = true)
public static class CglibAutoProxyConfiguration
从这个类中我们可以看到如下内容:
- 这个类上被添加了@Configuration注解。
- 这个类上添加了@ConditionalOnClass注解。说明这个类要想生效需要满足ConditionalOnClass中的条件,即需要在Classpath中存在EnableAspectJAutoProxy.class, Aspect.class, Advice.class,AnnotatedElement.class。
- 这个类上添加了@ConditionalOnProperty这个注解,这个注解限制了这个类要生效的条件,即spring.aop.auto这个属性为true。
- 这个类中定义了JdkDynamicAutoProxyConfiguration和CglibAutoProxyConfiguration这两个内部类,这两个内部类上同样有@Configuration和@ConditionalOnProperty注解,并且有@EnableAspectJAutoProxy这个注解,这个注解是AOP自动注入一个很关键的注解,我们在下面会分析。
那么AopAutoConfiguration这个类是在什么时候被加载的呢?它的加载过程又是怎么样的呢?
整个的加载过程比较复杂,我们这里只说几个重点的部分:在我们的SpringBoot的应用启动类上,我们通常会添加@SpringBootApplication这个注解,在这个注解上面又使用了@EnableAutoConfiguration这个注解,在@EnableAutoConfiguration这个注解上面又使用了@Import(AutoConfigurationImportSelector.class)这个注解。在SpringBoot启动Spring容器的时候,就会解析到我们上面提到的@Import(AutoConfigurationImportSelector.class)这个注解,在@Import这个注解中有如下说明:
public @interface Import
/**
* @link Configuration, @link ImportSelector, @link ImportBeanDefinitionRegistrar
* or regular component classes to import.
*/
Class<?>[] value();
翻译过来是我们可以在@Import这个注解中指定ImportSelector的实现类,ImportBeanDefinitionRegistrar的实现类,或者其他我们需要引入的Component类。而AutoConfigurationImportSelector就是一个ImportSelector的实现类,所以在SpringBoot的启动过程中会实例化AutoConfigurationImportSelector并调用它的selectImports方法类进行一些特殊的处理。那么我们就去AutoConfigurationImportSelector#selectImports中一探究竟。首先我们先看一下AutoConfigurationImportSelector的类图:
AutoConfigurationImportSelector实现了 DeferredImportSelector, BeanClassLoaderAware, ResourceLoaderAware,BeanFactoryAware, EnvironmentAware, Ordered这几个接口,这几个结论的作用就先不一一说明了,注意的是它实现了ImportSelector这个接口。它的selectImports方法内容如下:
@Override
public String[] selectImports(AnnotationMetadata annotationMetadata)
if (!isEnabled(annotationMetadata))
return NO_IMPORTS;
try
//这个方法的作用是从META-INF/spring-autoconfigure-metadata.properties中加载一些配置项
AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader
.loadMetadata(this.beanClassLoader);
//根据传进来的注解元数据获取属性信息
AnnotationAttributes attributes = getAttributes(annotationMetadata);
//从spring.factories中加载key为org.springframework.boot.autoconfigure.EnableAutoConfiguration的配置信息
List<String> configurations = getCandidateConfigurations(annotationMetadata,
attributes);
//去掉混合的配置信息 用LinkedHashSet做的
configurations = removeDuplicates(configurations);
//配置项排序
configurations = sort(configurations, autoConfigurationMetadata);
//满足annotationMetadata中的exclude方法条件的类
Set<String> exclusions = getExclusions(annotationMetadata, attributes);
checkExcludedClasses(configurations, exclusions);
//移除掉不符合条件的类
configurations.removeAll(exclusions);
//过滤掉不符合条件的类 主要逻辑是configurations中的元素+.ConditionalOnClass为key,然后
//判断其对应的value是否可以被当前类加载器加载
//如org.springframework.boot.autoconfigure.aop.AopAutoConfiguration.ConditionalOnClass=
//org.springframework.context.annotation.EnableAspectJAutoProxy,
//org.aspectj.lang.annotation.Aspect,
//org.aspectj.lang.reflect.Advice,org.aspectj.weaver.AnnotatedElement
//则判断EnableAspectJAutoProxy Aspect AnnotatedElement这三个类是否能被当前类加载器加载
//如果能则 AopAutoConfiguration被保留 否则被过滤掉
//当我们在引入spring-boot-starter-aop这个Maven依赖的时候,SpringAOP相关的jar就被引入进来了
//所以在最终的结果中可能会有AopAutoConfiguration这个类的
configurations = filter(configurations, autoConfigurationMetadata);
fireAutoConfigurationImportEvents(configurations, exclusions);
//返回最终符合条件的EnableAutoConfiguration对应的类
return StringUtils.toStringArray(configurations);
catch (IOException ex)
throw new IllegalStateException(ex);
在调用selectImports时方法的调用链信息如下所示:
我们在上面说过通过调用selectImports这个方法是会获取到AopAutoConfiguration这个类的,那么接下来的处理过程又是怎么样的呢?我们根据方法的调用链进行分析,接下来会调用org.springframework.context.annotation.ConfigurationClassParser#processImports这个方法。这个方法的信息我们先不具体的分析了,先聚焦关注下面这一段代码:
this.importStack.registerImport(currentSourceClass.getMetadata(),candidate.getMetadata().getClassName());
processConfigurationClass(candidate.asConfigClass(configClass));
具体要看的是processConfigurationClass这个方法,processConfigurationClass这个方法在SpringBoot启动的过程中是要被反复递归调用的,在这个方法中主要调用了doProcessConfigurationClass这个方法,这是一个很关键很关键的一个方法,我们以后要专门分析一下。
protected void processConfigurationClass(ConfigurationClass configClass) throws IOException
//如果当前的configClass不满足被加载的条件的话,会进行跳过 具体的验证逻辑很多 主要是Conditional相关的内容 以后慢慢分析
if (this.conditionEvaluator.shouldSkip(configClass.getMetadata(), ConfigurationPhase.PARSE_CONFIGURATION))
return;
//判断configClass是否已经被加载过了
ConfigurationClass existingClass = this.configurationClasses.get(configClass);
if (existingClass != null)
if (configClass.isImported())
if (existingClass.isImported())
existingClass.mergeImportedBy(configClass);
// Otherwise ignore new imported config class; existing non-imported class overrides it.
return;
else
// Explicit bean definition found, probably replacing an import.
// Let's remove the old one and go with the new one.
this.configurationClasses.remove(configClass);
this.knownSuperclasses.values().removeIf(configClass::equals);
// Recursively process the configuration class and its superclass hierarchy.
SourceClass sourceClass = asSourceClass(configClass);
do
//这是一个递归调用的过程
sourceClass = doProcessConfigurationClass(configClass, sourceClass);
while (sourceClass != null);
this.configurationClasses.put(configClass, configClass);
在调用doProcessConfigurationClass的时候会调用processMemberClasses这个方法,processMemberClasses主要是用来处理一个类的内部类或者接口的。其内容如下:
private void processMemberClasses(ConfigurationClass configClass, SourceClass sourceClass) throws IOException
//获取sourceClass所代表的类的内部类和接口 有点绕。。。 configClass和sourceClass都是对所以解析的类做了包装
Collection<SourceClass> memberClasses = sourceClass.getMemberClasses();
if (!memberClasses.isEmpty())
List<SourceClass> candidates = new ArrayList<>(memberClasses.size());
for (SourceClass memberClass : memberClasses)
//判断内部类是否满足所以解析的条件 如这个内部类上是否有@Configuration注解或者
//是否有Component ComponentScan Import ImportResource注解或者其方法是否有@Bean注解
if (ConfigurationClassUtils.isConfigurationCandidate(memberClass.getMetadata()) &&
!memberClass.getMetadata().getClassName().equals(configClass.getMetadata().getClassName()))
candidates.add(memberClass);
OrderComparator.sort(candidates);
for (SourceClass candidate : candidates)
if (this.importStack.contains(configClass))
this.problemReporter.error(new CircularImportProblem(configClass, this.importStack));
else
this.importStack.push(configClass);
try
//这里又调用了processConfigurationClass方法,是一个递归分析的过程 很绕
processConfigurationClass(candidate.asConfigClass(configClass));
finally
this.importStack.pop();
在下篇文章中做个总结。
以上是关于SpringBoot之集成SpringAOP分析的主要内容,如果未能解决你的问题,请参考以下文章
Spring系列之Spring框架和SpringAOP集成过程分析
Spring系列之Spring框架和SpringAOP集成过程分析