天天看點

springboot 自動配置原理

  1. 當springboot應用啟動的時候,就沖主方法裡面進行啟動的
@SpringBootApplication
public class SpringBoot02ConfigAutoconfigApplication {

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

它主要加載了@SpringBootApplication注解主配置類,這個@SpringBootApplication注解主配置類裡邊最主要的功能就是SpringBooot開啟了一個@EnableAutoConfiguration注解的自動配置功能。

  1. @EnableAutoConfiguration的作用

@EnableAutoConfiguratin,也就是開啟自動配置的意思

它主要利用了一個

EnableAutoConfigurationImportSelector選擇器給Spring容器中來導入一些元件。

@Import(EnableAutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration
           
  1. 那麼導入了哪些元件呢?

    我們來看EnableAutoConfigurationImportSelector這個類的父類selectImports

@Override
public String[] selectImports(AnnotationMetadata annotationMetadata) {
    if (!isEnabled(annotationMetadata)) {
        return NO_IMPORTS;
    }
           

父類裡面規定了一個方法叫selectImports這個方法,檢視了selectImport這個方法裡面的代碼内容就知道導入了哪些元件了。

在selectImport這個方法裡面主要有configuration,并且這個configuration最終會被傳回。

@Override
public String[] selectImports(AnnotationMetadata annotationMetadata) {
    if (!isEnabled(annotationMetadata)) {
        return NO_IMPORTS;
    }
    try {
        AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader
                .loadMetadata(this.beanClassLoader);
        AnnotationAttributes attributes = getAttributes(annotationMetadata);
        List<String> configurations = getCandidateConfigurations(annotationMetadata,
                attributes);
        configurations = removeDuplicates(configurations);
        configurations = sort(configurations, autoConfigurationMetadata);
        Set<String> exclusions = getExclusions(annotationMetadata, attributes);
        checkExcludedClasses(configurations, exclusions);
        configurations.removeAll(exclusions);
        configurations = filter(configurations, autoConfigurationMetadata);
        fireAutoConfigurationImportEvents(configurations, exclusions);
        return configurations.toArray(new String[configurations.size()]);
    }
           

這個configuration它是擷取候選的配置

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

這個configuration方法的作用就是利用SpringFactionsLoader.loadFactoryNames從類路徑下的到一個資源

public static List<String> loadFactoryNames(Class<?> factoryClass, ClassLoader classLoader) {
        String factoryClassName = factoryClass.getName();
        try {
            Enumeration<URL> urls = (classLoader != null ? classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :
                    ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
           
  1. 那麼得到哪些資源呢?

    它是掃描javajar包類路徑下的“META-INF/spring.factories”這個檔案

**
     * The location to look for factories.
     * <p>Can be present in multiple JAR files.
     */
    public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";
           

那麼掃到這些檔案的作用:是把這個檔案的urls拿到之後并把這些urls每一個周遊,最終把這些檔案整成一個properties對象

public static List<String> loadFactoryNames(Class<?> factoryClass, ClassLoader classLoader) {
        String factoryClassName = factoryClass.getName();
        try {
            Enumeration<URL> urls = (classLoader != null ? classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :
                    ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
            List<String> result = new ArrayList<String>();
            while (urls.hasMoreElements()) {
                URL url = urls.nextElement();
                Properties properties = PropertiesLoaderUtils.loadProperties(new UrlResource(url));
                String factoryClassNames = properties.getProperty(factoryClassName);
                result.addAll(Arrays.asList(StringUtils.commaDelimitedListToStringArray(factoryClassNames)));
            }
            return result;
           

然後它從properties對象裡邊擷取一些值,把這些擷取到的值來加載我們最終要傳回的這個結果,這個結果就是我們要交給Spring容器中的所有元件,這相當于這factoryClassName就是我們傳過來的Class的這個類名

而傳過來的Class是調用這個

getSpringFactoriesLoaderFactoryClass()這個方法得到從properties中擷取到EnableAutoConfiguration.class類名對應的值

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

然後把它們添加在容器中

  1. 安照它的這個意思,來到第二個Springjar包的META-INF下的spring.factories這個檔案找到配置所有EnableAutoConfiguration的值加入到Spring容器中

是以說我們容器中最終會添加很多的類

比如

springboot 自動配置原理
springboot 自動配置原理
springboot 自動配置原理

每一個xxxAutoConfigutation類都是容器中的一個元件,并都加入到容器中

加入到容器中之後的作用就是用他們類做自動配置,這就是springboot自動配置之源,也就是自動配置的開始,隻有這些自動配置類進入到容器中之後,接下來這個自動配置來才開始進行啟動

  1. 每個自動配置類進行自動配置功能

    以一個自動配置類HttpEncodingAutoConfiguration(HTTP的編碼自動配置)為例來解釋SpringBoot的自動配置之原理:

1). 這個HttpEncodingAutoConfiguration類上面标注了一大堆的注解

@Configuration    
//表示這是一個配置類,類似于以前編寫的配置檔案一樣,也可以給容器中添加元件
@EnableConfigurationProperties(HttpEncodingProperties.class) 
//啟用ConfigurationProperties功能:
//這個ConfigurationProperties裡面引入了一個類,這個類就是啟用指定類的ConfigurationProperties功能
//有了這個@EnableConfigurationPropertie注解以後相當于把配置檔案中對應值就和這個HttpEncodingProperties.class類綁定起來了。

@ConditionalOnWebApplication 
//這個注解的意思就是判斷目前是不是web應用,@Conditional是spring底層,意思就是根據不同的條件,來進行自己不同的條件判斷,如果滿足指定的條件,那麼整個配置類裡邊的配置才會生效。

@ConditionalOnClass(CharacterEncodingFilter.class)
//看這個類裡邊有沒有這個過濾器,就是判斷目前項目裡邊有沒有CharacterEncodingFilter這個類,這個CharacterEncodingFilter類是Springmvc中亂碼解決的過濾器。

@ConditionalOnProperty(prefix = "spring.http.encoding", value = "enabled", matchIfMissing = true)//@ConditionalOnProperty注解是來判斷配置檔案中是否存在某個配置,就是是否存在spring.http.encoding.enabled這個配置,matchIfMissing的意思就是如果不存在也認為這個判斷是正确的
//即使配置檔案中不配置spring.http.encoding.enabled=true這個屬性,也是預設生效的
public class HttpEncodingAutoConfiguration {

 

點進去HttpEncodingProperties這個類,發現這個HttpEncodingProperties類上面标注了@ConfigurationProperties注解

@ConfigurationProperties(prefix = "spring.http.encoding") 
//從配置檔案中擷取指定的值和bean的屬性進行綁定
public class HttpEncodingProperties {
    public static final Charset DEFAULT_CHARSET = Charset.forName("UTF-8");
           

是以說配置檔案中該配置什麼,我們就按照它的這個旨意,它要配spring.http.encoding這個屬性,這個屬性裡邊能配置什麼值,就對應HttpEncodingProperties這個類來配置,所有的配置檔案中能配置的屬性都是在xxx.Properties類中封裝着

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

    public static final Charset DEFAULT_CHARSET = Charset.forName("UTF-8");

    /**
     * Charset of HTTP requests and responses. Added to the "Content-Type" header if not
     * set explicitly.
     */
    private Charset charset = DEFAULT_CHARSET;

    /**
     * Force the encoding to the configured charset on HTTP requests and responses.
     */
    private Boolean force;

    /**
     * Force the encoding to the configured charset on HTTP requests. Defaults to true
     * when "force" has not been specified.
     */
    private Boolean forceRequest;

    /**
     * Force the encoding to the configured charset on HTTP responses.
     */
    private Boolean forceResponse;

    /**
     * Locale to Encoding mapping.
     */
    private Map<Locale, Charset> mapping;

    public Charset getCharset() {
        return this.charset;
    }

    public void setCharset(Charset charset) {
        this.charset = charset;
    }

    public boolean isForce() {
        return Boolean.TRUE.equals(this.force);
    }

    public void setForce(boolean force) {
        this.force = force;
    }

    public boolean isForceRequest() {
        return Boolean.TRUE.equals(this.forceRequest);
    }

    public void setForceRequest(boolean forceRequest) {
        this.forceRequest = forceRequest;
    }

    public boolean isForceResponse() {
        return Boolean.TRUE.equals(this.forceResponse);
    }

    public void setForceResponse(boolean forceResponse) {
        this.forceResponse = forceResponse;
    }

    public Map<Locale, Charset> getMapping() {
        return this.mapping;
    }

    public void setMapping(Map<Locale, Charset> mapping) {
        this.mapping = mapping;
    }
           

是以說配置檔案能配置什麼就可以參照某一個功能對應的這個屬性類

  1. 這個HttpEncodingProperties類就是根據目前不同的條件判斷,決定這個配置類是否生效

    如果一旦生效了,所有的配置類都成功了,就給容器中添加各種元件,這些元件的屬性是從對應的properties類中擷取的,而這properties類裡邊的每一個屬性又是和配置檔案綁定的

@Bean  
    //給容器中添加一個元件。
    @ConditionalOnMissingBean(CharacterEncodingFilter.class) 
    //添加一個我們自己來new這個CharacterEncodingFilter,把這個filter添加過去,但是注意這個filter裡邊要擷取字元集的名字(filter.setEncoding(this.properties.getCharset().name());),你是UTF8編碼還是什麼編碼,它要從properties中進行擷取,意思就是這個元件的某些值需要從properties中擷取
    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;
    }
           

我們可以再深入的看一下properties

private final HttpEncodingProperties properties; 
//它已經和SpringBoot配置檔案進行映射了。
           

我們看到properties是

HttpEncodingProperties,也就是說HttpEncodingProperties這個對象的值它是擷取配置檔案的值的,是以我們在配置這個filter到底要用什麼編碼的時候是從properties擷取的。

而且值得注意的是:

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

    private final HttpEncodingProperties properties;
    //隻有一個有參構造器
    public HttpEncodingAutoConfiguration(HttpEncodingProperties properties) {
        this.properties = properties;
    }
           

這個HttpEncodingAutoConfiguration隻有一個有參構造器,在隻有一個有參構造器的情況下,參數的值就會從容器中拿

  1. 而容器中它怎麼去拿到呢

相當于是前面的這個

@EnableConfigurationProperties(HttpEncodingProperties.class) 注解,這個@EnableConfigurationProperties注解的作用就是把HttpEncodingProperties.class和配置檔案進行綁定起來并把HttpEncodingProperties加入到容器中。

接下來這個自動配置類,通過一個有參構造器把這個屬性拿到,而這個屬性已經和SpringBoot映射了,接下來要用什麼編碼,就是拿到HttpEncodingProperties這個類裡邊的屬性。

是以SpringBoot能配置什麼,它要設定編碼,它是擷取properties裡邊getCharset裡邊的name值。

filter.setEncoding(this.properties.getCharset().name());

是以就以此類推,配置一個Spring配置,就可以照着HttpEncodingProperties這裡邊的來配置。

比如在application.properties配置檔案下配置一個http.encoding.enabled屬性:

spring.http.encoding.enabled=true   //能配置這個就相當于是我們之前的判斷屬性
           

還能配置其他的一些屬性。

比如:

spring.http.encoding.charset=UTF-8
           

是以我們能夠配置哪些屬性,都是來源于這個功能的properties類

有了這個自動配置類,自動配置類就給容器中添加這個filter,然後這個filter就會起作用了。

用好SpringBoot隻要把握這幾點:

SpringBoot啟動會加載大量的自動配置類

所要做的就是我們需要的功能SpringBoot有沒有幫我們寫好的自動配置類:

如果有就再來看這個自動配置類中到底配置了哪些元件,Springboot自動配置類裡邊隻要我們要用的元件有,我們就不需要再來配置了,但是如果說沒有我們所需要的元件,那麼我們就需要自己來寫一個配置類來把我們相應的元件配置起來。

給容器中自動配置類添加元件的時候,會從properties類中擷取某些屬性,而這些屬性我們就可以在配置檔案指定這些屬性的值