我们在之前的文章中简单的分析过SpringAOP和Spring的整合过程(Spring系列之Spring框架和SpringAOP集成过程分析(十)),我们在这篇文章中简单的分析一下SpringBoot整个SpringAOP的过程。
如果我们要在SpringBoot中使用SpringAOP我们需要哪些准备步骤呢?就一步:在你的应用中引入SpringBoot提供的aop-starter即可:
org.springframework.boot
spring-boot-starter-aop
为什么我们这样操作之后就可以在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 {
}
}
从这个类中我们可以看到如下内容:
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();
}
}
}
}
}
在下篇文章中做个总结。