SpringBoot-2.1.3自动配置原理
前言
J2EE笨重的开发、繁多的配置、低下的开发效率、复杂的部署流程、第三方技术集成难度大。springboot提供一站式解决方案,默认集成了主流的框架,大量的自动配置,免去了很多的配置文件,大大的简化开发,这大多数人对springboot的最直接的印象,这也是springboot的成功之处。
虽然springboot集成了很多框架并有默认的配置,但是我们去使用技术的时候要知其然知其所以然,要了解自动配置其背后的原理,增强自身对springboot的理解。
springboot版本:2.1.3
大家可以边看文章边看代码,不要光看文章,会看不下去的
这里大致看完(是弄明白大概的流程哦)大约半小时左右
分析过程
1、首先项目启动时会加载主配置项,在@SpringBootApplication注解中有个@EnableAutoConfiguration注解,表示启动自动配置。
2、@EnableAutoConfiguration具体的细节是如何;
2.1、进入@EnableAutoConfiguration注解,里面有个@Import({AutoConfigurationImportSelector.class})注解,表示想容器中导入AutoConfigurationImportSelector这个类,这个类就是一个配置项选择器,可以获取要导入的配置项。
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import({AutoConfigurationImportSelector.class})
public @interface EnableAutoConfiguration {
...
}
2.2、进入AutoConfigurationImportSelector类,可以看到一个方法selectImports()
public String[] selectImports(AnnotationMetadata annotationMetadata) {
if (!this.isEnabled(annotationMetadata)) {
return NO_IMPORTS;
} else {
AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader.loadMetadata(this.beanClassLoader);
AutoConfigurationImportSelector.AutoConfigurationEntry autoConfigurationEntry = this.getAutoConfigurationEntry(autoConfigurationMetadata, annotationMetadata);
return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
}
}
在这里面会调用一个方法getAutoConfigurationEntry(),该方法返回的就是配置项信息。
2.3、进入getAutoConfigurationEntry(),关注其中的这行代码,返回的是已申请即将导入容器的配置项信息集合
protected AutoConfigurationImportSelector.AutoConfigurationEntry getAutoConfigurationEntry(AutoConfigurationMetadata autoConfigurationMetadata, AnnotationMetadata annotationMetadata) {
// 省略...
List<String> configurations = this.getCandidateConfigurations(annotationMetadata, attributes);
// 省略...
}
}
2.4、进入getCandidateConfigurations()方法,同样继续深入loadFactoryNames()方法,这里大家留意该方法的第一个参数,后面我们会用到。
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;
}
PS:看到这里大家可以先回过头看看,捋一捋
2.5、重头戏来了,进入loadFactoryNames()方法
public static List<String> loadFactoryNames(Class<?> factoryClass, @Nullable ClassLoader classLoader) {
// 获取一个传进来的Class的全路径
String factoryClassName = factoryClass.getName();
return (List)loadSpringFactories(classLoader).getOrDefault(factoryClassName, Collections.emptyList());
}
具体细节在loadSpringFactories()方法中,该方法就在loadFactoryNames()的下面
public static List<String> loadFactoryNames(Class<?> factoryClass, @Nullable ClassLoader classLoader) {
String factoryClassName = factoryClass.getName();
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 {
// flag1:获取类路径下的资源"META-INF/spring.factories"
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);
// flag2:将URL转换成Properties
Properties properties = PropertiesLoaderUtils.loadProperties(resource);
Iterator var6 = properties.entrySet().iterator();
while(var6.hasNext()) {
Entry<?, ?> entry = (Entry)var6.next();
String factoryClassName = ((String)entry.getKey()).trim();
String[] var9 = StringUtils.commaDelimitedListToStringArray((String)entry.getValue());
int var10 = var9.length;
for(int var11 = 0; var11 < var10; ++var11) {
String factoryName = var9[var11];
result.add(factoryClassName, factoryName.trim());
}
}
}
cache.put(classLoader, result);
return result;
} catch (IOException var13) {
throw new IllegalArgumentException("Unable to load factories from location [META-INF/spring.factories]", var13);
}
}
}
-
在loadSpringFactories方法中首先会获取一个类路劲下的资源"META-INF/spring.factories",如图:
文件内容很多,只截取了一部分 -
在外层while中会将文件数据转换成URL,然后将URL装换成Properties,Properties是一个Hashtable,最终配置文件中的数据将以key-value的形式存在Properties中的entry中,如下所示:(具体如何转换的可以在表示flag2的地方继续深入,这里就不涉及了,太多了大家看的头疼)
-
那内层的while循环其实就是对Properties进行遍历,获取key,获取value,并且会将value的值按逗号分隔获取具体的每一个String,然后将这些数据存入result这个Map中,如下所示:
result(Map)
key | Value |
---|---|
org.springframework.context.ApplicationContextInitializer | org.springframework.boot.autoconfigure.SharedMetadataReaderFactoryContextInitializer |
org.springframework.context.ApplicationContextInitializer | org.springframework.boot.autoconfigure.logging.ConditionEvaluationReportLoggingListener |
org.springframework.context.ApplicationListener | org.springframework.boot.autoconfigure.BackgroundPreinitializer |
… | … |
- 最后返回该保存了所有配置项信息的map,回到loadFactoryNames()方法
2.6、上面执行完返回执行getOrDefault(…)
public static List<String> loadFactoryNames(Class<?> factoryClass, @Nullable ClassLoader classLoader) {
// 获取一个传进来的Class的全路径
String factoryClassName = factoryClass.getName();
return (List)loadSpringFactories(classLoader).getOrDefault(factoryClassName, Collections.emptyList());
}
default V getOrDefault(Object key, V defaultValue)
这里有个getOrDefault(…)方法,这是map的方法,用来判断当前map中是否包指定的key,有则返回对应的数据,这个key就是loadFactoryNames()第一个参数的某个Class的全路径名,往回loadFactoryNames方法上一层看,
一看就明白了,传进来的就是EnableAutoConfiguration,那自然这个key就是EnableAutoConfiguration的全路径,也就是会拿到配置文件中EnableAutoConfiguration所对应的数据
所以到这,就知道springboot启动时自动配置到底配置的那些组件,一看就知道了,总的来说就是:
- 获取META-INF/spring.factories文件中EnableAutoConfiguration指定的配置项,并将其导入组件,实现自动配置
- 每个xxxAutoConfiguration即为导入的组件,通过这些配置类实现具体的配置
后面一片以一个xxxAutoConfiguration作为实例进一步分析。