前言如果我們想要使用傳統(tǒng)意義上的 Spring 應(yīng)用,那么需要配置大量的 xml 文件才可以啟動(dòng),而且隨著項(xiàng)目的越來(lái)越龐大,配置文件也會(huì)越來(lái)越繁瑣,這在一定程度上也給開(kāi)發(fā)者帶來(lái)了困擾,于是 SpringBoot 就應(yīng)運(yùn)而生了。
什么是 SpringBoot
2012 年 10 月,一個(gè)叫 Mike Youngstrom
的人在 Spring Jira
中創(chuàng)建了一個(gè)功能請(qǐng)求,要求在 Spring Framework 中支持無(wú)容器 Web 應(yīng)用程序體系結(jié)構(gòu),提出了在主容器引導(dǎo) Spring
容器內(nèi)配置 Web 容器服務(wù)。這件事情對(duì) SpringBoot
的誕生應(yīng)該說(shuō)是起到了一定的推動(dòng)作用。
SpringBoot
的誕生就是為了簡(jiǎn)化 Spring
中繁瑣的 XML
配置,其本質(zhì)依然是 Spring
框架,使用 SpringBoot
之后可以不使用任何 XML 配置來(lái)啟動(dòng)一個(gè)服務(wù),使得我們?cè)谑褂梦⒎?wù)架構(gòu)時(shí)可以更加快速的建立一個(gè)應(yīng)用。
SpringBoot 具有以下特點(diǎn):
- 創(chuàng)建獨(dú)立的 Spring 應(yīng)用。
- 直接嵌入了 Tomcat、Jetty 或 Undertow(不需要部署 WAR 文件)。
- 提供了固定的配置來(lái)簡(jiǎn)化配置。
- 盡可能地自動(dòng)配置 Spring 和第三方庫(kù)。
- 提供可用于生產(chǎn)的特性,如度量、運(yùn)行狀況檢查和外部化配置。
- 完全不需要生成代碼,也不需要 XML 配置。
SpringBoot
這些特點(diǎn)中最重要的兩條就是約定優(yōu)于配置和自動(dòng)裝配。
約定優(yōu)于配置
SpringBoot
的約定由于配置主要體現(xiàn)在以下方面:
maven
項(xiàng)目的配置文件存放在 resources
資源目錄下。maven
項(xiàng)目默認(rèn)編譯后的文件放于 target
目錄。maven
項(xiàng)目默認(rèn)打包成 jar
格式。配置文件默認(rèn)為 application.yml 或者 application.yaml 或者 application.properties。默認(rèn)通過(guò)配置文件 spring.profiles.active 來(lái)激活配置。
自動(dòng)裝配
自動(dòng)裝配則是 SpringBoot
的核心,自動(dòng)裝配是如何實(shí)現(xiàn)的呢?為什么我們只要引入一個(gè) starter 組件依賴(lài)就能實(shí)現(xiàn)自動(dòng)裝配呢,接下來(lái)就讓我們一起來(lái)探討下 SpringBoot
的自動(dòng)裝配機(jī)制。
相比較于傳統(tǒng)的 Spring 應(yīng)用,搭建一個(gè) SpringBoot
應(yīng)用,我們只需要引入一個(gè)注解 @SpringBootApplication
,就可以成功運(yùn)行。
我們就從 SpringBoot
的這個(gè)注解開(kāi)始入手,看看這個(gè)注解到底替我們做了什么。
前面四個(gè)不用說(shuō),是定義一個(gè)注解所必須的,關(guān)鍵就在于后面三個(gè)注解:@SpringBootConfiguration
,@EnableAutoConfiguration
,@ComponentScan
。也就是說(shuō)我們?nèi)绻挥?@SpringBootApplication
這個(gè)復(fù)合注解,而是直接使用最下面這三個(gè)注解,也能啟動(dòng)一個(gè) SpringBoot
應(yīng)用。
@SpringBootConfiguration 注解
[這個(gè)注解我們點(diǎn)進(jìn)去就可以發(fā)現(xiàn),它實(shí)際上就是一個(gè) @Configuration
注解,這個(gè)注解大家應(yīng)該很熟悉了,加上這個(gè)注解就是為了讓當(dāng)前類(lèi)作為一個(gè)配置類(lèi)交由Spring
的 IOC
容器進(jìn)行管理,因?yàn)榍懊嫖覀冋f(shuō)了,SpringBoot
本質(zhì)上還是 Spring
,所以原屬于 Spring
的注解 @Configuration
在 SpringBoot
中也可以直接應(yīng)用。
@ComponentScan 注解
[這個(gè)注解也很熟悉,用于定義 Spring 的掃描路徑,等價(jià)于在 xml 文件中配置 context:component-scan,假如不配置掃描路徑,那么 Spring 就會(huì)默認(rèn)掃描當(dāng)前類(lèi)所在的包及其子包中的所有標(biāo)注了 @Component
,@Service
,@Controller
等注解的類(lèi)。
@EnableAutoConfiguration
這個(gè)注解才是實(shí)現(xiàn)自動(dòng)裝配的關(guān)鍵,點(diǎn)進(jìn)去之后發(fā)現(xiàn),它是一個(gè)由@AutoConfigurationPackage
和 @Import
注解組成的復(fù)合注解。
@EnableXXX
注解也并不是 SpringBoot
中的新注解,這種注解在 Spring 3.1
版本就開(kāi)始出現(xiàn)了,比如開(kāi)啟定時(shí)任務(wù)的注解 @EnableScheduling
等。
@Import 注解
[這個(gè)注解比較關(guān)鍵,我們通過(guò)一個(gè)例子來(lái)說(shuō)明一下。
定義一個(gè)普通類(lèi)TestImport
,不加任何注解,我們知道這個(gè)時(shí)候這個(gè)類(lèi)并不會(huì)被 Spring
掃描到,也就是無(wú)法直接注入這個(gè)類(lèi):
public class TestImport {
}
現(xiàn)實(shí)開(kāi)發(fā)中,假如就有這種情況,定義好了一個(gè)類(lèi),即使加上了注解,也不能保證這個(gè)類(lèi)一定被 Spring
掃描到,這個(gè)時(shí)候該怎么做呢?
這時(shí)候我們可以再定義一個(gè)類(lèi) MyConfiguration
,保證這個(gè)類(lèi)可以被 Spring 掃描到,然后通過(guò)加上 @Import
注解來(lái)導(dǎo)入 TestImport
類(lèi),這時(shí)候就可以直接注入 TestImport
了:
@Configuration
@Import(TestImport.class)
public class MyConfiguration {
}
所以這里的 @Import
注解其實(shí)就是為了去導(dǎo)入一個(gè)類(lèi) AutoConfigurationImportSelector
,接下來(lái)我們需要分析一下這個(gè)類(lèi)。
AutoConfigurationImportSelector 類(lèi)
進(jìn)入這個(gè)類(lèi)之后,有一個(gè)方法,這個(gè)方法很好理解,首先就是看一下 AnnotationMetadata
(注解的元信息),有沒(méi)有數(shù)據(jù),沒(méi)有就說(shuō)明沒(méi)導(dǎo)入直接返回一個(gè)空數(shù)組,否則就調(diào)用 getAutoConfigurationEntry
方法:
進(jìn)入 getAutoConfigurationEntry
方法:
這個(gè)方法里面就是通過(guò)調(diào)用 getCandidateConfigurations
來(lái)獲取候選的 Bean
,并將其存為一個(gè)集合,最后經(jīng)過(guò)去重,校驗(yàn)等一系列操作之后,被封裝成 AutoConfigurationEntry
對(duì)象返回。
繼續(xù)進(jìn)入 getCandidateConfigurations
方法,這時(shí)候就幾乎看到曙光了:
這里面再繼續(xù)點(diǎn)擊去就沒(méi)必要了,看錯(cuò)誤提示大概就知道了,loadFactoryNames
方法會(huì)去 META-INF/spring.factories
文件中根據(jù) EnableAutoConfiguration
的全限定類(lèi)名獲取到我們需要導(dǎo)入的類(lèi),而 EnableAutoConfiguration
類(lèi)的全限定類(lèi)名為 org.springframework.boot.autoconfigure.EnableAutoConfiguration
,那么就讓我們打開(kāi)這個(gè)文件看一下:
可以看到,這個(gè)文件中配置了大量的需要自動(dòng)裝配的類(lèi),當(dāng)我們啟動(dòng) SpringBoot
項(xiàng)目的時(shí)候,SpringBoot
會(huì)掃描所有jar
包下面的 META-INF/spring.factories
文件,并根據(jù) key
值進(jìn)行讀取,最后在經(jīng)過(guò)去重等一些列操作得到了需要自動(dòng)裝配的類(lèi)。
需要注意的是:上圖中的 spring.factories
文件是在 spring-boot-autoconfigure
包下面,這個(gè)包記錄了官方提供的 stater
中幾乎所有需要的自動(dòng)裝配類(lèi),所以并不是每一個(gè)官方的 starter 下都會(huì)有 spring.factories
文件。
談?wù)?SPI 機(jī)制
通過(guò) SpringFactoriesLoader
來(lái)讀取配置文件 spring.factories
中的配置文件的這種方式是一種 SPI
的思想。那么什么是 SPI
呢?
SPI,Service Provider Interface
。即:接口服務(wù)的提供者。就是說(shuō)我們應(yīng)該面向接口(抽象)編程,而不是面向具體的實(shí)現(xiàn)來(lái)編程,這樣一旦我們需要切換到當(dāng)前接口的其他實(shí)現(xiàn)就無(wú)需修改代碼。
在 Java 中,數(shù)據(jù)庫(kù)驅(qū)動(dòng)就使用到了 SPI 技術(shù),每次我們只需要引入數(shù)據(jù)庫(kù)驅(qū)動(dòng)就能被加載的原因就是因?yàn)槭褂昧?SPI 技術(shù)。
打開(kāi) DriverManager 類(lèi),其初始化驅(qū)動(dòng)的代碼如下:
進(jìn)入 ServiceLoader
方法,發(fā)現(xiàn)其內(nèi)部定義了一個(gè)變量:
private static final String PREFIX = "META-INF/services/";
這個(gè)變量在下面加載驅(qū)動(dòng)的時(shí)候有用到,下圖中的 service
即 java.sql.Driver
:
所以就是說(shuō),在數(shù)據(jù)庫(kù)驅(qū)動(dòng)的 jar
包下面的 META-INF/services/
下有一個(gè)文件 java.sql.Driver
,里面記錄了當(dāng)前需要加載的驅(qū)動(dòng),我們打開(kāi)這個(gè)文件可以看到里面記錄的就是驅(qū)動(dòng)的全限定類(lèi)名:
@AutoConfigurationPackage 注解
從這個(gè)注解繼續(xù)點(diǎn)進(jìn)去之后可以發(fā)現(xiàn),它最終還是一個(gè) @Import
注解:
這個(gè)時(shí)候它導(dǎo)入了一個(gè) AutoConfigurationPackages
的內(nèi)部類(lèi) Registrar
, 而這個(gè)類(lèi)其實(shí)作用就是讀取到我們?cè)谧钔鈱拥?@SpringBootApplication
注解中配置的掃描路徑(沒(méi)有配置則默認(rèn)當(dāng)前包下),然后把掃描路徑下面的類(lèi)都加到數(shù)組中返回。
手寫(xiě)一個(gè) stater 組件
了解完自動(dòng)裝配的原理,接下來(lái)就可以動(dòng)手寫(xiě)一個(gè)自己的 starter
組件了。
starter 組件命名規(guī)則
SpringBoot
官方的建議是,如果是我們開(kāi)發(fā)者自己開(kāi)發(fā)的 starter
組件(即屬于第三方組件),那么命名規(guī)范是{name}-spring-boot-starter
,而如果是 SpringBoot
官方自己開(kāi)發(fā)的組件,則命名為 spring-boot-starter-{name}
。
當(dāng)然,這只是一個(gè)建議,如果非不按這個(gè)規(guī)則也沒(méi)什么問(wèn)題,但是為了更好的識(shí)別區(qū)分,還是建議按照這個(gè)規(guī)則來(lái)命名。
手寫(xiě) starter
寫(xiě)一個(gè)非常簡(jiǎn)單的組件,這個(gè)組件只做一件事,那就是實(shí)現(xiàn) fastjson
序列化。
- 新建一個(gè)
SpringBoot
應(yīng)用lonelyWolf-spring-boot-starter
。 - 修改
pom
文件,并新增fastjson
依賴(lài)(省略了部分屬性)。
<parent>
<groupId>org.springframework.boot<span class="hljs-name"groupId>
<artifactId>spring-boot-starter-parent<span class="hljs-name"artifactId>
<version>2.4.0<span class="hljs-name"version>
<relativePath/>
<span class="hljs-name"parent>
<groupId>com.lonely.wolf.note<span class="hljs-name"groupId>
<artifactId>lonelyWolf-spring-boot-starter<span class="hljs-name"artifactId>
<version>1.0.0-SNAPSHOT<span class="hljs-name"version>
<dependencies>
<dependency>
<groupId>org.springframework.boot<span class="hljs-name"groupId>
<artifactId>spring-boot-starter<span class="hljs-name"artifactId>
<span class="hljs-name"dependency>
<dependency>
<groupId>com.alibaba<span class="hljs-name"groupId>
<artifactId>fastjson<span class="hljs-name"artifactId>
<version>1.2.72<span class="hljs-name"version>
<span class="hljs-name"dependency>
<span class="hljs-name"dependencies>
- 新建一個(gè)序列化類(lèi)
JsonSerial
類(lèi)來(lái)實(shí)現(xiàn)fastjson
序列化。
public class JsonSerial {
public
- 新建一個(gè)自動(dòng)裝配類(lèi)
MyAutoConfiguration
來(lái)生成JsonSerial
。
@Configuration
public class MyAutoConfiguration {
@Bean
public JsonSerial jsonSerial(){
return new JsonSerial();
}
}
- 完成之后將其打成一個(gè)
jar
包,然后再另一個(gè)SpringBoot
中引入依賴(lài):
<dependency>
<groupId>com.lonely.wolf.note<span class="hljs-name"groupId>
<artifactId>lonelyWolf-spring-boot-starter<span class="hljs-name"artifactId>
<version>1.0.0-SNAPSHOT<span class="hljs-name"version>
<span class="hljs-name"dependency>
- 這時(shí)候在這個(gè)
SpringBoot
應(yīng)用中直接注入JsonSerial
對(duì)象會(huì)直接提示找不到這個(gè)對(duì)象:
這是因?yàn)?MyAutoConfiguration
這個(gè)類(lèi)是在外部 jar 包之中,并沒(méi)有被掃描到(需要注意的是,假如剛好 jar
包的路徑和掃描的路徑相同,那么是可以被掃描到的,但是在實(shí)際項(xiàng)目中,我們不可能確保引入的 jar
包能被掃描到,所以才需要通過(guò)配置的方式來(lái)導(dǎo)入),所以我們還需要導(dǎo)入這個(gè)外部配置類(lèi)。
- 在
resources
目錄下新建一個(gè)文件META-INF/spring.factories
文件,文件內(nèi)新增一個(gè)如下配置:
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\\
com.lonely.wolf.note.MyAutoConfiguration
這樣,SpringBoot
就會(huì)將 MyAutoConfiguration
進(jìn)行管理,從而得到 JsonSerial
對(duì)象,這樣就可以直接注入使用了。
總結(jié)
本文從為什么要有 SpringBoot
,以及 SpringBoot
到底方便在哪里開(kāi)始入手,逐步分析了 SpringBoot
自動(dòng)裝配的原理,最后手寫(xiě)了一個(gè)簡(jiǎn)單的 start
組件,通過(guò)實(shí)戰(zhàn)來(lái)體會(huì)了 SpringBoot
自動(dòng)裝配機(jī)制的奧妙。
-
JAVA
+關(guān)注
關(guān)注
20文章
2989瀏覽量
109593 -
spring
+關(guān)注
關(guān)注
0文章
340瀏覽量
15062 -
自動(dòng)裝配
+關(guān)注
關(guān)注
0文章
7瀏覽量
738 -
SpringBoot
+關(guān)注
關(guān)注
0文章
175瀏覽量
399
發(fā)布評(píng)論請(qǐng)先 登錄
springboot spring data jpa使用總結(jié)
SpringBoot 學(xué)習(xí)筆記
springboot集成mqtt
SpringBoot應(yīng)用啟動(dòng)運(yùn)行run方法
SpringBoot配置嵌入式Servlet
SpringBoot的核心注解1

SpringBoot的核心注解2

評(píng)論