一、概述
在Spring Boot的开发过程中,我们想要在某一个场景下开发,只需要引入这个场景对应的starter,Spring Boot会自动加载相关依赖,配置好相关初始化参数,本文将从源码的角度解析Spring Boot自动配置的原理。从本文中,您将学习Spring Boot自动配置的核心原理、Spring、Spring Boot的一些核心注解,自动配置类的加载、注册的条件判断等。
二、源码解析
Spring Boot工程创建以后会默认生成一个*Application的启动类,这个类就是一个普通的包含Java main方法的类。在这个类中引入了@SpringBootApplication注解,自动配置的故事就是从这儿开始的。
1、@SpringBootApplication
这个注解是一个组合注解,源码如下:
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(
excludeFilters = {@Filter(
type = FilterType.CUSTOM,
classes = {TypeExcludeFilter.class}
), @Filter(
type = FilterType.CUSTOM,
classes = {AutoConfigurationExcludeFilter.class}
)}
)
public @interface SpringBootApplication {
/**
@AliasFor注解的作用是桥接到其他注解,该注解的属性中指定了所桥接的注解类。因为在@SpringBootApplication 定义的属性已经在其他注解中定义过了,这儿只是为了方便用户使用定义的组合注解。
**/
//排除指定的自动配置类
@AliasFor(
annotation = EnableAutoConfiguration.class
)
Class<?>[] exclude() default {};
//排除指定的自动配置类名
@AliasFor(
annotation = EnableAutoConfiguration.class
)
String[] excludeName() default {};
//扫描指定的包,用于激活@Component等注解类的初始化
@AliasFor(
annotation = ComponentScan.class,
attribute = "basePackages"
)
String[] scanBasePackages() default {};
//指定扫描的类,用于组件的初始化。
@AliasFor(
annotation = ComponentScan.class,
attribute = "basePackageClasses"
)
Class<?>[] scanBasePackageClasses() default {};
}
从源码可以看到,@SpringBootApplication是一个组合注解,包含了:@SpringBootConfiguration、
@EnableAutoConfiguration、@ComponentScan 三个注解。@SpringBootApplication注解的层级关系如图所示:
上图中很多注解都是在Spring中的基础注解,说明如下
①、@Configuration:标注在类上,用于定义配置类,可替换xml配置文件,被注解的类内部包含有一个或多个被@Bean注解的方法,这些方法将会被AnnotationConfigApplicationContext或AnnotationConfigWebApplicationContext类进行扫描,并用于构建bean定义,初始化Spring容器。
②、@Import:用来导入配置类或者一些需要前置加载的类。该注解的使用方法有三种:
a、带有@Configuration的配置类(4.2 版本之前只可以导入配置类,4.2版本之后 也可以导入 普通类)
b、ImportSelector 的实现
c、ImportBeanDefinitionRegistrar 的实现
由于篇幅原因,对@Import注解就不展开说明了。
③、@ComponentScan注解:根据定义的扫描路径,把符合扫描规则的类装配到spring容器中。
解释完Spring的基本注解后,我们来看一下@SpringBootConfiguration
@EnableAutoConfiguration注解。
2、@SpringBootConfiguration
@Configuration
public @interface SpringBootConfiguration {
}
通过以上源码可以发现,其实SpringBootConfiguration 只是标注了@Configuration,那么被SpringBootConfiguration 注解标注的类就是一个配置类。
3、@EnableAutoConfiguration
@EnableAutoConfiguration注解是开启自动配置的关键,源码如下:
@AutoConfigurationPackage
@Import({AutoConfigurationImportSelector.class})
public @interface EnableAutoConfiguration {
String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";
Class<?>[] exclude() default {};
String[] excludeName() default {};
}
我们通过源码可以看到,Spring Boot通过@Import注解给我们导入了AutoConfigurationImportSelector类。
在讲解AutoConfigurationImportSelector类之前呢,我们有必要先了解ImportSelector接口。该接口源码如下:
public interface ImportSelector {
String[] selectImports(AnnotationMetadata var1);
}
ImportSelector接口只提供了一个参数为AnnotationMetadata的方法,返回的结果为一个字符串数组。其中参数AnnotationMetadata内包含了被@Import注解的类的注解信息。在selectImports方法内可根据具体实现决定返回哪些配置类的全限定名,将结果以字符串数组的形式返回。
比如如下代码,就能够将TestC类加载到容器中:
public class Myclass implements ImportSelector {
@Override
public String[] selectImports(AnnotationMetadata annotationMetadata) {
return new String[]{"com.xwfintech.model.TestC"};
}
}
AutoConfigurationImportSelector正是采用这种方式。源码如下:
//DeferredImportSelector 是ImportSelector的子接口,它和ImportSelector的区别是,它会在所有@Configuration类加载完成后在去加载返回的配置类。而ImportSelector则是@Configuration类加载完成前在去加载返回的配置类。
public class AutoConfigurationImportSelector implements DeferredImportSelector, BeanClassLoaderAware, ResourceLoaderAware, BeanFactoryAware, EnvironmentAware, Ordered {
}
从上述代码可以看到AutoConfigurationImportSelector实现了DeferredImportSelector接口,那么它导入自动的配置类的源码必定在selectImports方法中。源码如下:
public String[] selectImports(AnnotationMetadata annotationMetadata) {
if (!this.isEnabled(annotationMetadata)) {
return NO_IMPORTS;
} else {
// 加载自动配置的元信息,该信息包含了各种自动配置类的过滤条件,配置文件为类路径中META-INF目录下的spring-autoconfigure-metadata.properties文件。该文件在spring-boot-autoconfigure包下
AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader.loadMetadata(this.beanClassLoader);
AnnotationAttributes attributes = this.getAttributes(annotationMetadata);
List<String> configurations = this.getCandidateConfigurations(annotationMetadata, attributes);
configurations = this.removeDuplicates(configurations);
Set<String> exclusions = this.getExclusions(annotationMetadata, attributes);
this.checkExcludedClasses(configurations, exclusions);
configurations.removeAll(exclusions);
configurations = this.filter(configurations, autoConfigurationMetadata);
this.fireAutoConfigurationImportEvents(configurations, exclusions);
// 返回符合条件的配置类的全限定名数组
return StringUtils.toStringArray(configurations);
}
}
上述代码分步骤讲解:
1、加载自动配置的元信息,该信息包含了各种自动配置类的过滤条件,配置文件为类路径中META-INF目录下的spring-autoconfigure-metadata.properties文件。该文件在spring-boot-autoconfigure包下
AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader.loadMetadata(this.beanClassLoader);
//AutoConfigurationMetadataLoader.loadMetadata()
public static AutoConfigurationMetadata loadMetadata(ClassLoader classLoader) {
return loadMetadata(classLoader, "META-INF/spring-autoconfigure-metadata.properties");
}
2、获取需要排除的类,我们知道在@SpringBootApplication上能够指定排除的类,像这种写法:@SpringBootApplication(exclude = MyConfig.class)
AnnotationAttributes attributes = this.getAttributes(annotationMetadata);
attributes结构如图所示:
3、从META-INF目录的spring.factories文件中,获取候选的自动配置类
List<String> configurations = this.getCandidateConfigurations(annotationMetadata, attributes);
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
List<String> configurations = SpringFactoriesLoader.loadFactoryNames(this.getSpringFactoriesLoaderFactoryClass(), this.getBeanClassLoader());
Assert.notEmpty(configurations, "No auto configuration classes found in META-INF/spring.factories. If you are using a custom packaging, make sure that file is correct.");
return configurations;
}
public static List<String> loadFactoryNames(Class<?> factoryClass, @Nullable ClassLoader classLoader) {
String factoryClassName = factoryClass.getName();
//调用loadSpringFactories方法获取自动配置的类
return (List)loadSpringFactories(classLoader).getOrDefault(factoryClassName, Collections.emptyList());
}
private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
MultiValueMap<String, String> result = (MultiValueMap)cache.get(classLoader);
if (result != null) {
return result;
} else {
try {
//获取系统资源
Enumeration<URL> urls = classLoader != null ? classLoader.getResources("META-INF/spring.factories") : ClassLoader.getSystemResources("META-INF/spring.factories");
LinkedMultiValueMap result = new LinkedMultiValueMap();
while(urls.hasMoreElements()) {
URL url = (URL)urls.nextElement();
UrlResource resource = new UrlResource(url);
Properties properties = PropertiesLoaderUtils.loadProperties(resource);
Iterator var6 = properties.entrySet().iterator();
while(var6.hasNext()) {
Entry<?, ?> entry = (Entry)var6.next();
List<String> factoryClassNames = Arrays.asList(StringUtils.commaDelimitedListToStringArray((String)entry.getValue()));
result.addAll((String)entry.getKey(), factoryClassNames);
}
}
cache.put(classLoader, result);
return result;
} catch (IOException var9) {
throw new IllegalArgumentException("Unable to load factories from location [META-INF/spring.factories]", var9);
}
}
}
从源码中我们可以看到,是从META-INF/spring.factories获取到了自动配置的类。
4、移除重复获取的自动配置类
configurations = this.removeDuplicates(configurations);
protected final <T> List<T> removeDuplicates(List<T> list) {
return new ArrayList(new LinkedHashSet(list));
}
这一步我们可以看到,其实就是使用LinkedHashSet去重。
5、获取需要排除的类的全类名。(需要排除的类是第二步获取的)
Set<String> exclusions = this.getExclusions(annotationMetadata, attributes);
6、将需要排除的类从候选的自动配置类中移除
configurations.removeAll(exclusions);
7、根据条件移除需要注册的自动配置类,(条件为第一步从spring-autoconfigure-metadata.properties文件中获得的)。
configurations = this.filter(configurations, autoConfigurationMetadata);
private List<String> filter(List<String> configurations, AutoConfigurationMetadata autoConfigurationMetadata) {
long startTime = System.nanoTime();
String[] candidates = StringUtils.toStringArray(configurations);
//定义一个标识位,标识指定位置的自动配置类,是否满足地导入条件
boolean[] skip = new boolean[candidates.length];
boolean skipped = false;
//从spring.factories中获取AutoConfigurationImportFilter类型的过滤器
//org.springframework.boot.autoconfigure.AutoConfigurationImportFilter=org.springframework.boot.autoconfigure.condition.OnClassCondition
Iterator var8 = this.getAutoConfigurationImportFilters().iterator();
while(var8.hasNext()) {
AutoConfigurationImportFilter filter = (AutoConfigurationImportFilter)var8.next();
this.invokeAwareMethods(filter);
//调用filter的match方法,设置标识位
boolean[] match = filter.match(candidates, autoConfigurationMetadata);
for(int i = 0; i < match.length; ++i) {
if (!match[i]) {
skip[i] = true;
skipped = true;
}
}
}
//如果所有的配置都符合条件,直接返回即可
if (!skipped) {
return configurations;
} else {
List<String> result = new ArrayList(candidates.length);
int numberFiltered;
//将符合条件的自动配置类加入结果集
for(numberFiltered = 0; numberFiltered < candidates.length; ++numberFiltered) {
if (!skip[numberFiltered]) {
result.add(candidates[numberFiltered]);
}
}
if (logger.isTraceEnabled()) {
numberFiltered = configurations.size() - result.size();
logger.trace("Filtered " + numberFiltered + " auto configuration class in " + TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startTime) + " ms");
}
return new ArrayList(result);
}
}
8、在完成了以上步骤的过滤、筛选之后,我们最终获得了要进行自动配置的类的集合,在将该集合返回之前,在AutoConfigurationImportSelector类中完成的最后一步操作就是相关事件的封装和广播,关于事件的封装和广播,本文就不展开详述了。
附加题 -_-:
从上一步中,我们可以清晰的看到,SpringBoot是如何过滤自动配置类的。其中调用了filter.match()方法去匹配。我们可以继续深入的看看match方法的运行原理,filter在配置中是org.springframework.boot.autoconfigure.condition.OnClassCondition类型。所以我们直接查看该类型的match方法即可:
public boolean[] match(String[] autoConfigurationClasses, AutoConfigurationMetadata autoConfigurationMetadata) {
ConditionEvaluationReport report = this.getConditionEvaluationReport();
//在此处判断候选的自动配置类是否满足条件
ConditionOutcome[] outcomes = this.getOutcomes(autoConfigurationClasses, autoConfigurationMetadata);
boolean[] match = new boolean[outcomes.length];
for(int i = 0; i < outcomes.length; ++i) {
match[i] = outcomes[i] == null || outcomes[i].isMatch();
if (!match[i] && outcomes[i] != null) {
this.logOutcome(autoConfigurationClasses[i], outcomes[i]);
if (report != null) {
report.recordConditionEvaluation(autoConfigurationClasses[i], this, outcomes[i]);
}
}
}
return match;
}
private ConditionOutcome[] getOutcomes(String[] autoConfigurationClasses, AutoConfigurationMetadata autoConfigurationMetadata) {
//将数组分为两个部分,如果是多核CPU,那么就使用多线程去处理,提升效率
int split = autoConfigurationClasses.length / 2;
//创建一个ThreadedOutcomesResolver类型的解析器,其实就是新开一个线程去处理
OnClassCondition.OutcomesResolver firstHalfResolver = this.createOutcomesResolver(autoConfigurationClasses, 0, split, autoConfigurationMetadata);
//使用标准的解析器,主线程去处理
OnClassCondition.OutcomesResolver secondHalfResolver = new OnClassCondition.StandardOutcomesResolver(autoConfigurationClasses, split, autoConfigurationClasses.length, autoConfigurationMetadata, this.beanClassLoader);
//获取到解析结果
ConditionOutcome[] secondHalf = secondHalfResolver.resolveOutcomes();
ConditionOutcome[] firstHalf = firstHalfResolver.resolveOutcomes();
ConditionOutcome[] outcomes = new ConditionOutcome[autoConfigurationClasses.length];
//使用系统类提供的拷贝方法,将结果拷贝到新的数组中
System.arraycopy(firstHalf, 0, outcomes, 0, firstHalf.length);
System.arraycopy(secondHalf, 0, outcomes, split, secondHalf.length);
return outcomes;
}
//在上一个方法中,我们看到Spring Boot使用StandardOutcomesResolver.resolveOutcomes()方法去匹配结果,该方法源码如下:
public ConditionOutcome[] resolveOutcomes() {
//调用getOutcomes获取返回值
return this.getOutcomes(this.autoConfigurationClasses, this.start, this.end, this.autoConfigurationMetadata);
}
//
private ConditionOutcome[] getOutcomes(String[] autoConfigurationClasses, int start, int end, AutoConfigurationMetadata autoConfigurationMetadata) {
ConditionOutcome[] outcomes = new ConditionOutcome[end - start];
for(int i = start; i < end; ++i) {
//获取自动配置类
String autoConfigurationClass = autoConfigurationClasses[i];
//根据自动配置类的类名+ConditionalOnClass,从条件筛选列表中获取对应的条件。比如:org.springframework.boot.autoconfigure.transaction.TransactionAutoConfiguration.ConditionalOnClass=org.springframework.transaction.PlatformTransactionManager,即:要想TransactionAutoConfiguration自动配置类生效,那么类路径下需要存在PlatformTransactionManager类。
Set<String> candidates = autoConfigurationMetadata.getSet(autoConfigurationClass, "ConditionalOnClass");
if (candidates != null) {
//继续调用getOutcome,获得返回结果,具体的判断逻辑
outcomes[i - start] = this.getOutcome(candidates);
}
}
return outcomes;
}
//具体的判断逻辑
private ConditionOutcome getOutcome(Set<String> candidates) {
try {
//调用OnClassCondition.MatchType.MISSING去判断
List<String> missing = OnClassCondition.this.getMatches(candidates, OnClassCondition.MatchType.MISSING, this.beanClassLoader);
if (!missing.isEmpty()) {
return ConditionOutcome.noMatch(ConditionMessage.forCondition(ConditionalOnClass.class, new Object[0]).didNotFind("required class", "required classes").items(Style.QUOTE, missing));
}
} catch (Exception var3) {
}
return null;
}
MISSING {
public boolean matches(String className, ClassLoader classLoader) {
return !OnClassCondition.MatchType.isPresent(className, classLoader);
}
};
//最终就是根据传入的类名,去加载这个类,如果加载成功,返回true,加载失败返回false
private static boolean isPresent(String className, ClassLoader classLoader) {
if (classLoader == null) {
classLoader = ClassUtils.getDefaultClassLoader();
}
try {
forName(className, classLoader);
return true;
} catch (Throwable var3) {
return false;
}
}
总结:SpringBoot加载时,通过spring-autoconfigure-metadata.properties中配置的条件,依次循环每一个候选的自动配置类,去判断是否应该导入某一个自动配置类。
本文有不足之处,请大家指正。