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類裏面規定好的。在這個類裏面會與配置文件裏面的值進行綁定。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章