3 selectImports将 process 方法处理后得到的自动配置类,进行过滤、排除,最后将所有自动配置类添加到容器中
public Iterable<DeferredImportSelector.Group.Entry> selectImports() { if (this.autoConfigurationEntries.isEmpty()) { return Collections.emptyList(); } else { //获取所有需要排除的配置类 Set<String> allExclusions = (Set)this.autoConfigurationEntries.stream(). map(AutoConfigurationImportSelector.AutoConfigurationEntry::getExclusions).flatMap(Collection::stream).collect(Collectors.toSet()); //获取所有经过自动化配置过滤器的配置类 Set<String> processedConfigurations = (Set)this.autoConfigurationEntries.stream().map(AutoConfigurationImportSelector. AutoConfigurationEntry::getConfigurations).flatMap(Collection::stream).collect(Collectors.toCollection(LinkedHashSet::new)); //排除过滤后配置类中需要排除的类 processedConfigurations.removeAll(allExclusions); return (Iterable)this.sortAutoConfigurations(processedConfigurations, this.getAutoConfigurationMetadata()).stream().map((importClassName) -> { return new DeferredImportSelector.Group.Entry((AnnotationMetadata)this.entries.get(importClassName), importClassName); }).collect(Collectors.toList()); } }
SpringBoot自动配置绑定和修改
spring.factories 文件中的所有自动配置类(xxxAutoConfiguration),都是必须在一定的条件下才会作为组件添加到容器中,配置的内容才会生效。这些限制条件在 Spring Boot 中以 @Conditional
派生注解的形式体现
下面我们以 ServletWebServerFactoryAutoConfiguration 为例,介绍 Spring Boot 自动配置是如何生效的
ServletWebServerFactoryAutoConfiguration自动配置类
ServletWebServerFactoryAutoConfiguration配置类的代码如下:
@Configuration( //表示这是一个配置类,与 xml 配置文件等价,也可以给容器中添加组件 proxyBeanMethods = false ) @AutoConfigureOrder(-2147483648) @ConditionalOnClass({ServletRequest.class})//判断当前项目有没有 ServletRequest 这个类 @ConditionalOnWebApplication(// 判断当前应用是否是 web 应用,如果是,当前配置类生效 type = Type.SERVLET ) @EnableConfigurationProperties({ServerProperties.class}) //启动指定类的属性配置(ConfigurationProperties)功能;将配置文件中对应的值和 ServerProperties 绑定起来;并把 ServerProperties 加入到ioc容器中 @Import({ServletWebServerFactoryAutoConfiguration.BeanPostProcessorsRegistrar.class, EmbeddedTomcat.class, EmbeddedJetty.class, EmbeddedUndertow.class}) public class ServletWebServerFactoryAutoConfiguration { public ServletWebServerFactoryAutoConfiguration() { } @Bean //给容器中添加一个组件,这个组件的某些值需要从properties中获取 public ServletWebServerFactoryCustomizer servletWebServerFactoryCustomizer(ServerProperties serverProperties, ObjectProvider<WebListenerRegistrar> webListenerRegistrars) { return new ServletWebServerFactoryCustomizer(serverProperties, (List) webListenerRegistrars.orderedStream().collect(Collectors.toList())); } @Bean @ConditionalOnClass( name = {"org.apache.catalina.startup.Tomcat"} ) public TomcatServletWebServerFactoryCustomizer tomcatServletWebServerFactoryCustomizer(ServerProperties serverProperties) { return new TomcatServletWebServerFactoryCustomizer(serverProperties); } @Bean @ConditionalOnMissingFilterBean({ForwardedHeaderFilter.class}) @ConditionalOnProperty( value = {"server.forward-headers-strategy"}, havingValue = "framework" ) public FilterRegistrationBean<ForwardedHeaderFilter> forwardedHeaderFilter() { ForwardedHeaderFilter filter = new ForwardedHeaderFilter(); FilterRegistrationBean<ForwardedHeaderFilter> registration = new FilterRegistrationBean(filter, new ServletRegistrationBean[0]); registration.setDispatcherTypes(DispatcherType.REQUEST, new DispatcherType[]{DispatcherType.ASYNC, DispatcherType.ERROR}); registration.setOrder(-2147483648); return registration; } public static class BeanPostProcessorsRegistrar implements ImportBeanDefinitionRegistrar, BeanFactoryAware { private ConfigurableListableBeanFactory beanFactory; public BeanPostProcessorsRegistrar() { } public void setBeanFactory(BeanFactory beanFactory) throws BeansException { if (beanFactory instanceof ConfigurableListableBeanFactory) { this.beanFactory = (ConfigurableListableBeanFactory) beanFactory; } } public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) { if (this.beanFactory != null) { this.registerSyntheticBeanIfMissing(registry, "webServerFactoryCustomizerBeanPostProcessor", WebServerFactoryCustomizerBeanPostProcessor.class, WebServerFactoryCustomizerBeanPostProcessor::new); this.registerSyntheticBeanIfMissing(registry, "errorPageRegistrarBeanPostProcessor", ErrorPageRegistrarBeanPostProcessor.class, ErrorPageRegistrarBeanPostProcessor::new); } } private <T> void registerSyntheticBeanIfMissing(BeanDefinitionRegistry registry, String name, Class<T> beanClass, Supplier<T> instanceSupplier) { if (ObjectUtils.isEmpty(this.beanFactory.getBeanNamesForType(beanClass, true, false))) { RootBeanDefinition beanDefinition = new RootBeanDefinition(beanClass, instanceSupplier); beanDefinition.setSynthetic(true); registry.registerBeanDefinition(name, beanDefinition); } } } }
该类使用了以下注解:
@Configuration
:用于定义一个配置类,可用于替换 Spring 中的 xml 配置文件;@Bean
:被 @Configuration 注解的类内部,可以包含有一个或多个被 @Bean 注解的方法,用于构建一个 Bean,并添加到 Spring 容器中;该注解与 spring 配置文件中 等价,方法名与 的 id 或 name 属性等价,方法返回值与 class 属性等价;- 除了 @Configuration 和 @Bean 注解外,该类还使用 5 个 @Conditional 衍生注解:
@ConditionalOnClass({ServletRequest.class})
:判断当前项目是否存在 ServletRequest 这个类,若存在,则该配置类生效。@ConditionalOnWebApplication(type = Type.SERVLET)
:判断当前应用是否是 Web 应用,如果是的话,当前配置类生效。@ConditionalOnClass(name = {"org.apache.catalina.startup.Tomcat"})
:判断是否存在 Tomcat 类,若存在则该方法生效。@ConditionalOnMissingFilterBean({ForwardedHeaderFilter.class})
:判断容器中是否有 ForwardedHeaderFilter 这个过滤器,若不存在则该方法生效。@ConditionalOnProperty(value = {"server.forward-headers-strategy"},havingValue = "framework")
:判断配置文件中是否存在 server.forward-headers-strategy = framework,若不存在则该方法生效。
那么我们想要给自动配置类中属性什么样的值又是怎么实现的呢?
ServerProperties 类
ServletWebServerFactoryAutoConfiguration 类还使用了一个 @EnableConfigurationProperties
注解,通过该注解导入了一个 ServerProperties 类,其部分源码如下
@ConfigurationProperties( prefix = "server", ignoreUnknownFields = true ) public class ServerProperties { private Integer port; private InetAddress address; @NestedConfigurationProperty private final ErrorProperties error = new ErrorProperties(); private ServerProperties.ForwardHeadersStrategy forwardHeadersStrategy; private String serverHeader; private DataSize maxHttpHeaderSize = DataSize.ofKilobytes(8L); private Shutdown shutdown; @NestedConfigurationProperty private Ssl ssl; @NestedConfigurationProperty private final Compression compression; @NestedConfigurationProperty private final Http2 http2; private final ServerProperties.Servlet servlet; private final ServerProperties.Tomcat tomcat; private final ServerProperties.Jetty jetty; private final ServerProperties.Netty netty; private final ServerProperties.Undertow undertow; public ServerProperties() { this.shutdown = Shutdown.IMMEDIATE; this.compression = new Compression(); this.http2 = new Http2(); this.servlet = new ServerProperties.Servlet(); this.tomcat = new ServerProperties.Tomcat(); this.jetty = new ServerProperties.Jetty(); this.netty = new ServerProperties.Netty(); this.undertow = new ServerProperties.Undertow(); } .... }
我们看到,ServletWebServerFactoryAutoConfiguration 使用了一个 @EnableConfigurationProperties 注解,通过该注解导入了一个 ServerProperties 类,而 ServerProperties 类上则使用了一个 @ConfigurationProperties 注解。这其实是 Spring Boot 自动配置机制中的通用用法。
自动配置实现步骤
@ConfigurationProperties 注解的作用,是将这个类的所有属性与配置文件中相关的配置进行绑定,以便于获取或修改配置,但是 @ConfigurationProperties 功能是由容器提供的,被它注解的类必须是容器中的一个组件,否则该功能就无法使用。而 @EnableConfigurationProperties 注解的作用正是将指定的类以组件的形式注入到 IOC 容器中,并开启其 @ConfigurationProperties 功能。因此,@ConfigurationProperties + @EnableConfigurationProperties
组合使用,便可以为 XxxProperties 类实现配置绑定功能。整体回顾下:
- SpringBoot启动会加载大量的自动配置类
- 我们看我们需要的功能有没有在SpringBoot默认写好的自动配置类当中
- 我们再来看这个自动配置类中到底配置了哪些组件,(只要我们要用的组件存在在其中,我们就不需要再手动配置了)
- 给容器中自动配置类添加组件的时候,会从properties类中获取某些属性。我们只需要在配置文件中指定这些属性的值即可
而这一切机制都是通过SpringBoot主启动类启动时的自动发现机制实现的。
其实SpringBoot的IOC和DI管理就是通过@Configuration
纯注解方式实现的,提前编写好自动配置类,通过Spring Factories 机制发现导入,然后再读取配置文件中的值并绑定就可以直接使用或修改了。
自动配置类 XxxAutoConfiguration 负责使用 XxxProperties 中属性进行自动配置,而 XxxProperties 则负责将自动配置属性与配置文件的相关配置进行绑定,以便于用户通过配置文件修改默认的自动配置。也就是说,真正限制我们可以在配置文件中配置哪些属性的类就是这些 XxxxProperties 类,它与配置文件中定义的 prefix 关键字开头的一组属性是唯一对应的,这也是为什么我们在yaml这篇Blog【SpringBoot学习笔记 三】Profile多环境配置及配置优先级中提到的servler配置会自动生效。
Spring Boot 中为我们提供了大量的自动配置类 XxxAutoConfiguration 以及 XxxProperties,每个自动配置类 XxxAutoConfiguration 都使用了 @EnableConfigurationProperties 注解,而每个 XxxProperties 上都使用 @ConfigurationProperties 注解,再回顾下我在这篇Blog中【SpringBoot学习笔记 二】YAML格式文件配置方式详解提到的Person配置为什么能被读取,就是因为这其实就是一个手动的配置,我们自己写配置,自己绑定配置与组件
手动写配置
person: name: tml age: 30 pets: -dog -cat -pig car: name: 蔚来es6 url: domain: www.baidu.com location: china
手动注册组件,手动将配置与组件绑定
package com.example.springboot.model; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.context.annotation.PropertySource; import org.springframework.stereotype.Component; import java.util.List; /** * * @Name Person * * @Description * * @author tianmaolin * * @Data 2021/9/27 */ @Component @Data @NoArgsConstructor @AllArgsConstructor @ConfigurationProperties(prefix = "person") @PropertySource(value = "classpath:application.properties") public class Person { //@ConfigurationProperties配置yaml自动JavaBean注入 private String name; private Integer age; private List<String> pets; private Car car; //@Value配置yaml属性引入,SPEL表达式获取值 @Value("${url.domain}") private String url; @Value("${url.location}") private String location; //@Value配置外部配置文件属性绑定 @Value("${spring.application.name}") private String appName; }
总结一下
话不多说,通过学习理解整理了如下的流程图,也就是为什么SpringBoot可以基本实现零配置启动,其实并不是零配置,而是Spring提前给我们做好了一切而已。