SpringBoot自動配置原理


 

SpringBoot自動配置原理

備注:該SpringBoot自動配置原理不適合java剛入門學者以及不熟悉Spring4+Springmvc+maven的同學

1、當SpringBoot應用啟動的時候,就從主方法里面進行啟動的。

@SpringBootApplication
public class SpringBoot02ConfigAutoconfigApplication {

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

 

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

 

2、@EnableAutoConfiguration作用:

它主要利用了一個

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

@Import(EnableAutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration 

 

3、那么導入了哪些組件呢?

我們來看

EnableAutoConfigurationImportSelector這個類的父類selectImports

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

父類里面規定了一個方法叫selectImports這個方法,查看了selectImports這個方法里面的代碼內容就能知道導入了哪些組件了。

 

在selectImports這個方法里面主要有個configurations,並且這個configurations最終會被返回。

@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()]);
        }

 

這個configurations它是獲取候選的配置。

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

 

這個configurations方法的作用就是利用SpringFactoriesLoader.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));

 

4、那么得到哪些資源呢?

它是掃描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;
    }

 

然后把它們添加在容器中

 

5、按照它的這個意思,來到第二個Springjar包的META-INF下的spring.factories這個文件找到配置所有EnableAutoConfiguration的值加入到Spring容器中

 

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

比如:

 

 

每一個xxxAutoConfiguration類都是容器中的一個組件,並都加入到容器中。

 

加入到容器中之后的作用就是用它們來做自動配置,這就是Springboot自動配置之源,也就是自動配置的開始,只有這些自動配置類進入到容器中以后,接下來這個自動配置類才開始進行啟動

 

6、每一個自動配置類進行自動配置功能

以一個自動配置類

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;
    }

 

所以說配置文件能配置什么就可以參照某一個功能對應的這個屬性類

 

7、這個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只有一個有參構造器,在只有一個有參構造器的情況下,參數的值就會從容器中拿

 

8、而容器中它怎么去拿到呢?

相當於是前面的這個

@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只要把握這幾點:

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

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

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

  4. 給容器中自動配置類添加組件的時候,會從properties類中獲取某些屬性,而這些屬性我們就可以在配置文件指定這些屬性的值

 

以上內容就是SpringBoot自動配置原理的整個精髓,只要掌握了SpringBoot的原理,我們才能隨心所欲的運用。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM