SpringBoot 自动配置
自动配置原理
流程图
ConfigurationClass
这是 Spring 用于存储 @Configuration 注解解析后的封装类,里面有带有 @Bean 注解的方法以及其他一些信息。
ConfigurationClassPostProcessor
ConfigurationClassPostProcessor 是手动注册的,根据堆栈可以看到在创建 ApplicationContext 的过程中调用到了 AnnotationConfigUtils#registerAnnotationConfigProcessors:
public static Set<BeanDefinitionHolder> registerAnnotationConfigProcessors(
BeanDefinitionRegistry registry, @Nullable Object source) {
Set<BeanDefinitionHolder> beanDefs = new LinkedHashSet<>(8);
if (!registry.containsBeanDefinition(CONFIGURATION_ANNOTATION_PROCESSOR_BEAN_NAME)) {
RootBeanDefinition def = new RootBeanDefinition(ConfigurationClassPostProcessor.class);
def.setSource(source);
beanDefs.add(registerPostProcessor(registry, def, CONFIGURATION_ANNOTATION_PROCESSOR_BEAN_NAME));
}
if (!registry.containsBeanDefinition(AUTOWIRED_ANNOTATION_PROCESSOR_BEAN_NAME)) {
RootBeanDefinition def = new RootBeanDefinition(AutowiredAnnotationBeanPostProcessor.class);
def.setSource(source);
beanDefs.add(registerPostProcessor(registry, def, AUTOWIRED_ANNOTATION_PROCESSOR_BEAN_NAME));
}
// Check for JSR-250 support, and if present add the CommonAnnotationBeanPostProcessor.
if (jsr250Present && !registry.containsBeanDefinition(COMMON_ANNOTATION_PROCESSOR_BEAN_NAME)) {
RootBeanDefinition def = new RootBeanDefinition(CommonAnnotationBeanPostProcessor.class);
def.setSource(source);
beanDefs.add(registerPostProcessor(registry, def, COMMON_ANNOTATION_PROCESSOR_BEAN_NAME));
}
return beanDefs;
}
这个过程中注册了很多 PostProcessor,包括 ConfigurationClassPostProcessor、AutowiredAnnotationBeanPostProcessor 和 CommonAnnotationBeanPostProcessor,第一个就是用于处理配置文件的,后面两个是处理 @Autowired、@Resource 和 @Inject 的。
ConfigurationClassPostProcessor 实现了 BeanFactoryPostProcessor 接口,最终的实现是 processConfigBeanDefinitions 方法,该方法将带有 @Configuration 注解的类转换成 ConfigurationClass,最后注册到 BeanFactory 中。
public void processConfigBeanDefinitions(BeanDefinitionRegistry registry) {
//...
// 两个最重要的调用
// ConfigurationClassParser parser
parser.parse(candidates);
//...
// ConfigurationClassBeanDefinitionReader reader
this.reader.loadBeanDefinitions(configClasses);
//...
}
ConfigurationClassParser
两次解析
该类是解析的关键,上一个代码块的源码可以看到调用了 parse 方法,
public void parse(Set<BeanDefinitionHolder> configCandidates) {
// 其他代码忽略
// 第一次解析
parse(((AnnotatedBeanDefinition) bd).getMetadata(), holder.getBeanName());
//第二次延迟解析
this.deferredImportSelectorHandler.process();
}
第一次 parse 解析当前工程的 ConfigurationClass,因此第二次 process 延迟解析的 ConfigurationClass 就可以通过 Conditional 注解判断 starter 应该提供什么样的 Bean 了,接下来主要分析 parse 和 process 两个方法。
parse
parse 方法只解析当前工程的配置类,包括工程所有 @Configuration (当然也有 @SpringApplication 所在的类),但不包括引入的 jar 包中的。
parse 方法通过 processConfigurationClass 方法调用了 doProcessConfigurationClass,它包括对嵌套类的递归处理和 @PropertySource、@ComponentScan、@Import、@ImportResource 和 @Bean 注解的解析,最终装到 ConfigurationClass 中。
protected final SourceClass doProcessConfigurationClass(
ConfigurationClass configClass, SourceClass sourceClass, Predicate<String> filter)
throws IOException {
if (configClass.getMetadata().isAnnotated(Component.class.getName())) {
// 递归处理嵌套的类
processMemberClasses(configClass, sourceClass, filter);
}
// Process any @PropertySource annotations
// Process any @ComponentScan annotations
// Process any @Import annotations
// 处理例如 @Import(AutoConfigurationImportSelector.class)
processImports(configClass, sourceClass, getImports(sourceClass), filter, true);
// Process any @ImportResource annotations
// Process individual @Bean methods
// Process default methods on interfaces
// Process superclass, if any
}
其中 processImports 是自动配置的关键,方法中可以看到这样一行代码:
if (selector instanceof DeferredImportSelector) {
this.deferredImportSelectorHandler.handle(configClass, (DeferredImportSelector) selector);
}
意思是如果是延迟的就调用 handle,再看下 handle:
public void handle(ConfigurationClass configClass, DeferredImportSelector importSelector) {
DeferredImportSelectorHolder holder = new DeferredImportSelectorHolder(configClass, importSelector);
if (this.deferredImportSelectors == null) {
// 是空的,所以一定调用不到
DeferredImportSelectorGroupingHandler handler = new DeferredImportSelectorGroupingHandler();
handler.register(holder);
handler.processGroupImports();
}
else {
this.deferredImportSelectors.add(holder);
}
}
而启动类 @SpringApplication 包含 @EnableAutoConfiguration,@EnableAutoConfiguration 又包含 @Import(AutoConfigurationImportSelector.class),AutoConfigurationImportSelector 类就实现了 DeferredImportSelector接口,所以最后将 AutoConfigurationImportSelector 放到了 deferredImportSelectorHandler 这个 list 中。
process
process 中最重要的方法调用是 processGroupImports:
public void processGroupImports() {
for (DeferredImportSelectorGrouping grouping : this.groupings.values()) {
Predicate<String> exclusionFilter = grouping.getCandidateFilter();
grouping.getImports().forEach(entry -> {
ConfigurationClass configurationClass = this.configurationClasses.get(entry.getMetadata());
try {
processImports(configurationClass, asSourceClass(configurationClass, exclusionFilter),
Collections.singleton(asSourceClass(entry.getImportClassName(), exclusionFilter)),
exclusionFilter, false);
}
catch (BeanDefinitionStoreException ex) {
throw ex;
}
catch (Throwable ex) {
throw new BeanDefinitionStoreException(
"Failed to process import candidates for configuration class [" +
configurationClass.getMetadata().getClassName() + "]", ex);
}
});
}
}
该方法有两个步骤:1.getImports,找到所有引入的 jar 中的配置类;2.processImports,导入配置类。
步骤1:getImports 主要的调用顺序:getImports -> process -> getAutoConfigurationEntry,getAutoConfigurationEntry 方法如下:
public Iterable<Group.Entry> getImports() {
for (DeferredImportSelectorHolder deferredImport : this.deferredImports) {
this.group.process(deferredImport.getConfigurationClass().getMetadata(),
deferredImport.getImportSelector());
}
return this.group.selectImports();
}
//下面都是 AutoConfigurationImportSelector 中的方法
protected AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) {
//...
List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
//...
}
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
List<String> configurations = SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(),
getBeanClassLoader());
}
它最终 for 循环调用了之前添加到 deferredImportSelectors 的 AutoConfigurationImportSelector 类的 process 方法,再通过 SpringFactoriesLoader 获取到了所有引入的 jar 中的配置类( SpringFactoriesLoader 可以看到资源的位置 META-INF/spring.factories。)
步骤二:processImports 中有很多 ifelse,可以看到下面几行代码:
// Candidate class not an ImportSelector or ImportBeanDefinitionRegistrar ->
// process it as an @Configuration class
this.importStack.registerImport(currentSourceClass.getMetadata(), candidate.getMetadata().getClassName());
processConfigurationClass(candidate.asConfigClass(configClass), exclusionFilter);
意思是如果是有 @configuration 的类就交给 processConfigurationClass,而 processConfigurationClass 就是第一次解析配置类所调用的方法。
ConfigurationClassBeanDefinitionReader
配置类的注册依赖的是 ConfigurationClassBeanDefinitionReader 的 loadBeanDefinitions 方法,具体是 for 循环调用 loadBeanDefinitionsForConfigurationClass。
private void loadBeanDefinitionsForConfigurationClass(
ConfigurationClass configClass, TrackedConditionEvaluator trackedConditionEvaluator) {
if (trackedConditionEvaluator.shouldSkip(configClass)) {
String beanName = configClass.getBeanName();
if (StringUtils.hasLength(beanName) && this.registry.containsBeanDefinition(beanName)) {
this.registry.removeBeanDefinition(beanName);
}
this.importRegistry.removeImportingClass(configClass.getMetadata().getClassName());
return;
}
if (configClass.isImported()) {
// 注册 ConfigurationClass
registerBeanDefinitionForImportedConfigurationClass(configClass);
}
// 注册 @Bean
for (BeanMethod beanMethod : configClass.getBeanMethods()) {
loadBeanDefinitionsForBeanMethod(beanMethod);
}
// 注册 @ImportResource
loadBeanDefinitionsFromImportedResources(configClass.getImportedResources());
// 注册 @EnableConfigurationProperties
loadBeanDefinitionsFromRegistrars(configClass.getImportBeanDefinitionRegistrars());
}
自定义 starter
创建 spring-boot-starter-gdp
- 首先在 resources 下新建 META-INF 目录,再新建 spring.factories 文件,添加如下配置:
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.example.springbootstartergdp.config.GdpAutoConfiguration
- 新建配置类 GdpAutoConfiguration
@Configuration(proxyBeanMethods = false)
@EnableConfigurationProperties(GdpProperties.class)
@Import({GdpConfiguration.Family.class, GdpConfiguration.Friend.class})
public class GdpAutoConfiguration {
}
- @EnableConfigurationPropertie 使 spring 能自动配置 GdpProperties,GdpProperties 如下:
@ConfigurationProperties(prefix = "spring.gdp")
public class GdpProperties {
/**
* 用户名
*/
private String username;
/**
* 密码
*/
private String password;
/**
* 类型
*/
private String type;
//get set 不粘贴了
}
- @Import 导入了 GdpConfiguration.Family 和 GdpConfiguration.Friend 配置,如下:
public class GdpConfiguration {
@Configuration(proxyBeanMethods = false)
@ConditionalOnProperty(name = "spring.gdp.type", havingValue = "family")
static class Family {
@Bean
@ConditionalOnMissingBean
Chat familyChat() {
return new FamilyChat();
}
}
@Configuration(proxyBeanMethods = false)
@ConditionalOnProperty(name = "spring.gdp.type", havingValue = "friend")
static class Friend {
@Bean
@ConditionalOnMissingBean
Chat friendChat() {
return new FriendChat();
}
}
}
FamilyChat:
public class FamilyChat implements Chat {
public String say(String info) {
return "晚上闭灯了别玩手机!";
}
}
目录结构:
├─src
│ ├─main
│ │ ├─java
│ │ │ └─com
│ │ │ └─example
│ │ │ └─springbootstartergdp
│ │ │ │ SpringBootStarterGdpApplication.java
│ │ │ │
│ │ │ ├─config
│ │ │ │ GdpAutoConfiguration.java
│ │ │ │ GdpConfiguration.java
│ │ │ │ GdpProperties.java
│ │ │ │
│ │ │ ├─listener
│ │ │ │ HelloApplicationRunListener.java
│ │ │ │
│ │ │ └─service
│ │ │ Chat.java
│ │ │ FamilyChat.java
│ │ │ FriendChat.java
│ │ │
│ │ └─resources
│ │ │ application.yml
│ │ │
│ │ └─META-INF
│ │ additional-spring-configuration-metadata.json
│ │ spring.factories
使用 spring-boot-starter-gdp
引入 jar 包并添加如下配置
spring:
gdp:
username: mama
password: 123456
type: FAMILY
测试是否获取自定义 starter 中的 bean,成功输出 晚上闭灯了别玩手机!
。
public static void main(String[] args) {
BeanFactory beanFactory = SpringApplication.run(StudyAutoconfigurationApplication.class, args);
Chat family = beanFactory.getBean("familyChat", Chat.class);
System.out.println(family.say("nihao"));
}