SpringBoot 自动配置初探

springboot的一大特点就是自动配置,本笔记将记录springboot的自动配置是如何实现的。将对源码进行跟踪和分析。
2019年07月31日09:16:31
[email protected]

@EnableAutoConfiguration

当我们看到springboot的启动类的时候,会在上部分看到这个类的注解,其中有一个@EnableAutoConfiguration的注解 AutoConfiguration的出现,告诉我们这个一定和自动 Auto和配置 Configuration 有关。

按住 ctrl进入这个注解,我们可以看见这个注解的源代码,下面截取头部部分。

@Documented
@Inherited
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {

@Import(AutoConfigurationImportSelector.class)

这个注解的作用,是利用这个选择器 ...Selector.class来给容器中导入一些组件。

进入这个注解,下拉。我们可以看见在这个类里面有如下这样一个方法。

	@Override
	public String[] selectImports(AnnotationMetadata annotationMetadata) {...}

meta,一般在计算机中的意思是 元 元数据(Metadata)的元。

这个方法,返回的是字符串数组。

selectImports

我们仔细看源码的返回值:

return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());

利用 StringUtils这个spring的工具类,将一个autoConfigurationEntry.getConfigurations()传回的数据转换成了字符串数组。

autoConfigurationEntry

我们来观察这个autoConfigurationEntry

AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(autoConfigurationMetadata,
				annotationMetadata);

这个AutoConfigurationEntry类是怎么定义的呢?

protected static class AutoConfigurationEntry {

   private final List<String> configurations;

   private final Set<String> exclusions;

   private AutoConfigurationEntry() {
      this.configurations = Collections.emptyList();
      this.exclusions = Collections.emptySet();
   }

一目了然,AutoConfigurationEntry 里面包含着String类型的集合 List<String> configurations

回到 selectImports这个方法。

这个方法的作用,就是想要获取到这一系列的配置(configurations)

在selectImports这个方法内部

AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(autoConfigurationMetadata,
      annotationMetadata);

这句话告诉我们,autoConfigurationEntry也就是想要的这些configurations,必须通过 getAutoConfigurationEntry这个方法获取,我们点进来一看,有这样一句话:

List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);

继续点,找到这样一个方法:

getCandidateConfigurations

protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
   List<String> configurations = SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(),
         getBeanClassLoader());
   Assert.notEmpty(configurations, "No auto configuration classes found in META-INF/spring.factories. If you "
         + "are using a custom packaging, make sure that file is correct.");
   return configurations;
}

其中,下面这个方法才是最主要的。

List<String> configurations = SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(),
         getBeanClassLoader());

SpringFactoriesLoader这个类的作用就是扫描所有jar包类路径下的一个文件,在这个类的源码首行。

public final class SpringFactoriesLoader {
    public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";

"META-INF/spring.factories"就是需要扫描的路径和文件。

那么扫描这个文件的作用是什么?

loadFactoryNames

在上面提及到的 loadFactoryNames这个方法里面,我们可以继续点进去查看。

public static List<String> loadFactoryNames(Class<?> factoryClass, @Nullable ClassLoader classLoader) {
   String factoryClassName = factoryClass.getName();
   return loadSpringFactories(classLoader).getOrDefault(factoryClassName, Collections.emptyList());
}

同样,点入 loadSpringFactories(classLoader).getOrDefault(factoryClassName, Collections.emptyList());中的 loadSpringFactories(classLoader)方法。

在这个方法里面,代码的语义可以理解为。

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 factoryClassName = ((String) entry.getKey()).trim();
					for (String factoryName : StringUtils.commaDelimitedListToStringArray((String) entry.getValue())) {
						result.add(factoryClassName, factoryName.trim());
					}
				}
			}

得到这些方法的url,最后将每个方法遍历。

将这些文件的内容整合成一个properties对象。

String factoryClassName = ((String) entry.getKey()).trim();

每一个url都会被获取它的值,最后被加载返回值result中,这个result中,就包含了spring容器中需要的一些组件。

回到getCandidateConfigurations方法。

List<String> configurations = SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(),
				getBeanClassLoader());

注意看 loadFactoryNames 的第一个入参。 getSpringFactoriesLoaderFactoryClass()点进去之后。

protected Class<?> getSpringFactoriesLoaderFactoryClass() {
   return EnableAutoConfiguration.class;
}

可以清楚的观察到,EnableAutoConfiguration.class的存在。从properties中获取到EnableAutoConfiguration.class这个类类名对应的值,把他们添加在容器中。

Jar包下的spring.factories

我们可以点开一个jar包,查看里面的内容。在这里插入图片描述
但是查看内容之后,并没有EnableAutoConfiguration这个类对应的值。

再往下看一个jar包 spring-boot-autoconfigure-2.1.6.RELEASE.jar ,点开 spring.factories

[外链图片转存失败(img-9G7Y1Zob-1564535868320)(/Users/apple/Desktop/MarkDown笔记/文章图片/image-20190731082946075.png)]

我们可以清楚的看到,这个jar包配置了 EnableAutoConfiguration,那么下面的这些组件,都会被自动配置到spring容器中。

回顾一下SpringFactoriesLoader类里面,属性规定了我们要去 "META-INF/spring.factories"jar包类路径下配置所有开启 EnableAutoConfiguration的值,把他们加入到容器当中去。

结合上面 spring.factories文件,这个流程更加的直观。

我们把这个jar包开启自动配置之下的值全部拷在下面。

# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration,\
org.springframework.boot.autoconfigure.aop.AopAutoConfiguration,\
org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration,\
org.springframework.boot.autoconfigure.batch.BatchAutoConfiguration,\
org.springframework.boot.autoconfigure.cache.CacheAutoConfiguration,\
org.springframework.boot.autoconfigure.cassandra.CassandraAutoConfiguration,\
org.springframework.boot.autoconfigure.cloud.CloudServiceConnectorsAutoConfiguration,\
org.springframework.boot.autoconfigure.context.ConfigurationPropertiesAutoConfiguration,\
org.springframework.boot.autoconfigure.context.MessageSourceAutoConfiguration,\
org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration,\
org.springframework.boot.autoconfigure.couchbase.CouchbaseAutoConfiguration,\
org.springframework.boot.autoconfigure.dao.PersistenceExceptionTranslationAutoConfiguration,\
org.springframework.boot.autoconfigure.data.cassandra.CassandraDataAutoConfiguration,\
org.springframework.boot.autoconfigure.data.cassandra.CassandraReactiveDataAutoConfiguration,\
org.springframework.boot.autoconfigure.data.cassandra.CassandraReactiveRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.cassandra.CassandraRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.couchbase.CouchbaseDataAutoConfiguration,\
org.springframework.boot.autoconfigure.data.couchbase.CouchbaseReactiveDataAutoConfiguration,\
org.springframework.boot.autoconfigure.data.couchbase.CouchbaseReactiveRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.couchbase.CouchbaseRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.elasticsearch.ElasticsearchAutoConfiguration,\
org.springframework.boot.autoconfigure.data.elasticsearch.ElasticsearchDataAutoConfiguration,\
org.springframework.boot.autoconfigure.data.elasticsearch.ElasticsearchRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.jdbc.JdbcRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.jpa.JpaRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.ldap.LdapRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.mongo.MongoDataAutoConfiguration,\
org.springframework.boot.autoconfigure.data.mongo.MongoReactiveDataAutoConfiguration,\
org.springframework.boot.autoconfigure.data.mongo.MongoReactiveRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.mongo.MongoRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.neo4j.Neo4jDataAutoConfiguration,\
org.springframework.boot.autoconfigure.data.neo4j.Neo4jRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.solr.SolrRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration,\
org.springframework.boot.autoconfigure.data.redis.RedisReactiveAutoConfiguration,\
org.springframework.boot.autoconfigure.data.redis.RedisRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.rest.RepositoryRestMvcAutoConfiguration,\
org.springframework.boot.autoconfigure.data.web.SpringDataWebAutoConfiguration,\
org.springframework.boot.autoconfigure.elasticsearch.jest.JestAutoConfiguration,\
org.springframework.boot.autoconfigure.elasticsearch.rest.RestClientAutoConfiguration,\
org.springframework.boot.autoconfigure.flyway.FlywayAutoConfiguration,\
org.springframework.boot.autoconfigure.freemarker.FreeMarkerAutoConfiguration,\
org.springframework.boot.autoconfigure.gson.GsonAutoConfiguration,\
org.springframework.boot.autoconfigure.h2.H2ConsoleAutoConfiguration,\
org.springframework.boot.autoconfigure.hateoas.HypermediaAutoConfiguration,\
org.springframework.boot.autoconfigure.hazelcast.HazelcastAutoConfiguration,\
org.springframework.boot.autoconfigure.hazelcast.HazelcastJpaDependencyAutoConfiguration,\
org.springframework.boot.autoconfigure.http.HttpMessageConvertersAutoConfiguration,\
org.springframework.boot.autoconfigure.http.codec.CodecsAutoConfiguration,\
org.springframework.boot.autoconfigure.influx.InfluxDbAutoConfiguration,\
org.springframework.boot.autoconfigure.info.ProjectInfoAutoConfiguration,\
org.springframework.boot.autoconfigure.integration.IntegrationAutoConfiguration,\
org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration,\
org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration,\
org.springframework.boot.autoconfigure.jdbc.JdbcTemplateAutoConfiguration,\
org.springframework.boot.autoconfigure.jdbc.JndiDataSourceAutoConfiguration,\
org.springframework.boot.autoconfigure.jdbc.XADataSourceAutoConfiguration,\
org.springframework.boot.autoconfigure.jdbc.DataSourceTransactionManagerAutoConfiguration,\
org.springframework.boot.autoconfigure.jms.JmsAutoConfiguration,\
org.springframework.boot.autoconfigure.jmx.JmxAutoConfiguration,\
org.springframework.boot.autoconfigure.jms.JndiConnectionFactoryAutoConfiguration,\
org.springframework.boot.autoconfigure.jms.activemq.ActiveMQAutoConfiguration,\
org.springframework.boot.autoconfigure.jms.artemis.ArtemisAutoConfiguration,\
org.springframework.boot.autoconfigure.groovy.template.GroovyTemplateAutoConfiguration,\
org.springframework.boot.autoconfigure.jersey.JerseyAutoConfiguration,\
org.springframework.boot.autoconfigure.jooq.JooqAutoConfiguration,\
org.springframework.boot.autoconfigure.jsonb.JsonbAutoConfiguration,\
org.springframework.boot.autoconfigure.kafka.KafkaAutoConfiguration,\
org.springframework.boot.autoconfigure.ldap.embedded.EmbeddedLdapAutoConfiguration,\
org.springframework.boot.autoconfigure.ldap.LdapAutoConfiguration,\
org.springframework.boot.autoconfigure.liquibase.LiquibaseAutoConfiguration,\
org.springframework.boot.autoconfigure.mail.MailSenderAutoConfiguration,\
org.springframework.boot.autoconfigure.mail.MailSenderValidatorAutoConfiguration,\
org.springframework.boot.autoconfigure.mongo.embedded.EmbeddedMongoAutoConfiguration,\
org.springframework.boot.autoconfigure.mongo.MongoAutoConfiguration,\
org.springframework.boot.autoconfigure.mongo.MongoReactiveAutoConfiguration,\
org.springframework.boot.autoconfigure.mustache.MustacheAutoConfiguration,\
org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration,\
org.springframework.boot.autoconfigure.quartz.QuartzAutoConfiguration,\
org.springframework.boot.autoconfigure.reactor.core.ReactorCoreAutoConfiguration,\
org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration,\
org.springframework.boot.autoconfigure.security.servlet.SecurityRequestMatcherProviderAutoConfiguration,\
org.springframework.boot.autoconfigure.security.servlet.UserDetailsServiceAutoConfiguration,\
org.springframework.boot.autoconfigure.security.servlet.SecurityFilterAutoConfiguration,\
org.springframework.boot.autoconfigure.security.reactive.ReactiveSecurityAutoConfiguration,\
org.springframework.boot.autoconfigure.security.reactive.ReactiveUserDetailsServiceAutoConfiguration,\
org.springframework.boot.autoconfigure.sendgrid.SendGridAutoConfiguration,\
org.springframework.boot.autoconfigure.session.SessionAutoConfiguration,\
org.springframework.boot.autoconfigure.security.oauth2.client.servlet.OAuth2ClientAutoConfiguration,\
org.springframework.boot.autoconfigure.security.oauth2.client.reactive.ReactiveOAuth2ClientAutoConfiguration,\
org.springframework.boot.autoconfigure.security.oauth2.resource.servlet.OAuth2ResourceServerAutoConfiguration,\
org.springframework.boot.autoconfigure.security.oauth2.resource.reactive.ReactiveOAuth2ResourceServerAutoConfiguration,\
org.springframework.boot.autoconfigure.solr.SolrAutoConfiguration,\
org.springframework.boot.autoconfigure.task.TaskExecutionAutoConfiguration,\
org.springframework.boot.autoconfigure.task.TaskSchedulingAutoConfiguration,\
org.springframework.boot.autoconfigure.thymeleaf.ThymeleafAutoConfiguration,\
org.springframework.boot.autoconfigure.transaction.TransactionAutoConfiguration,\
org.springframework.boot.autoconfigure.transaction.jta.JtaAutoConfiguration,\
org.springframework.boot.autoconfigure.validation.ValidationAutoConfiguration,\
org.springframework.boot.autoconfigure.web.client.RestTemplateAutoConfiguration,\
org.springframework.boot.autoconfigure.web.embedded.EmbeddedWebServerFactoryCustomizerAutoConfiguration,\
org.springframework.boot.autoconfigure.web.reactive.HttpHandlerAutoConfiguration,\
org.springframework.boot.autoconfigure.web.reactive.ReactiveWebServerFactoryAutoConfiguration,\
org.springframework.boot.autoconfigure.web.reactive.WebFluxAutoConfiguration,\
org.springframework.boot.autoconfigure.web.reactive.error.ErrorWebFluxAutoConfiguration,\
org.springframework.boot.autoconfigure.web.reactive.function.client.ClientHttpConnectorAutoConfiguration,\
org.springframework.boot.autoconfigure.web.reactive.function.client.WebClientAutoConfiguration,\
org.springframework.boot.autoconfigure.web.servlet.DispatcherServletAutoConfiguration,\
org.springframework.boot.autoconfigure.web.servlet.ServletWebServerFactoryAutoConfiguration,\
org.springframework.boot.autoconfigure.web.servlet.error.ErrorMvcAutoConfiguration,\
org.springframework.boot.autoconfigure.web.servlet.HttpEncodingAutoConfiguration,\
org.springframework.boot.autoconfigure.web.servlet.MultipartAutoConfiguration,\
org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration,\
org.springframework.boot.autoconfigure.websocket.reactive.WebSocketReactiveAutoConfiguration,\
org.springframework.boot.autoconfigure.websocket.servlet.WebSocketServletAutoConfiguration,\
org.springframework.boot.autoconfigure.websocket.servlet.WebSocketMessagingAutoConfiguration,\
org.springframework.boot.autoconfigure.webservices.WebServicesAutoConfiguration,\
org.springframework.boot.autoconfigure.webservices.client.WebServiceTemplateAutoConfiguration

仔细观察,每一个 xxAutoConfiguration 类都是容器中的自动配置类。都加入到容器中去,使用他们来做更多的自动配置功能。

例子 HttpEncodingAutoConfiguration

在上面 我们可以找到 org.springframework.boot.autoconfigure.web.servlet.HttpEncodingAutoConfiguration,\

我们去文件中找到这类。

@Configuration
@EnableConfigurationProperties(HttpProperties.class)
@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET)
@ConditionalOnClass(CharacterEncodingFilter.class)
@ConditionalOnProperty(prefix = "spring.http.encoding", value = "enabled", matchIfMissing = true)
public class HttpEncodingAutoConfiguration {

   private final HttpProperties.Encoding properties;

   public HttpEncodingAutoConfiguration(HttpProperties properties) {
      this.properties = properties.getEncoding();
   }

   @Bean
   @ConditionalOnMissingBean
   public CharacterEncodingFilter characterEncodingFilter() {
      CharacterEncodingFilter filter = new OrderedCharacterEncodingFilter();
      filter.setEncoding(this.properties.getCharset().name());
      filter.setForceRequestEncoding(this.properties.shouldForce(Type.REQUEST));
      filter.setForceResponseEncoding(this.properties.shouldForce(Type.RESPONSE));
      return filter;
   }

   @Bean
   public LocaleCharsetMappingsCustomizer localeCharsetMappingsCustomizer() {
      return new LocaleCharsetMappingsCustomizer(this.properties);
   }

   private static class LocaleCharsetMappingsCustomizer
         implements WebServerFactoryCustomizer<ConfigurableServletWebServerFactory>, Ordered {

      private final HttpProperties.Encoding properties;

      LocaleCharsetMappingsCustomizer(HttpProperties.Encoding properties) {
         this.properties = properties;
      }

      @Override
      public void customize(ConfigurableServletWebServerFactory factory) {
         if (this.properties.getMapping() != null) {
            factory.setLocaleCharsetMappings(this.properties.getMapping());
         }
      }

      @Override
      public int getOrder() {
         return 0;
      }

   }

}

首先是注解的解析:

@Configuration
@EnableConfigurationProperties(HttpProperties.class)
@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET)
@ConditionalOnClass(CharacterEncodingFilter.class)
@ConditionalOnProperty(prefix = "spring.http.encoding", value = "enabled", matchIfMissing = true)
  • @Configuration 表示,这是一个配置类的方式向容器中添加组件 @Bean的注解。
  • @EnableConfigurationProperties(HttpProperties.class)指定启动 HttpProperties.class这个类的ConfigurationProperties功能。
  • @ConditionalOnWebApplication根据当前程序是不是web应用,如果是,则生效。
  • @ConditionalOnClass判断当期项目有没有CharacterEncodingFilter.class的存在。这个是springmvc中解决乱码的过滤器
  • @ConditionalOnProperty判断配置文件中是否存在"spring.http.encoding"的配置,matchIfMissing = true如果不存在,也生效。也就是说,如果在我们的配置文件中,没有 spring.http.encoding.enabled=true那么这个注解也是生效的。

HttpEncodingAutoConfiguration这个自动配置类生效之后,该类会在容器中添加如下两个组件 @Bean

	@Bean
	@ConditionalOnMissingBean
	public CharacterEncodingFilter characterEncodingFilter() {
===================================================
	@Bean
	public LocaleCharsetMappingsCustomizer localeCharsetMappingsCustomizer() {

这些组件的值,也就来自于开头注解中的配置文件。作为容器中的一个bean,程序上下文在执行这个方法的时候,自动将里面的属性赋值。

HttpProperties

源码如下:

@ConfigurationProperties(prefix = "spring.http")
public class HttpProperties {

点进来,我们可以看见 @ConfigurationProperties(prefix = "spring.http")的注解。

这个非常好理解,之前也试过这个注解的使用并那他来和 @Value来区分。

这个注解的意思就是指定了配置文件中这个类属性key的前缀,spring会自动从其中取出这个值并吧上下文中这个bean的属性的值进行绑定。

@ConfigurationProperties支持松绑定

这个类下面的属性,也就基本上对应了配置文件中,根据他指定的前缀能取到什么的值。

所有能在配置文件中配置的属性,都是在XXXProperties的类中封装着。

关于字符过滤器,我们在学习springMVC的时候,这些属性是配置在web.xml中的。迁移过来思考一下,就更容易理解。我们可以观察之前的SSM项目中的配置文件对应着思考一下。

比如在这个类里面,我们就能配置:

public static final Charset DEFAULT_CHARSET = StandardCharsets.UTF_8; 
private Charset charset = DEFAULT_CHARSET; 
private Boolean force; 
private Boolean forceRequest; 
private Boolean forceResponse;

仔细看看,是不是都很熟悉。


总结

以上就是springboot的整个精髓,总结如下

  1. springboot启动会加载大量配置类。
  2. 我们在开发需要某个功能的时候,可以先看看springboot里面有没有已经写好的自动配置类。
  3. 再看看自动配置类里面可以为我们配置哪些组件,如果有,我们的配置文件中就不需要提及。
  4. 容器中自动配置类添加组件的时候,会从 properties 类中获取属性,这些属性都是从配置文件中获取的。因为这两个内容是绑定的。
  5. XXXAutoConfiguration 类是自动配置类,这个类会向spring 容器中配置组件,这些组件的属性是在xxxProperties类里面规定好的。在这个类里面会与配置文件里面的值进行绑定。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章