Springboot @EnableAutoConfiguration原理
Springboot是如何加载@EnableAutoConfiguration
本文着重介绍,如何让用户自定义的类通过@EnableAutoConfiguration加载
1. 如何把自己的配置类加入到Spring容器中呢?
1.1 spring.factories
此处代码,我们可以借助开源框架的mybatis-plus来理解
# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.baomidou.mybatisplus.autoconfigure.MybatisPlusAutoConfiguration
可以发现此处直接把自己想要加入到spring容器中的配置类作为org.springframework.boot.autoconfigure.EnableAutoConfiguration的value就行,是不是很简单。那么Springboot是如何基于这种配置加载我们的配置类的呢?
2. 探究Springboot是如何加载我们的配置类的
首先,让我们来看看Springboot的几个基础注解
2.1 注解介绍
2.1.1 @SpringbootApplication注解是什么
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication {
可以看到该注解继承了@SpringbootConfiguration注解和@EnableAutoConfiguration注解
2.1.2 @SpringbootConfiguration注解
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Configuration
public @interface SpringBootConfiguration {
此处可以看到@SpringbootConfiguration注解是@Configuration注解的子类
2.1.3 @EnableAutoConfiguration注解
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {
熟悉Springboot的@EnableXXX相关注解的同学,看到@Import注解就开心了~
public class AutoConfigurationImportSelector implements DeferredImportSelector, BeanClassLoaderAware,
ResourceLoaderAware, BeanFactoryAware, EnvironmentAware, Ordered {
不过需要注意的是此处AutoConfigurationImportSelector类实现的DeferredImportSelector而不同于一般的ImportSelector
通过对以上三个注解的,以及注解之间关系的介绍,大家可以把加了@SpringbootApplication注解的启动类认为是加了**@ComponentScan、@Configuration、@Import**注解的类
2.2 Springboot启动过程解析spring.factories
Springboot在启动过程中,会在这个方法中,加载所有的spring.factories里面的配置,并缓存起来。
SpringFactoriesLoader类
private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
MultiValueMap<String, String> result = cache.get(classLoader);
if (result != null) {
return result;
}
try {
Enumeration<URL> urls = (classLoader != null ?
classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :
ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
result = new LinkedMultiValueMap<>();
while (urls.hasMoreElements()) {
URL url = urls.nextElement();
UrlResource resource = new UrlResource(url);
Properties properties = PropertiesLoaderUtils.loadProperties(resource);
for (Map.Entry<?, ?> entry : properties.entrySet()) {
String factoryTypeName = ((String) entry.getKey()).trim();
for (String factoryImplementationName : StringUtils.commaDelimitedListToStringArray((String) entry.getValue())) {
result.add(factoryTypeName, factoryImplementationName.trim());
}
}
}
//把classLoader作为key,将spring.factories里面的配置保存起来,cache是静态常量
cache.put(classLoader, result);
return result;
}
通过断点可以看到运行情况
2.3 Spring容器初始化过程中的处理@EnableAutoConfiguration注解
2.3.1 ConfigurationClassPostProcessor#postProcessBeanDefinitionRegistry方法执行过程
此处涉及到Spring容器初始化过程,以后再记录。先简单记录下:
Spring容器初始化过程中,会调用ConfigurationClassPostProcessor的postProcessBeanDefinitionRegistry方法进行BeanDefinition的注册,此处简单记录下调用链:
postProcessBeanDefinitionRegistry ->
processConfigBeanDefinitions ->
ConfigurationClassParser#parse(这个方法牛逼) ->
ConfigurationClassParser#validate ->
ConfigurationClassBeanDefinitionReader#loadBeanDefinitions
2.3.2 ConfigurationClassParser#parse
ConfigurationClassParser#parse(这个方法牛逼) -> {
ConfigurationClassParser#processConfigurationClass -> {
ConfigurationClassParser#doProcessConfigurationClass -> {
1. 通过componentScanParser.parse来处理类上面的@ComponentScan和@ComponentScans
2. processImports方法处理所有的@Import注解
3. 处理@ImportResource注解
4. 处理@Bean注解
}
this.configurationClasses.put(configClass, configClass);这一步很重要,很多开源框架都是通过调用此处,将类变为bd,注入的。
}
//注意这一步,也就是ConfigurationClassParser#parse方法的最后一步
this.deferredImportSelectorHandler.process();
}
2.3.3 ConfigurationClassParser#processImports执行过程
今天重点关注processImports方法,拿到所有的@import注解里import进来的类:
1. 如果是DeferredImportSelector实现类:封装之后放入到ConfigurationClassParser.deferredImportSelectors中
2. 如果是ImportSelector实现类:调用接口的selectImports方法,对返回值的classNames进行processImports,递归循环起来
3. 如果是ImportBeanDefinitionRegistrar:实例化之后放入到ConfigurationClass.importBeanDefinitionRegistrars中,后续处理
4. 如果不满足上述三者,则对import进来的类进行ConfigurationClassParser#processConfigurationClass操作,这样就进入了循环递归,将import进来的配置类进行处理,同时在处理的过程中,会把配置类放入到ConfigurationClassParser.configurationClasses中,这一步很重要,很多开源框架都是通过调用此处,将类变为bd,注入的。
2.3.4 deferredImportSelectorHandler#process
由于上文介绍过AutoConfigurationImportSelector是DeferredImportSelector,所以放入到ConfigurationClassParser.deferredImportSelectors后续处理;
回到ConfigurationClassParser#parse中,当上述操作执行完了之后,进行
this.deferredImportSelectorHandler.process() -> {
1. DeferredImportSelectorGroupingHandler#register -> {
1.1. 调用了getImportGroup方法,获取class,生成对应的group。注意!!!AutoConfigurationImportSelector类重写了getImportGroup,返回了其内部类AutoConfigurationGroup
}
2. DeferredImportSelectorGroupingHandler#processGroupImports-> {
2.1. 调用group的process方法,
也就是调用了AutoConfigurationGroup#process ->
getAutoConfigurationEntry ->
getCandidateConfigurations ->
SpringFactoriesLoader.loadFactoryNames 终于拿到了在2.2节放入到cache中的ClassName,拿到了以后你猜猜又干嘛了。对嘛,又调用了processImports方法。}
}
2.3.5 this.reader.loadBeanDefinitions(configClasses)
上述执行完成之后,ConfigurationClassPostProcessor#processConfigBeanDefinitions中,执行this.reader.loadBeanDefinitions(configClasses);此处会把ConfigurationClassParser.configurationClasses的keySet全都new成BeanDefinition放入到容器中,同时beanDefinitionNames中放入bd的名字,还要同时处理ImportResource注解内容,以及processImports的第3步的后续。
2.3.6 补充一个方法调用时序图
之后就是Spring容器根据bd创建bean的过程了。
完~