Spring Boot Starter是在SpringBoot組件中被提出來的一種概念,stackoverflow上面已經有人概括了這個starter是什么東西,想看完整的回答戳這里
Starter POMs are a set of convenient dependency descriptors that you can include in your application. You get a one-stop-shop for all the Spring and related technology that you need, without having to hunt through sample code and copy paste loads of dependency descriptors. For example, if you want to get started using Spring and JPA for database access, just include the spring-boot-starter-data-jpa dependency in your project, and you are good to go.
大概意思就是說starter是一種對依賴的synthesize(合成),這是什么意思呢?我可以舉個例子來說明。
傳統的做法
在沒有starter之前,假如我想要在Spring中使用jpa,那我可能需要做以下操作:
- 在Maven中引入使用的數據庫的依賴(即JDBC的jar)
- 引入jpa的依賴
- 在xxx.xml中配置一些屬性信息
- 反復的調試直到可以正常運行
需要注意的是,這里操作在我們每次新建一個需要用到jpa的項目的時候都需要重復的做一次。也許你在第一次自己建立項目的時候是在Google上自己搜索了一番,花了半天時間解決掉了各種奇怪的問題之后,jpa終於能正常運行了。有些有經驗的人會在OneNote上面把這次建立項目的過程給記錄下來,包括操作的步驟以及需要用到的配置文件的內容,在下一次再創建jpa項目的時候,就不需要再次去Google了,只需要照着筆記來,之后再把所有的配置文件copy&paste就可以了。
像上面這樣的操作也不算不行,事實上我們在沒有starter之前都是這么干的,但是這樣做有幾個問題:
- 如果過程比較繁瑣,這樣一步步操作會增加出錯的可能性
- 不停地copy&paste不符合Don’t repeat yourself精神
- 在第一次配置的時候(尤其如果開發者比較小白),需要花費掉大量的時間
使用Spring Boot Starter提升效率
starter的主要目的就是為了解決上面的這些問題。
starter的理念:starter會把所有用到的依賴都給包含進來,避免了開發者自己去引入依賴所帶來的麻煩。需要注意的是不同的starter是為了解決不同的依賴,所以它們內部的實現可能會有很大的差異,例如jpa的starter和Redis的starter可能實現就不一樣,這是因為starter的本質在於synthesize,這是一層在邏輯層面的抽象,也許這種理念有點類似於Docker,因為它們都是在做一個“包裝”的操作,如果你知道Docker是為了解決什么問題的,也許你可以用Docker和starter做一個類比。
starter的實現:雖然不同的starter實現起來各有差異,但是他們基本上都會使用到兩個相同的內容:ConfigurationProperties和AutoConfiguration。因為Spring Boot堅信“約定大於配置”這一理念,所以我們使用ConfigurationProperties來保存我們的配置,並且這些配置都可以有一個默認值,即在我們沒有主動覆寫原始配置的情況下,默認值就會生效,這在很多情況下是非常有用的。除此之外,starter的ConfigurationProperties還使得所有的配置屬性被聚集到一個文件中(一般在resources目錄下的application.properties),這樣我們就告別了Spring項目中XML地獄。
starter的整體邏輯:
上面的starter依賴的jar和我們自己手動配置的時候依賴的jar並沒有什么不同,所以我們可以認為starter其實是把這一些繁瑣的配置操作交給了自己,而把簡單交給了用戶。除了幫助用戶去除了繁瑣的構建操作,在“約定大於配置”的理念下,ConfigurationProperties還幫助用戶減少了無謂的配置操作。並且因為 application.properties
文件的存在,即使需要自定義配置,所有的配置也只需要在一個文件中進行,使用起來非常方便。
了解了starter其實就是幫助用戶簡化了配置的操作之后,要理解starter和被配置了starter的組件之間並不是競爭關系,而是輔助關系,即我們可以給一個組件創建一個starter來讓最終用戶在使用這個組件的時候更加的簡單方便。基於這種理念,我們可以給任意一個現有的組件創建一個starter來讓別人在使用這個組件的時候更加的簡單方便,事實上Spring Boot團隊已經幫助現有大部分的流行的組件創建好了它們的starter,你可以在這里查看這些starter的列表。
Starter原理
首先說說原理,我們知道使用一個公用的starter的時候,只需要將相應的依賴添加的Maven的配置文件當中即可,免去了自己需要引用很多依賴類,並且SpringBoot會自動進行類的自動配置。那么 SpringBoot 是如何知道要實例化哪些類,並進行自動配置的呢? 下面簡單說一下。
首先,SpringBoot 在啟動時會去依賴的starter包中尋找 resources/META-INF/spring.factories
文件(通過autoconfigure 管理,通過服務中的springboot main 啟動中@EnableAutoConfiguration(@SpringBootApplication)引入),然后根據文件中配置的Jar包去掃描項目所依賴的Jar包,這類似於 Java 的 SPI 機制。
第二步,根據 spring.factories
配置加載AutoConfigure
類。
最后,根據 @Conditional
注解的條件,進行自動配置並將Bean注入Spring Context 上下文當中。
我們也可以使用@ImportAutoConfiguration({MyServiceAutoConfiguration.class})
指定自動配置哪些類。
創建自己的Spring Boot Starter
如果你想要自己創建一個starter,那么基本上包含以下幾步
- 創建一個starter項目,關於項目的命名你可以參考這里
- 創建一個ConfigurationProperties用於保存你的配置信息(如果你的項目不使用配置信息則可以跳過這一步,不過這種情況非常少見)
- 創建一個AutoConfiguration,引用定義好的配置信息;在AutoConfiguration中實現所有starter應該完成的操作(//添加自動掃描注解,basePackages為TestBean包路徑 @ComponentScan(basePackages = "com.test.spring.support.configuration"),或者在該類中通過@Bean),並且把這個類加入spring.factories配置文件中進行聲明
- 打包項目,之后在一個SpringBoot項目中引入該項目依賴,然后就可以使用該starter了
注意點:
- 自定義的starter是不能有啟動入口的!即:只能作為工具類!類似jdk!
- 不要把自定義的pom寫成了一個可啟動的項目哈!
- 不然install后是引用不到自定義的starter里面的類的!!!
- 可對比install后的web項目 和 install后的工具類pom , 生成的jar文件的目錄結構是不同的哈!!!
我們來看一個例子(例子的完整代碼位於https://github.com/RitterHou/learn-spring-boot-starter)
首先新建一個Maven項目,設置 pom.xml
文件如下:
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <artifactId>http-starter</artifactId> <version>0.0.1-SNAPSHOT</version> <!-- 自定義starter都應該繼承自該依賴 --> <!-- 如果自定義starter本身需要繼承其它的依賴,可以參考 https://stackoverflow.com/a/21318359 解決 --> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starters</artifactId> <version>1.5.2.RELEASE</version> </parent> <dependencies> <!-- 自定義starter依賴此jar包 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter</artifactId> </dependency> <!-- lombok用於自動生成get、set方法 --> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>1.16.10</version> </dependency> </dependencies> </project>
創建proterties類來保存配置信息:
@ConfigurationProperties(prefix = "http") // 自動獲取配置文件中前綴為http的屬性,把值傳入對象參數
@Setter
@Getter
public class HttpProperties {
// 如果配置文件中配置了http.url屬性,則該默認屬性會被覆蓋
private String url = "http://www.baidu.com/";
}
上面這個類就是定義了一個屬性,其默認值是 http://www.baidu.com/
,我們可以通過在 application.properties
中添加配置 http.url=https://www.zhihu.com
來覆蓋參數的值。
創建業務類:
@Setter @Getter public class HttpClient { private String url; // 根據url獲取網頁數據 public String getHtml() { try { URL url = new URL(this.url); URLConnection urlConnection = url.openConnection(); BufferedReader br = new BufferedReader(new InputStreamReader(urlConnection.getInputStream(), "utf-8")); String line = null; StringBuilder sb = new StringBuilder(); while ((line = br.readLine()) != null) { sb.append(line).append("\n"); } return sb.toString(); } catch (Exception e) { e.printStackTrace(); } return "error"; } }
這個業務類的操作非常簡單,只包含了一個 url
屬性和一個 getHtml
方法,用於獲取一個網頁的HTML數據,讀者看看就懂了。
創建AutoConfiguration
@Configuration @EnableConfigurationProperties(HttpProperties.class) public class HttpAutoConfiguration { @Resource private HttpProperties properties; // 使用配置 // 在Spring上下文中創建一個對象 @Bean @ConditionalOnMissingBean public HttpClient init() { HttpClient client = new HttpClient(); String url = properties.getUrl(); client.setUrl(url); return client; } }
在上面的AutoConfiguration中我們實現了自己要求:在Spring的上下文中創建了一個HttpClient類的bean,並且我們把properties中的一個參數賦給了該bean。
關於@ConditionalOnMissingBean
這個注解,它的意思是在該bean不存在的情況下此方法才會執行,這個相當於開關的角色,更多關於開關系列的注解可以參考這里。
最后,我們在 resources
文件夾下新建目錄 META-INF
,在目錄中新建 spring.factories
文件,並且在 spring.factories
中配置AutoConfiguration:
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.nosuchfield.httpstarter.HttpAutoConfiguration
到此,我們的starter已經創建完畢了,使用Maven打包該項目。之后創建一個SpringBoot項目,在項目中添加我們之前打包的starter作為依賴,然后使用SringBoot來運行我們的starter,代碼如下:
@Component public class RunIt { @Resource private HttpClient httpClient; public void hello() { System.out.println(httpClient.getHtml()); } }
正常情況下此方法的執行會打印出url http://www.baidu.com/
的HTML內容,之后我們在application.properties中加入配置:
http.url=https://www.zhihu.com/
再次運行程序,此時打印的結果應該是知乎首頁的HTML了,證明properties中的數據確實被覆蓋了。
=========================================================================================
1. start.spring.io的使用
首先帶你瀏覽http://start.spring.io/,在這個網址中有一些Spring Boot提供的組件。
在網站Spring Initializr上填對應的表單,描述Spring Boot項目的主要信息,例如Project Metadata、Dependency等。在Project Dependencies區域,你可以根據應用程序的功能需要選擇相應的starter。
Spring Boot starters可以簡化Spring項目的庫依賴管理,將某一特定功能所需要的依賴庫都整合在一起,就形成一個starter,例如:連接數據庫、springmvc、spring測試框架等等。簡單來說,spring boot使得你的pom文件從此變得很清爽且易於管理。
常用的starter以及用處可以列舉如下:
- spring-boot-starter: 這是核心Spring Boot starter,提供了大部分基礎功能,其他starter都依賴於它,因此沒有必要顯式定義它。
- spring-boot-starter-actuator:主要提供監控、管理和審查應用程序的功能。
- spring-boot-starter-jdbc:該starter提供對JDBC操作的支持,包括連接數據庫、操作數據庫,以及管理數據庫連接等等。
- spring-boot-starter-data-jpa:JPA starter提供使用Java Persistence API(例如Hibernate等)的依賴庫。
- spring-boot-starter-data-*:提供對MongoDB、Data-Rest或者Solr的支持。
- spring-boot-starter-security:提供所有Spring-security的依賴庫。
- spring-boot-starter-test:這個starter包括了spring-test依賴以及其他測試框架,例如JUnit和Mockito等等。
- spring-boot-starter-web:該starter包括web應用程序的依賴庫。
2、如何自己寫starter
2.1、示例一
-
1、選擇已有的starters,在此基礎上進行擴展.
-
2、創建自動配置文件並設定META-INF/spring.factories里的內容.
-
3、發布你的starter
添加依賴管理
<dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-dependencies</artifactId> <version>${spring.boot.version}</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement>
添加starter自己的依賴
<dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> </dependencies>
新建configuration
@Configuration @ComponentScan( basePackages = {"com.patterncat.actuator"} ) public class WebAutoConfiguration { /** * addViewController方法不支持placeholder的解析 * 故在這里用變量解析出來 */ @Value("${actuator.web.base:}") String actuatorBase; // @Bean // public ActuatorNavController actuatorNavController(){ // return new ActuatorNavController(); // } @Bean public WebMvcConfigurerAdapter configStaticMapping() { return new WebMvcConfigurerAdapter() { @Override public void addViewControllers(ViewControllerRegistry registry) { //配置跳轉 registry.addViewController(actuatorBase+"/nav").setViewName( "forward:/static/nav.html"); } @Override public void addResourceHandlers(ResourceHandlerRegistry registry) { registry.addResourceHandler("/static/**"). addResourceLocations("classpath:/static/"); } }; } }
修改/META-INF/spring.factories
# AutoConfigurations
org.springframework.boot.autoconfigure.EnableAutoConfiguration=com.patterncat.actuator.configuration.WebAutoConfiguration
發布
mvn clean install
引用
<dependency> <groupId>com.patterncat</groupId> <artifactId>spring-boot-starter-actuator-web</artifactId> <version>1.0-SNAPSHOT</version> </dependency>
啟動訪問
mvn spring-boot:run
訪問
http://localhost:8080/nav
note
2、2、示例二
- 新建一個模塊db-count-starter,然后修改db-count-starter模塊下的pom文件,增加對應的庫。
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot</artifactId>
<!-- version繼承父模塊的-->
</dependency>
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-commons</artifactId>
<version>1.9.3.RELEASE</version>
</dependency></dependencies>
- 新建包結構com/test/bookpubstarter/dbcount,然后新建DbCountRunner類,實現CommandLineRunner接口,在run方法中輸出每個實體的數量。
package com.test.bookpubstarter.dbcount;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.CommandLineRunner;
import org.springframework.data.repository.CrudRepository;
import java.util.Collection;
public class DbCountRunner implements CommandLineRunner {
protected final Logger logger = LoggerFactory.getLogger(DbCountRunner.class);
private Collection<CrudRepository> repositories;
public DbCountRunner(Collection<CrudRepository> repositories) {
this.repositories = repositories;
}
@Override
public void run(String... strings) throws Exception {
repositories.forEach(crudRepository -> {
logger.info(String.format("%s has %s entries",
getRepositoryName(crudRepository.getClass()),
crudRepository.count()));
});
}
private static String getRepositoryName(Class crudRepositoryClass) {
for (Class repositoryInterface : crudRepositoryClass.getInterfaces()) {
if (repositoryInterface.getName().startsWith("com.test.bookpub.repository")) {
return repositoryInterface.getSimpleName();
}
}
return "UnknownRepository";
}
}
- 增加自動配置文件DbCountAutoConfiguration
package com.test.bookpubstarter.dbcount;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.repository.CrudRepository;
import java.util.Collection;
@Configuration
public class DbCountAutoConfiguration {
@Bean
public DbCountRunner dbCountRunner(Collection<CrudRepository> repositories) {
return new DbCountRunner(repositories);
}
}
- 在src/main/resources目錄下新建META-INF文件夾,然后新建spring.factories文件,這個文件用於告訴Spring Boot去找指定的自動配置文件,因此它的內容是
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.test.bookpubstarter.dbcount.DbCountAutoConfiguration
- 在之前的程序基礎上,在頂層pom文件中增加starter的依賴
<dependency>
<groupId>com.test</groupId>
<artifactId>db-count-starter</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
- 把StartupRunner相關的注釋掉,然后在main函數上右鍵Run BookPubApplication.main(...),可以看出我們編寫的starter被主程序使用了。

2.3、總結分析
正規的starter是一個獨立的工程,然后在maven中新倉庫注冊發布,其他開發人員就可以使用你的starter了。
常見的starter會包括下面幾個方面的內容:
- 自動配置文件,根據classpath是否存在指定的類來決定是否要執行該功能的自動配置。
- spring.factories,非常重要,指導Spring Boot找到指定的自動配置文件。
- endpoint:可以理解為一個admin,包含對服務的描述、界面、交互(業務信息的查詢)
- health indicator:該starter提供的服務的健康指標
在應用程序啟動過程中,Spring Boot使用SpringFactoriesLoader類加載器查找org.springframework.boot.autoconfigure.EnableAutoConfiguration關鍵字對應的Java配置文件。Spring Boot會遍歷在各個jar包種META-INF目錄下的spring.factories文件,構建成一個配置文件鏈表。除了EnableAutoConfiguration關鍵字對應的配置文件,還有其他類型的配置文件:
- org.springframework.context.ApplicationContextInitializer
- org.springframework.context.ApplicationListener
- org.springframework.boot.SpringApplicationRunListener
- org.springframework.boot.env.PropertySourceLoader
- org.springframework.boot.autoconfigure.template.TemplateAvailabilityProvider
- org.springframework.test.contex.TestExecutionListener
Spring Boot的starter在編譯時不需要依賴Spring Boot的庫。這個例子中依賴spring boot並不是因為自動配置要用spring boot,而僅僅是因為需要實現CommandLineRunner接口。
兩個需要注意的點
-
@ConditionalOnMissingBean的作用是:只有對應的ban在系統中都沒有被創建,它修飾的初始化代碼塊才會執行,用戶自己手動創建的bean優先;
-
Spring Boot starter如何找到自動配置文件(xxxxAutoConfiguration之類的文件)?
- spring.factories:由Spring Boot觸發探測classpath目錄下的類,進行自動配置;
-
- @Enable:有時需要由starter的用戶觸發*查找自動配置文件的過程。
3、 Spring Boot的自動配置
在Spring Boot項目中,xxxApplication.java會作為應用程序的入口,負責程序啟動以及一些基礎性的工作。@SpringBootApplication是這個注解是該應用程序入口的標志,然后有熟悉的main函數,通過SpringApplication.run(xxxApplication.class, args)
來運行Spring Boot應用。打開SpringBootApplication注解可以發現,它是由其他幾個類組合而成的:@Configuration(等同於spring中的xml配置文件,使用Java文件做配置可以檢查類型安全)、@EnableAutoConfiguration(自動配置,稍后細講)、@ComponentScan(組件掃描,大家非常熟悉的,可以自動發現和裝配一些Bean)。
我們在pom文件里可以看到,com.h2database這個庫起作用的范圍是runtime,也就是說,當應用程序啟動時,如果Spring Boot在classpath下檢測到org.h2.Driver的存在,會自動配置H2數據庫連接。現在啟動應用程序來觀察,以驗證我們的想法。打開shell,進入項目文件夾,利用mvn spring-boot:run
啟動應用程序,如下圖所示。

可以看到類似Building JPA container EntityManagerFactory for persistence unit 'default、HHH000412: Hibernate Core {4.3.11.Final}、HHH000400: Using dialect: org.hibernate.dialect.H2Dialect這些Info信息;由於我們之前選擇了jdbc和jpa等starters,Spring Boot將自動創建JPA容器,並使用Hibernate4.3.11,使用H2Dialect管理H2數據庫(內存數據庫)。
4、 使用Command-line runners
我們新建一個StartupRunner類,該類實現CommandLineRunner接口,這個接口只有一個函數:public void run(String... args)
,最重要的是:這個方法會在應用程序啟動后首先被調用。
How do
- 在src/main/java/org/test/bookpub/下建立StartRunner類,代碼如下:
package com.test.bookpub;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.CommandLineRunner;
public class StartupRunner implements CommandLineRunner {
protected final Logger logger = LoggerFactory.getLogger(StartupRunner.class);
@Override
public void run(String... strings) throws Exception {
logger.info("hello");
}
}
- 在BookPubApplication類中創建bean對象,代碼如下:
@Bean
public StartupRunner schedulerRunner() {
return new StartupRunner();
}
還是用mvn spring-boot:run
命令啟動程序,可以看到hello的輸出。對於那種只需要在應用程序啟動時執行一次的任務,非常適合利用Command line runners來完成。Spring Boot應用程序在啟動后,會遍歷CommandLineRunner接口的實例並運行它們的run方法。也可以利用@Order注解(或者實現Order接口)來規定所有CommandLineRunner實例的運行順序。
利用command-line runner的這個特性,再配合依賴注入,可以在應用程序啟動時后首先引入一些依賴bean,例如data source、rpc服務或者其他模塊等等,這些對象的初始化可以放在run方法中。不過,需要注意的是,在run方法中執行初始化動作的時候一旦遇到任何異常,都會使得應用程序停止運行,因此最好利用try/catch語句處理可能遇到的異常。