1. 程式人生 > >SpringBoot自動配置原理

SpringBoot自動配置原理

override activity eve rop 哪些 isf 代碼 文件 multi

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的原理,我們才能隨心所欲的運用。

SpringBoot自動配置原理