Spring Boot Starters 原理與實踐

Spring Boot Starters

 

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,那我可能需要做以下操作:

  1. 在Maven中引入使用的數據庫的依賴(即JDBC的jar)
  2. 引入jpa的依賴
  3. 在xxx.xml中配置一些屬性信息
  4. 反覆的調試直到可以正常運行

需要注意的是,這裏操作在我們每次新建一個需要用到jpa的項目的時候都需要重複的做一次。也許你在第一次自己建立項目的時候是在Google上自己搜索了一番,花了半天時間解決掉了各種奇怪的問題之後,jpa終於能正常運行了。有些有經驗的人會在OneNote上面把這次建立項目的過程給記錄下來,包括操作的步驟以及需要用到的配置文件的內容,在下一次再創建jpa項目的時候,就不需要再次去Google了,只需要照着筆記來,之後再把所有的配置文件copy&paste就可以了。

像上面這樣的操作也不算不行,事實上我們在沒有starter之前都是這麼幹的,但是這樣做有幾個問題:

  1. 如果過程比較繁瑣,這樣一步步操作會增加出錯的可能性
  2. 不停地copy&paste不符合Don’t repeat yourself精神
  3. 在第一次配置的時候(尤其如果開發者比較小白),需要花費掉大量的時間

使用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的列表。

創建自己的Spring Boot Starter

如果你想要自己創建一個starter,那麼基本上包含以下幾步

  1. 創建一個starter項目,關於項目的命名你可以參考這裏
  2. 創建一個ConfigurationProperties用於保存你的配置信息(如果你的項目不使用配置信息則可以跳過這一步,不過這種情況非常少見)
  3. 創建一個AutoConfiguration,引用定義好的配置信息;在AutoConfiguration中實現所有starter應該完成的操作,並且把這個類加入spring.factories配置文件中進行聲明
  4. 打包項目,之後在一個SpringBoot項目中引入該項目依賴,然後就可以使用該starter了

我們來看一個例子(例子的完整代碼位於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中的數據確實被覆蓋了。

 

鏈文

配置元數據

配置元數據2

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章