Spring Boot自动化配置原理

时间秒杀一切 提交于 2020-02-24 12:40:28

Spring Boot自动化配置原理依赖于 @Conditional 注解来实现:
@Conditional是Spring4提供的一个新特性用于根据条件来控制Bean的创建行为。
我们从大家熟知的Spring Boot 的启动类开始

@SpringBootApplication
public class DemoApplication {
   public static void main(String[] args) {
       SpringApplication.run(DemoApplication.class, args);
   }
}

@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 {
   @AliasFor(
       annotation = EnableAutoConfiguration.class,
       attribute = "exclude"
   )
   Class<?>[] exclude() default {};
   @AliasFor(
       annotation = EnableAutoConfiguration.class,
       attribute = "excludeName"
   )
   String[] excludeName() default {};
   @AliasFor(
       annotation = ComponentScan.class,
       attribute = "basePackages"
   )
   String[] scanBasePackages() default {};
   @AliasFor(
       annotation = ComponentScan.class,
       attribute = "basePackageClasses"
   )
   Class<?>[] scanBasePackageClasses() default {};
}

该注解相当于同时声明了@Configuration、@EnableAutoConfiguration与@ComponentScan三个注解(如果我们想定制自定义的自动配置实现,声明这三个注解就足够了),而@EnableAutoConfiguration是我们的关注点,从它的名字可以看出来,它是用来开启自动配置的,源码如下:

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import({EnableAutoConfigurationImportSelector.class})
public @interface EnableAutoConfiguration {
   String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";
   Class<?>[] exclude() default {};
   String[] excludeName() default {};
}

我们发现@Import(Spring 提供的一个注解,可以导入配置类或者Bean到当前类中)导入了EnableAutoConfigurationImportSelector类,根据名字来看,它应该就是我们要找到的目标了。不过查看它的源码发现它已经被Deprecated了,而官方API中告知我们去查看它的父类AutoConfigurationImportSelector。

/** @deprecated */
@Deprecated
public class EnableAutoConfigurationImportSelector extends AutoConfigurationImportSelector {
   public EnableAutoConfigurationImportSelector() {
   }
   protected boolean isEnabled(AnnotationMetadata metadata) {
       return this.getClass().equals(EnableAutoConfigurationImportSelector.class)?((Boolean)this.getEnvironment().getProperty("spring.boot.enableautoconfiguration", Boolean.class, Boolean.valueOf(true))).booleanValue():true;
   }
}

由于AutoConfigurationImportSelector的源码太长了,这里我只截出关键的地方,显然方法selectImports是选择自动配置的主入口,它调用了其他的几个方法来加载元数据等信息,最后返回一个包含许多自动配置类信息的字符串数组。

public String[] selectImports(AnnotationMetadata annotationMetadata) {
   if(!this.isEnabled(annotationMetadata)) {
       return NO_IMPORTS;
   } else {
       try {
           AutoConfigurationMetadata ex = AutoConfigurationMetadataLoader.loadMetadata(this.beanClassLoader);
           AnnotationAttributes attributes = this.getAttributes(annotationMetadata);
           List configurations = this.getCandidateConfigurations(annotationMetadata, attributes);
           configurations = this.removeDuplicates(configurations);
           configurations = this.sort(configurations, ex);
           Set exclusions = this.getExclusions(annotationMetadata, attributes);
           this.checkExcludedClasses(configurations, exclusions);
           configurations.removeAll(exclusions);
           configurations = this.filter(configurations, ex);
           this.fireAutoConfigurationImportEvents(configurations, exclusions);
           return (String[])configurations.toArray(new String[configurations.size()]);
       } catch (IOException var6) {
           throw new IllegalStateException(var6);
       }
   }
   }

重点在于方法getCandidateConfigurations()返回了自动配置类的信息列表,而它通过调用SpringFactoriesLoader.loadFactoryNames()来扫描加载含有META-INF/spring.factories文件的jar包,该文件记录了具有哪些自动配置类。

# PropertySource Loaders
org.springframework.boot.env.PropertySourceLoader=\
org.springframework.boot.env.PropertiesPropertySourceLoader,\
org.springframework.boot.env.YamlPropertySourceLoader

# Run Listeners
org.springframework.boot.SpringApplicationRunListener=\
org.springframework.boot.context.event.EventPublishingRunListener

# Error Reporters
org.springframework.boot.SpringBootExceptionReporter=\
org.springframework.boot.diagnostics.FailureAnalyzers

# Application Context Initializers
org.springframework.context.ApplicationContextInitializer=\
org.springframework.boot.context.ConfigurationWarningsApplicationContextInitializer,\
org.springframework.boot.context.ContextIdApplicationContextInitializer,\
org.springframework.boot.context.config.DelegatingApplicationContextInitializer,\
org.springframework.boot.web.context.ServerPortInfoApplicationContextInitializer

# Application Listeners
org.springframework.context.ApplicationListener=\
org.springframework.boot.ClearCachesApplicationListener,\
org.springframework.boot.builder.ParentContextCloserApplicationListener,\
org.springframework.boot.context.FileEncodingApplicationListener,\
org.springframework.boot.context.config.AnsiOutputApplicationListener,\
org.springframework.boot.context.config.ConfigFileApplicationListener,\
org.springframework.boot.context.config.DelegatingApplicationListener,\
org.springframework.boot.context.logging.ClasspathLoggingApplicationListener,\
org.springframework.boot.context.logging.LoggingApplicationListener,\
org.springframework.boot.liquibase.LiquibaseServiceLocatorApplicationListener

spring.factories文件是以key=value 的形势,key是注解全类名,value是类名列。(以逗号分隔)

面试回答方案:

Spring Boot启动的时候会通过@EnableAutoConfiguration注解通过@SpringBootApplication被间接的标记在了Spring Boot的启动类上。在SpringApplication.run(...)的内部就会执行selectImports()方法,找到所有JavaConfig自动配置类的全限定名对应的class,然后将所有自动配置类加载到Spring容器中。
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!