SpringBoot自动配置原理

ゞ 浴缸里的玫瑰 2022-05-08 22:26 503阅读 0赞

一、引言

在Spring+SpringMVC框架下,我们需要两个配置文件(一个文件也行哈),来集成Spring和SpringMVC。但使用SpringBoot之后,我们就不需要像之前那样繁琐的配置了,SpringBoot帮我们一站式配置好,这就要归功于SpringBoot的自动配置,下面来探索一下自动配置的原理。

下面这个链接是SpringBoot官方文档,专门介绍了在application.properties配置文件中,可以配置哪些参数,可以参考一下。

https://docs.spring.io/spring-boot/docs/1.5.9.RELEASE/reference/htmlsingle/#common-application-properties

二、自动配置原理

1、SpringBoot启动时,会加载 被@SpringBootApplication注解 修饰的主配置类,而该注解又开启了自动配置功能 @EnableAutoConfiguration;

  1. @Target(ElementType.TYPE) // 注解的适用范围,其中TYPE用于描述类、接口(包括包注解类型)或enum声明
  2. @Retention(RetentionPolicy.RUNTIME) // 注解的生命周期,保留到class文件中(三个生命周期)
  3. @Documented // 表明这个注解应该被javadoc记录
  4. @Inherited // 子类可以继承该注解
  5. @SpringBootConfiguration // 继承了Configuration,表示当前是注解类
  6. @EnableAutoConfiguration // 开启springboot的注解功能
  7. @ComponentScan(excludeFilters = { // 扫描路径设置(具体使用待确认)
  8. @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
  9. @Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
  10. public @interface SpringBootApplication {
  11. @AliasFor(annotation = EnableAutoConfiguration.class)
  12. Class<?>[] exclude() default { };
  13. @AliasFor(annotation = EnableAutoConfiguration.class)
  14. String[] excludeName() default { };
  15. @AliasFor(annotation = ComponentScan.class, attribute = "basePackages")
  16. String[] scanBasePackages() default { };
  17. @AliasFor(annotation = ComponentScan.class, attribute = "basePackageClasses")
  18. Class<?>[] scanBasePackageClasses() default { };
  19. }

虽然定义使用了多个Annotation进行了原信息标注,但实际上重要的只有三个Annotation:

  1. @SpringBootConfiguration:点开查看发现里面是应用了@Configuration
  2. @EnableAutoConfiguration
  3. @ComponentScan

2、@Configuration注解说明

这里的@Configuration不陌生,它就是JavaConfig形式的Spring Ioc容器的配置类使用的那个@Configuration,SpringBoot社区推荐使用基于JavaConfig的配置形式,所以,这里的启动类标注了@Configuration之后,本身其实也是一个IoC容器的配置类。以下两种配置方式等价:

基于XML的配置形式:

  1. <bean id="jdbcTemplate" class="..JDBCTemplate">
  2. ...
  3. </bean>

基于JavaConfig的配置形式:

  1. @Configuration
  2. public class JDBCConfiguration{
  3. @Bean
  4. public JDBCTemplate jdbcTemplate(){
  5. return new JDBCTemplate ();
  6. }
  7. }

任何一个标注了@Bean的方法,其返回值将作为一个bean定义注册到Spring的IoC容器,方法名将默认成该bean定义的id。

3、@ComponentScan

@ComponentScan注解功能:其实就是自动扫描并加载符合条件的组件(比如@Component、@Repository等)或者bean定义,最终将这些bean定义加载到IoC容器中。

我们可以通过basePackages等属性来细粒度的定制@ComponentScan自动扫描的范围,如果不指定,则默认Spring框架实现会从声明@ComponentScan所在类的package进行扫描。

4、@EnableAutoConfiguration 注解:

  1. @Target(ElementType.TYPE)
  2. @Retention(RetentionPolicy.RUNTIME)
  3. @Documented
  4. @Inherited
  5. @AutoConfigurationPackage
  6. @Import(AutoConfigurationImportSelector.class)
  7. public @interface EnableAutoConfiguration {
  8. String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";
  9. Class<?>[] exclude() default { };
  10. String[] excludeName() default { };
  11. }

被两个注解修饰:

  1. @AutoConfigurationPackage
  2. @Import(AutoConfigurationImportSelector.class)

5、@AutoConfigurationPackage 注解:

点击进入该注解,可以发现:

  1. @Import(AutoConfigurationPackages.Registrar.class)
  2. public @interface AutoConfigurationPackage {
  3. }
  4. //注册了一个Bean的定义
  5. static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports {
  6. @Override
  7. public void registerBeanDefinitions(AnnotationMetadata metadata,
  8. BeanDefinitionRegistry registry) {
  9. register(registry, new PackageImport(metadata).getPackageName());
  10. }
  11. }

new PackageImport(metadata).getPackageName(),它其实返回了当前主程序类所在包以及下面所有子包的组件,然后将这些组件注册到Spring容器中。

注意:这里分析可以知道,我们要把将主配置类(@SpringBootApplication标注的类)放在项目的最高级中。

6、@Import注解:
利用AutoConfigurationImportSelector给容器中导入一些组件,在AutoConfigurationImportSelector类中,查看selectImports()函数:

  1. @Override
  2. public String[] selectImports(AnnotationMetadata annotationMetadata) {
  3. if (!isEnabled(annotationMetadata)) {
  4. return NO_IMPORTS;
  5. }
  6. AutoConfigurationMetadata autoConfigurationMetadata =
  7. AutoConfigurationMetadataLoader.loadMetadata(this.beanClassLoader);
  8. AnnotationAttributes attributes = getAttributes(annotationMetadata);
  9. //获取候选的配置,此行代码很重要
  10. List<String> configurations = getCandidateConfigurations(annotationMetadata,attributes);
  11. configurations = removeDuplicates(configurations);
  12. Set<String> exclusions = getExclusions(annotationMetadata, attributes);
  13. checkExcludedClasses(configurations, exclusions);
  14. configurations.removeAll(exclusions);
  15. configurations = filter(configurations, autoConfigurationMetadata);
  16. fireAutoConfigurationImportEvents(configurations, exclusions);
  17. return StringUtils.toStringArray(configurations);
  18. }

查看getCandidateConfigurations()方法

  1. protected List<String> getCandidateConfigurations(AnnotationMetadata metadata,
  2. AnnotationAttributes attributes) {
  3. List<String> configurations = SpringFactoriesLoader.loadFactoryNames(
  4. getSpringFactoriesLoaderFactoryClass(), getBeanClassLoader());
  5. return configurations;
  6. }

该方法扫描所有jar包类路径下 META‐INF/spring.factories,把扫描到的这些文件的内容包装成properties对象,从properties中获取到EnableAutoConfiguration.class类(类名)对应的值,然后把他们添加在容器中。

查看loadFactoryNames()方法

  1. private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
  2. MultiValueMap<String, String> result = cache.get(classLoader);
  3. if (result != null) {
  4. return result;
  5. }
  6. try {
  7. Enumeration<URL> urls = (classLoader != null ?
  8. classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :
  9. ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
  10. result = new LinkedMultiValueMap<>();
  11. while (urls.hasMoreElements()) {
  12. URL url = urls.nextElement();
  13. UrlResource resource = new UrlResource(url);
  14. Properties properties = PropertiesLoaderUtils.loadProperties(resource);
  15. for (Map.Entry<?, ?> entry : properties.entrySet()) {
  16. List<String> factoryClassNames = Arrays.asList(
  17. StringUtils.commaDelimitedListToStringArray((String) entry.getValue()));
  18. result.addAll((String) entry.getKey(), factoryClassNames);
  19. }
  20. }
  21. cache.put(classLoader, result);
  22. return result;
  23. }
  24. catch (IOException ex) {
  25. throw new IllegalArgumentException("Unable to load factories from location [" +
  26. FACTORIES_RESOURCE_LOCATION + "]", ex);
  27. }
  28. }

该方法会将类路径下 META-INF/spring.factories 里面配置的所有EnableAutoConfiguration的值加入到了容器中;

下面是spring-boot-autoconfigure包中的META-INF下的spring.factories文件中的自动配置,其中每一个这样的 xxxAutoConfiguration类都是容器中的一个组件,都加入到容器中,用它们来做自动配置;
在这里插入图片描述

  1. # Auto Configure
  2. org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
  3. org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration,\
  4. org.springframework.boot.autoconfigure.aop.AopAutoConfiguration,\
  5. org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration,\
  6. org.springframework.boot.autoconfigure.batch.BatchAutoConfiguration,\
  7. org.springframework.boot.autoconfigure.cache.CacheAutoConfiguration,\
  8. org.springframework.boot.autoconfigure.cassandra.CassandraAutoConfiguration,\
  9. org.springframework.boot.autoconfigure.cloud.CloudAutoConfiguration,\
  10. org.springframework.boot.autoconfigure.context.ConfigurationPropertiesAutoConfiguration,\
  11. org.springframework.boot.autoconfigure.context.MessageSourceAutoConfiguration,\
  12. org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration,\
  13. org.springframework.boot.autoconfigure.couchbase.CouchbaseAutoConfiguration,\
  14. org.springframework.boot.autoconfigure.dao.PersistenceExceptionTranslationAutoConfiguration,\
  15. org.springframework.boot.autoconfigure.data.cassandra.CassandraDataAutoConfiguration,\
  16. org.springframework.boot.autoconfigure.data.cassandra.CassandraReactiveDataAutoConfiguration,\
  17. org.springframework.boot.autoconfigure.data.cassandra.CassandraReactiveRepositoriesAutoConfiguration,\
  18. org.springframework.boot.autoconfigure.data.cassandra.CassandraRepositoriesAutoConfiguration,\
  19. org.springframework.boot.autoconfigure.data.couchbase.CouchbaseDataAutoConfiguration,\
  20. org.springframework.boot.autoconfigure.data.couchbase.CouchbaseReactiveDataAutoConfiguration,\
  21. org.springframework.boot.autoconfigure.data.couchbase.CouchbaseReactiveRepositoriesAutoConfiguration,\
  22. org.springframework.boot.autoconfigure.data.couchbase.CouchbaseRepositoriesAutoConfiguration,\
  23. org.springframework.boot.autoconfigure.data.elasticsearch.ElasticsearchAutoConfiguration,\
  24. org.springframework.boot.autoconfigure.data.elasticsearch.ElasticsearchDataAutoConfiguration,\
  25. org.springframework.boot.autoconfigure.data.elasticsearch.ElasticsearchRepositoriesAutoConfiguration,\
  26. org.springframework.boot.autoconfigure.data.jpa.JpaRepositoriesAutoConfiguration,\
  27. org.springframework.boot.autoconfigure.data.ldap.LdapDataAutoConfiguration,\
  28. org.springframework.boot.autoconfigure.data.ldap.LdapRepositoriesAutoConfiguration,\
  29. org.springframework.boot.autoconfigure.data.mongo.MongoDataAutoConfiguration,\
  30. org.springframework.boot.autoconfigure.data.mongo.MongoReactiveDataAutoConfiguration,\
  31. org.springframework.boot.autoconfigure.data.mongo.MongoReactiveRepositoriesAutoConfiguration,\
  32. ……
  33. ……
  34. 此处略,可自行查看

三、自动配置幕后英雄:SpringFactoriesLoader详解

借助于Spring框架原有的一个工具类:SpringFactoriesLoader的支持,@EnableAutoConfiguration注解智能的自动配置功效才能得以大功告成!

SpringFactoriesLoader属于Spring框架私有的一种扩展方案,其主要功能就是从指定的配置文件META-INF/spring.factories加载配置。

  1. public final class SpringFactoriesLoader {
  2. public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";
  3. private static final Log logger = LogFactory.getLog(SpringFactoriesLoader.class);
  4. private static final Map<ClassLoader, MultiValueMap<String, String>> cache = new ConcurrentReferenceHashMap<>();
  5. private SpringFactoriesLoader() {
  6. }
  7. public static <T> List<T> loadFactories(Class<T> factoryClass, @Nullable ClassLoader classLoader) {
  8. }
  9. public static List<String> loadFactoryNames(Class<?> factoryClass, @Nullable ClassLoader classLoader) {
  10. }
  11. private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
  12. }
  13. @SuppressWarnings("unchecked")
  14. private static <T> T instantiateFactory(String instanceClassName, Class<T> factoryClass,
  15. }
  16. }

SpringFactoriesLoader配合@EnableAutoConfiguration使用的话,它更多的是提供一种配置查找的功能支持,即根据@EnableAutoConfiguration的完整类名org.springframework.boot.autoconfigure.EnableAutoConfiguration作为查找的Key,去获取对应的一组@Configuration类。
在这里插入图片描述

上图就是SpringBoot的autoconfigure依赖包中META-INF/spring.factories配置文件中的一段内容。

所以,@EnableAutoConfiguration自动配置就变成了:从classpath中搜寻所有的META-INF/spring.factories配置文件,并将其中org.springframework.boot.autoconfigure.EnableutoConfiguration对应的配置项,通过反射实例化为对应的标注了@Configuration的JavaConfig形式的IoC容器配置类,然后汇总为一个并加载到IoC容器。

四、自动配置原理例子

以HttpEncodingAutoConfiguration(http编码自动配置)为例解释自动配置原理

1、代码
  1. @Configuration
  2. @EnableConfigurationProperties(HttpEncodingProperties.class)
  3. @ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET)
  4. @ConditionalOnClass(CharacterEncodingFilter.class)
  5. @ConditionalOnProperty(prefix = "spring.http.encoding", value = "enabled", matchIfMissing = true)
  6. public class HttpEncodingAutoConfiguration {
  7. //已经和SpringBoot的配置文件映射了
  8. private final HttpEncodingProperties properties;
  9. //只有一个有参构造器的情况下,参数的值就会从容器中拿
  10. public HttpEncodingAutoConfiguration(HttpEncodingProperties properties) {
  11. this.properties = properties;
  12. }
  13. @Bean //给容器中添加一个组件,这个组件的某些值需要从properties中获取
  14. @ConditionalOnMissingBean //如果容器中没有这个组件,则把该组件加入到容器中
  15. public CharacterEncodingFilter characterEncodingFilter() {
  16. CharacterEncodingFilter filter = new OrderedCharacterEncodingFilter();
  17. filter.setEncoding(this.properties.getCharset().name());
  18. filter.setForceRequestEncoding(this.properties.shouldForce(Type.REQUEST));
  19. filter.setForceResponseEncoding(this.properties.shouldForce(Type.RESPONSE));
  20. return filter;
  21. }
  22. @Bean
  23. public LocaleCharsetMappingsCustomizer localeCharsetMappingsCustomizer() {
  24. return new LocaleCharsetMappingsCustomizer(this.properties);
  25. }
  26. private static class LocaleCharsetMappingsCustomizer implements
  27. WebServerFactoryCustomizer<ConfigurableServletWebServerFactory>, Ordered {
  28. private final HttpEncodingProperties properties;
  29. LocaleCharsetMappingsCustomizer(HttpEncodingProperties properties) {
  30. this.properties = properties;
  31. }
  32. @Override
  33. public void customize(ConfigurableServletWebServerFactory factory) {
  34. if (this.properties.getMapping() != null) {
  35. factory.setLocaleCharsetMappings(this.properties.getMapping());
  36. }
  37. }
  38. @Override
  39. public int getOrder() {
  40. return 0;
  41. }
  42. }
  43. }
2、说明
  1. (1)、@Configuration
  2. 表示这是一个配置类,以前编写的配置文件一样,也可以给容器中添加组件
  3. (2)、@EnableConfigurationProperties(HttpEncodingProperties.class)
  4. 启动指定类的ConfigurationProperties功能,将配置文件中对应的值和HttpEncodingProperties绑定起来,
  5. 并把HttpEncodingProperties加入到ioc容器中
  6. (3)、@ConditionalOnWebApplication
  7. Spring底层@Conditional注解,根据不同的条件,如果满足指定的条件,整个配置类里面的配置就会生效;
  8. 判断当前应用是否是web应用,如果是,当前配置类生效
  9. (4)、@ConditionalOnClass(CharacterEncodingFilter.class)
  10. 判断当前项目有没有这个类CharacterEncodingFilterSpringMVC中进行乱码解决的过滤器;
  11. (5)、@ConditionalOnProperty(prefix = "spring.http.encoding", value = "enabled", matchIfMissing = true)
  12. 判断配置文件中是否存在某个配置 spring.http.encoding.enabled;如果不存在,判断也是成立的,
  13. 即使我们配置文件中不配置pring.http.encoding.enabled=true,也是默认生效的;

一但这个配置类生效,这个配置类就会给容器中添加各种组件,这些组件的属性是从对应的properties类(参看HttpEncodingProperties.class)中获取的,这些类里面的每一个属性又是和配置文件绑定的;

3、HttpEncodingProperties.class

所有在配置文件中能配置的属性都是在xxxxProperties类(该例子中,对应HttpEncodingProperties类)中封装着,配置文件能配置什么就可以参照某个功能对应的这个属性类;

  1. //从配置文件中获取指定的值和bean的属性进行绑定
  2. @ConfigurationProperties(prefix = "spring.http.encoding")
  3. public class HttpEncodingProperties {
  4. public static final Charset DEFAULT_CHARSET = StandardCharsets.UTF_8;
  5. private Charset charset = DEFAULT_CHARSET;
  6. private Boolean force;
  7. private Boolean forceRequest;
  8. private Boolean forceResponse;
  9. private Map<Locale, Charset> mapping;
  10. //此处略
  11. ……
  12. }

五、自动配置类生效

自动配置类必须在一定的条件下才能生效;

必须是@Conditional指定的条件成立,才给容器中添加组件,配置配里面的所有内容才生效;

1、@Conditional派生注解

在这里插入图片描述

六、总结

(1)、SpringBoot启动会加载大量的自动配置类;

(2)、看我们需要的功能有没有SpringBoot默认写好的自动配置类;

(3)、再来看这个自动配置类中到底配置了哪些组件;(只要我们要用的组件有,我们就不需要再来配置了)

(4)、给容器中自动配置类添加组件的时候,会从properties类中获取某些属性,我们就可以在配置文件中指定这些属性的值;

(5)、规则
xxxxAutoConfigurartion:自动配置类,给容器中添加组件;
xxxxProperties:封装配置文件中相关属性;

(6)、规则SpringBoot启动流程:
在这里插入图片描述

发表评论

表情:
评论列表 (有 0 条评论,503人围观)

还没有评论,来说两句吧...

相关阅读

    相关 SpringBoot自动配置原理

    SpringBoot可以简化开发的一个主要原因就是采用了默认配置,所谓约定大于配置就是这个意思。在没有自己指定配置的时候使用默认配置的原理大致如下。如有错误,还请指正。 ==

    相关 SpringBoot自动配置原理

            配置文件到底能写什么?怎么写?自动配置原理; [配置文件能配置的属性参照][Link 1] 1、自动配置原理: 1)、SpringBoot启动的时候加