前言
我們知道 Spring Boot 能快速的搭建起一個應用,簡化了大量的配置過程,那到底有多"簡"呢?
我們通過一個例子來說明,平時我們通過 Spring
和Spring MVC
搭建一個helloword
的 Web 應用,需要做以下工作:
-
- 配置 pom.xml 新增
Spring
、Spring MVC
框架的依賴,同時還需要考慮這些不同的框架的不同版本是否存在不相容的問題。
- 配置 pom.xml 新增
-
- 配置 Web.xml,載入 Spring、Spring MVC。
-
- 配置 Spring 。
-
- 配置 Spring MVC。
-
- 編寫業務邏輯程式碼。
而使用 Spring Boot 搭建的話,需要做以下工作:
-
- 配置 pom.xml 繼承 Spring Boot 的
pom.xml
,新增 Web 起步依賴。
- 配置 pom.xml 繼承 Spring Boot 的
-
- 建立啟動引導類。
-
- 編寫業務邏輯程式碼。
單從步驟數量上看就知道通過 Spring、Spring MVC 搭建比通過 Spring Boot
搭建更復雜,需要編寫大量的配置,這還僅僅是在很少框架和 Spring 整合情況下,如果需要將多個第三方框架和 Spring 整合,恐怕就要陷入"配置地獄"了,此外這些配置基本都是固化的,也就是搭建新的應用,你仍然需要再次編寫相同的配置資訊,特別是在微服務這麼火的當下,一個應用可能由十幾個甚至幾十個小型服務無組成,如果每個小型服務都重複的做著這些配置工作......。
那有沒有什麼辦法解決這個局面呢?答案是有的,那就是使用 Spring Boot
,上從上面的例子就可以發現,使用 Spring Boot
的最大優點就是減少了配置的工作,那麼是不是說使用 Spring Boot
就不需要這些配置過程了?當然不是,而是 Spring Boot
幫我們把這些工作給做了。
那 Spring Boot 是如何幫我們把這些配置工作給做了呢?這就是本文需要探討的問題了,在探討之前,我們需要了解兩個概念起步依賴和自動配置,這裡暫且知道這兩個東西是Spring Boot
的核心、是Spring Boot
的精華所在、是我們不需要再進行大量配置工作的原因所在就行了。
起步依賴
起步依賴說白了就是Spring Boot
通過對常用的依賴進行再一次封裝,例如我們平時需要搭建一個Web
應用的時候,一般都會匯入以下幾個依賴:
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.1.3.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<version>5.1.3.RELEASE</version>
</dependency>
複製程式碼
也就是需要將spring-web
和spring mvc
分別匯入,而使用Spring Boot
的話只需要匯入一個:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
複製程式碼
也就是隻需要匯入一個名為web
的起步依賴即可,我們點spring-boot-starter-web
進去可以看到,其實這個起步依賴整合了常用的 web 依賴,如下:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
<version>2.1.4.RELEASE</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-json</artifactId>
<version>2.1.4.RELEASE</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
<version>2.1.4.RELEASE</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.hibernate.validator</groupId>
<artifactId>hibernate-validator</artifactId>
<version>6.0.16.Final</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<version>5.1.6.RELEASE</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.1.6.RELEASE</version>
<scope>compile</scope>
</dependency>
複製程式碼
也就是前面所說的,Spring Boot的起步依賴說白了就是對常用的依賴進行再一次封裝,方便我們引入,簡化了 pom.xml 配置,但是更重要的是將依賴的管理交給了 Spring Boot,我們無需關注不同的依賴的不同版本是否存在衝突的問題,Spring Boot 都幫我們考慮好了,我們拿來用即可!
在使用 Spring Boot 的起步依賴之前,我們需要在pom.xml
中新增配置:
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.4.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
複製程式碼
即讓pom.xml
繼承 Spring Boot 的pom.xml
,而 Spring Boot 的pom.xml
裡面定義了常用的框架的依賴以及相應的版本號。
總結一下 Spring Boot 的起步依賴的優點:
-
- 無需考慮不同框架的不同版本的衝突問題。
-
- 簡化了
pom.xml
配置。
- 簡化了
自動配置
如果將開發一個應用比喻成裝修房子的過程,那麼 Spring Boot 就像是一個全能型公司一樣存在,而起步依賴可以比喻成購買裝修用品的過程,自動配置比喻成用裝修用品進行裝修的過程。
我們可以通過 Spring Boot 的起步依賴獲取到你想要的塗料、瓷磚、裝飾品等, Spring Boot 公司會根據最佳的組合將這些裝修用品打包好給我們,我們無需考慮各種裝修用品是否搭配、是否衝突等問題。
通過起步依賴我們獲取到了想要的裝修用品,那接下來我們需要做的就是進行裝修了,前面我們說過 Spring Boot 就像一個全能型公司一樣,所以我們在他那裡購買裝修用品之後,他不僅將裝修用品送上門還會幫我們完成裝修(自動配置),讓我們享受一站式的服務,從購買裝飾品(起步依賴)到裝修完成(自動配置)都不用我們考慮,我們只需要在裝修完成之後入住(編寫自己的業務邏輯程式碼)即可。
說了這麼多,我們還不知道Spring Boot
是如何完成自動配置的,接下來我們來分析 Spring Boot 神奇的自動配置!
首先我們知道 Spring Boot 啟動需要有一個啟動引導類,這個類除了是應用的入口之外,還發揮著配置的 Spring Boot 的重要作用。下面是一個簡單的啟動引導類:
@SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}
複製程式碼
我們發現有一個名為@SpringBootApplication
的註解,點選進去可以發現,這個註解發揮著多個註解的作用:
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(
excludeFilters = {@Filter(
type = FilterType.CUSTOM,
classes = {TypeExcludeFilter.class}
), @Filter(
type = FilterType.CUSTOM,
classes = {AutoConfigurationExcludeFilter.class}
)}
)
複製程式碼
這裡簡要的說下@SpringBootConfiguration
和@ComponentScan
註解。前者實質為@Configuration
註解,這個註解相比大家都接觸過,也就是起到宣告這個類為配置類的作用,而後者起到開啟自動掃描元件的作用。
這裡需要重點分析的是@EnableAutoConfiguration
這個註解,這個註解的作用是開啟 Spring Boot 的自動配置功能,我們來分析一下它是如何開啟的,點選進去可以看到:
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import({AutoConfigurationImportSelector.class})
public @interface EnableAutoConfiguration {
String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";
Class<?>[] exclude() default {};
String[] excludeName() default {};
}
複製程式碼
@EnableAutoConfiguration
這個註解同樣發揮著多個註解的功能,我們重點分析@Import({AutoConfigurationImportSelector.class})
這個註解,我們知道@import
的作用是將元件新增到 Spring 容器中,而在這裡即是將AutoConfigurationImportSelector
這個元件新增到 Spring 容器中。
我們進一步分析AutoConfigurationImportSelector
protected AutoConfigurationImportSelector.AutoConfigurationEntry getAutoConfigurationEntry(AutoConfigurationMetadata autoConfigurationMetadata, AnnotationMetadata annotationMetadata) {
if (!this.isEnabled(annotationMetadata)) {
return EMPTY_ENTRY;
} else {
AnnotationAttributes attributes = this.getAttributes(annotationMetadata);
List<String> configurations = this.getCandidateConfigurations(annotationMetadata, attributes);
configurations = this.removeDuplicates(configurations);
Set<String> exclusions = this.getExclusions(annotationMetadata, attributes);
this.checkExcludedClasses(configurations, exclusions);
configurations.removeAll(exclusions);
configurations = this.filter(configurations, autoConfigurationMetadata);
this.fireAutoConfigurationImportEvents(configurations, exclusions);
return new AutoConfigurationImportSelector.AutoConfigurationEntry(configurations, exclusions);
}
}
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
List<String> configurations = SpringFactoriesLoader.loadFactoryNames(this.getSpringFactoriesLoaderFactoryClass(), this.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;
}
複製程式碼
有一個名為getAutoConfigurationEntry
的方法,這個方法發揮的作用是掃描ClassPath
下的所有jar
包的spring.factories
檔案,將spring.factories
檔案key
為EnableAutoConfiguration
的所有值取出,然後這些值其實是類的全限定名,也就是自動配置類的全限定名,然後 Spring Boot 通過這些全限定名進行類載入(反射),將這些自動配置類新增到 Spring 容器中。
那這些自動配置類有哪些?發揮什麼作用呢?我們接著往下看,我們找到一個名為spring-boot-autoconfigure-2.1.4.RELEASE.jar
的 jar 包,開啟它的spring.factories
檔案,發現這個檔案有key
為EnableAutoConfiguration
的鍵值對
# 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,\
......
複製程式碼
也就是這個jar
包有自動配置類,可以發現這些自動配置配都是以xxxAutoConfiguration
的命名規則來取名的,這些自動配置類包含我了們常用的框架的自動配置類,比如aop
、elasticsearch
、redis
和web
等等,基本能滿足我們日常開發的需求。
那這些自動配置類又是如何發揮配置作用的呢,我們取一個較為簡單的配置類進行分析,名為HttpEncodingAutoConfiguration
,它的部分程式碼如下:
@Configuration //宣告這個類為配置類
@EnableConfigurationProperties({HttpProperties.class}) //開啟ConfigurationProperties功能,同時將配置檔案和HttpProperties.class繫結起來
@ConditionalOnWebApplication( //只有在web應用下自動配置類才生效
type = Type.SERVLET
)
@ConditionalOnClass({CharacterEncodingFilter.class}) //只有存在CharacterEncodingFilter.class情況下 自動配置類才生效
@ConditionalOnProperty( //判斷配置檔案是否存在某個配置spring.http.encoding,如果存在其值為enabled才生效,如果不存在這個配置類也生效。
prefix = "spring.http.encoding",
value = {"enabled"},
matchIfMissing = true
)
//將字元編碼過濾器元件新增到 Spring 容器中
@Bean
@ConditionalOnMissingBean
public CharacterEncodingFilter characterEncodingFilter() {
CharacterEncodingFilter filter = new OrderedCharacterEncodingFilter();
filter.setEncoding(this.properties.getCharset().name());
filter.setForceRequestEncoding(this.properties.shouldForce(org.springframework.boot.autoconfigure.http.HttpProperties.Encoding.Type.REQUEST));
filter.setForceResponseEncoding(this.properties.shouldForce(org.springframework.boot.autoconfigure.http.HttpProperties.Encoding.Type.RESPONSE));
return filter;
}
@Bean
public HttpEncodingAutoConfiguration.LocaleCharsetMappingsCustomizer localeCharsetMappingsCustomizer() {
return new HttpEncodingAutoConfiguration.LocaleCharsetMappingsCustomizer(this.properties);
}
複製程式碼
首先它同樣有許多註解,我們一個一個分析:
- Configuration:這個註解宣告瞭這個類為配置類(和我們平時寫的配置類一樣,同樣是在類上加這個註解)。
- EnableConfigurationProperties:開啟
ConfigurationProperties
功能,也就是將配置檔案和HttpProperties.class
這個類繫結起來,將配置檔案的相應的值和HttpProperties.class
的變數關聯起來,可以點選HttpProperties.class
進去看看,下面擷取了部分程式碼進行分析:
@ConfigurationProperties(
prefix = "spring.http"
)
public static final Charset DEFAULT_CHARSET;
private Charset charset;
private Boolean force;
private Boolean forceRequest;
private Boolean forceResponse;
private Map<Locale, Charset> mapping;
複製程式碼
通過ConfigurationProperties
指定字首,將配置檔案application.properties
字首為spring.http
的值和HttpProperties.class
的變數關聯起來,通過類的變數可以發現,我們可以設定的屬性是charset
、force
、forceRequest
、forceResponse
和mapping
。也就是我們除了使用 Spring Boot 預設提供的配置資訊之外,我們還可以通過配置檔案指定配置資訊。
- ConditionalOnWebApplication:這個註解的作用是自動配置類在 Web 應用中才生效。
- ConditionalOnClass:只有在存在
CharacterEncodingFilter
這個類的情況下自動配置類才會生效。 - ConditionalOnProperty:判斷配置檔案是否存在某個配置 spring.http.encoding ,如果存在其值為 enabled 才生效,如果不存在這個配置類也生效。
可以發現後面幾個註解都是ConditionalXXXX
的命名規則,這些註解是 Spring 制定的條件註解,只有在符合條件的情況下自動配置類才會生效。
接下來的characterEncodingFilter
方法,建立一個CharacterEncodingFilter
的物件,也就是字元編碼過濾器,同時設定相關屬性,然後將物件返回,通過@Bean
註解,將返回的物件新增到 Spring 容器中。這樣字元編碼過濾器元件配置好了,而平時的話,我們需要在 web.xml 進行如下配置:
<filter>
<filter-name>springUtf8Encoding</filter-name>
<filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
<init-param>
<param-name>encoding</param-name>
<param-value>utf-8</param-value>
</init-param>
<init-param>
<param-name>forceEncoding</param-name>
<param-value>true</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>springUtf8Encoding</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
複製程式碼
到這裡是不是感受到了 Spring Boot 自動配置帶來的好處了?
接下來的localeCharsetMappingsCustomizer
方法同理,就不分析了。
最後我們用一句話總結一下 Spring Boot 的自動配置:Spring Boot 啟動的時候,會掃描ClassPath
下的所有 jar 包,將其spring.factories
檔案中key
為EnableAutoConfiguration
的所有值取出,然後這些值其實是類的全限定名,也就是自動配置類的全限定名,然後 Spring Boot 通過這些全限定名進行類載入(反射),將這些自動配置類新增到 Spring 容器中。這些自動配置類根據不同的條件(@ConditionalXXX)決定自動配置類是否生效,生效的話自動配置類會將相關元件新增到 Spring 容器中,也就不用我們再進行配置!
總結
看了網上挺多的文章都說的不是很清楚,所以按照自己的理解寫下了這篇總結,有誤之處歡迎指出。
原文地址:ddnd.cn/2019/05/10/…