手把手教你手寫一個最簡單的 Spring Boot Starter

Mr_ηobody發表於2021-02-28

歡迎關注微信公眾號:「Java之言」技術文章持續更新,請持續關注......

  • 第一時間學習最新技術文章
  • 領取最新技術學習資料視訊
  • 最新網際網路資訊和麵試經驗

何為 Starter ?

想必大家都使用過 SpringBoot,在 SpringBoot 專案中,使用最多的無非就是各種各樣的 Starter 了。那何為 Starter 呢?你可以理解為一個可拔插式的外掛(元件)。或者理解為場景啟動器。

通過 Starter,能夠簡化以前繁雜的配置,無需過多的配置和依賴,它會幫你合併依賴,並且將其統一整合到一個 Starter 中,我們只需在 Maven 或 Gradle 中引入 Starter 依賴即可。SpringBoot 會自動掃描需要載入的資訊並啟動相應的預設配置。例如,如果你想使用 jdbc 外掛,你只需引入 spring-boot-starter-jdbc 即可;如果你想使用 mongodb,你只需引入 spring-boot-starter-data-mongodb 依賴即可。

SpringBoot 官方提供了大量日常企業應用研發各種場景的 spring-boot-starter 依賴模組。這些依賴模組都遵循著約定成俗的預設配置,並允許我們根據自身情況調整這些配置。

總而言之,Starter 提供了以下功能:

  • 整合了模組需要的所有依賴,統一集合到 Starter 中。
  • 提供了預設配置,並允許我們調整這些預設配置。
  • 提供了自動配置類對模組內的 Bean 進行自動裝配,注入 Spring 容器中。

Starter 命名規則

Spring 官方定義的 Starter 通常命名遵循的格式為 spring-boot-starter-{name},例如 spring-boot-starter-data-mongodb。Spring 官方建議,非官方 Starter 命名應遵循 {name}-spring-boot-starter 的格式,例如,myjson-spring-boot-starter。

自定義一個 Starter

瞭解了 Starter 的含義以及應用場景後,我們可以嘗試手寫一個 Starter,加深對它的瞭解以及能在實際工作中,開發出自己的 Starter,提高我們的開發效率。

可能有人會問 Starter 能幹嘛呢?其實在我們的日常開發工作中,總有一些獨立於業務系統之外的配置模組,它是可以在不同專案中進行復用的。如果在每個專案中都編寫重複的模組程式碼,不僅浪費時間和人力,而且還和專案耦合。所以我們將這些可獨立於業務程式碼之外的功能配置模組封裝成一個 Starter,在需要用到此功能模組的專案中,只需要在其 pom.xml 檔案中引用依賴即可,SpringBoot 幫我們完成自動裝配,而且我們還可以在配置檔案中調整 Starter 中預設的配置資訊。

假設我們現在需要實現這樣一個功能:

  1. 根據使用者提供的 Java 物件,將其轉換為 JSON 形式,並且在 JSON 字串中新增指定的前輟和後輟。
  2. 使用者可以動態改變前輟和後輟,即可在 yml 或 properties 配置檔案中自定義。

舉個例子,假如使用者輸入下面這個類的物件 person:

public class Person {
    private String name;
    private int age;
    private String address;
    public Person(String name, int age, String address) {
        super();
        this.name = name;
        this.age = age;
        this.address = address;
    }
    // 省略get和set方法
}
Person person = new Person("Mr.nobody", 18, "拉斯維加斯");

並假設使用者在 application.yml 配置檔案中配置的前輟為 @,後輟為 %,則最終生成的字串為:

@{"address":"拉斯維加斯","age":18,"name":"Mr.nobody"}%

首先新建一個 Maven 工程(當然也可以其他型別例如 Gradle 工程),在 pom.xml 檔案中引入如下依賴。fastjson 依賴是我們業務用到將 Java 物件轉換為 JSON 字串;spring-boot-configuration-processor 依賴是可選的,加入此依賴主要是打包時,自動生成配置元資訊檔案 META-INF/spring-configuration-metadata.json,並放入到 jar 中。方便使用者瞭解到一些配置元資訊。

<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>
	<groupId>com.nobody</groupId>
	<artifactId>myjson-spring-boot-starter</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	<name>myjson-spring-boot-starter</name>
	<description>Demo project for Spring Boot Starter</description>
	<properties>
		<java.version>1.8</java.version>
	</properties>
	<dependencies>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter</artifactId>
			<version>2.3.8.RELEASE</version>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-configuration-processor</artifactId>
			<version>2.3.8.RELEASE</version>
			<optional>true</optional>
		</dependency>
		<dependency>
			<groupId>com.alibaba</groupId>
			<artifactId>fastjson</artifactId>
			<version>1.2.73</version>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-autoconfigure</artifactId>
			<version>2.3.8.RELEASE</version>
		</dependency>
	</dependencies>
</project>

業務處理類,實現 Java 物件轉換為帶有指定前字尾的 JSON 字串。

package com.nobody.myjson.service;

import com.alibaba.fastjson.JSON;

/**
 * @Description 業務處理類
 * @Author Mr.nobody
 * @Date 2021/2/27
 * @Version 1.0
 */
public class MyJsonService {
    // 字首
    private String prefixName;
    // 字尾
    private String suffixName;

    /**
     * 將Java物件轉為帶有指定前字尾的JSON字串
     * 
     * @param o 需要轉換的Java物件
     * @return 轉換後的字串
     */
    public String objectToMyJson(Object o) {
        return prefixName + JSON.toJSONString(o) + suffixName;
    }

    public String getPrefixName() {
        return prefixName;
    }

    public void setPrefixName(String prefixName) {
        this.prefixName = prefixName;
    }

    public String getSuffixName() {
        return suffixName;
    }

    public void setSuffixName(String suffixName) {
        this.suffixName = suffixName;
    }
}

配置類,定義需要的配置資訊和預設配置項,並指明關聯配置檔案的配置項字首。它可以把相同字首的配置資訊通過配置項名稱對映成實體類的屬性中。

package com.nobody.myjson.config;

import org.springframework.boot.context.properties.ConfigurationProperties;

/**
 * @Description 配置類(類名一般為模組名+Properties) nobody.json為Starter使用者通過yml配置檔案動態修改屬性值的變數名字首
 * @Author Mr.nobody
 * @Date 2021/2/27
 * @Version 1.0
 */
@ConfigurationProperties(prefix = "nobody.json")
public class MyJsonProperties {

    // Starter使用者沒在配置檔案中配置prefixName屬性的值時的預設值
    public static final String DEFAULT_PREFIX_NAME = "@";

    // Starter使用者沒在配置檔案中配置suffixName屬性的值時的預設值
    public static final String DEFAULT_SUFFIX_NAME = "@";

    private String prefixName = DEFAULT_PREFIX_NAME;

    private String suffixName = DEFAULT_SUFFIX_NAME;

    public String getPrefixName() {
        return prefixName;
    }

    public void setPrefixName(String prefixName) {
        this.prefixName = prefixName;
    }

    public String getSuffixName() {
        return suffixName;
    }

    public void setSuffixName(String suffixName) {
        this.suffixName = suffixName;
    }
}

自動裝配類,使用 @Configuration 和 @Bean 來進行自動裝配,注入 Spring 容器中。

package com.nobody.myjson.config;

import com.nobody.myjson.service.MyJsonService;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * @Description 自動裝配類
 * @Author Mr.nobody
 * @Date 2021/2/27
 * @Version 1.0
 */
@Configuration // 標識此類是配置類
@ConditionalOnClass(MyJsonService.class) // 表示只有指定的class在classpath上時才能被註冊
@EnableConfigurationProperties(MyJsonProperties.class) // 啟用@ConfigurationProperties
public class MyJsonConfiguration {

    private MyJsonProperties myJsonProperties;

    // 自動注入配置類
    public MyJsonConfiguration(MyJsonProperties myJsonProperties) {
        this.myJsonProperties = myJsonProperties;
    }

    // 建立MyJsonService物件,注入到Spring容器中
    @Bean
    @ConditionalOnMissingBean(MyJsonService.class)  // 當容器沒有此bean時,才註冊
    public MyJsonService myJsonService() {
        MyJsonService myJsonService = new MyJsonService();
        myJsonService.setPrefixName(myJsonProperties.getPrefixName());
        myJsonService.setSuffixName(myJsonProperties.getSuffixName());
        return myJsonService;
    }
}

src/main/resources/META-INF目錄下新建 spring.factories 檔案,輸入以下內容:

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.nobody.myjson.config.MyJsonConfiguration

SpringBoot 專案啟動時,類載入器會從 META-INF / spring.factories 載入給定型別的工廠實現的完全限定類名。也就是說類載入器得到工程中所有 jar 包中的 META-INF/spring.factories 檔案資源,從而得到了一些包括自動配置相關的類的集合,然後將它們例項化,放入 Spring 容器中。

最終專案結構如下:

在開發工具 IDEA 通過 Maven 的 install 命令進行構建打包。或者在專案的目錄下,開啟命令列視窗,使用mvn install命令進行構建打包。打包後,會在工程的 target 目錄下生成一個 jar 包,並且在 maven 本地倉庫也會生成相應的 jar 包。




使用自定義的 Starter

經過上面幾個步驟,我們自定義的 Starter 就開發好了,以下是在其他工程進行引入使用。在需要引用此 Starter 的工程的 pom.xml 檔案中引入此依賴。

<dependency>
   <groupId>com.nobody</groupId>
   <artifactId>myjson-spring-boot-starter</artifactId>
   <version>0.0.1-SNAPSHOT</version>
</dependency>

重新整理依賴,就能在專案的依賴庫中看到此依賴了。

展開,還能檢視此 Starter 可以配置的屬性項有哪些,如下:

然後在需要用到的類中進行注入使用即可。

package com.nobody.controller;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import com.nobody.domain.Person;
import com.nobody.service.MyJsonService;

@RestController
@RequestMapping("demo")
public class DemoController {
    
    // 注入我們Starter中的服務類
    @Autowired
    private MyJsonService myJsonService;
    
    @GetMapping()
    public String test() {
        Person p = new Person("Mr.nobody", 18, "拉斯維加斯");
        // 呼叫服務方法
        return myJsonService.objectToMyJson(p);
    }
}

啟動專案,在瀏覽器中訪問此介面,得到如下結果:

如果我們在 application.yml 檔案中新增以下配置資訊,然後再訪問介面的結果如下,也驗證了我們可以自定義 Starter 中預設的配置項。

nobody: 
  json:
    prefixName: HH
    suffixName: KK

當我們引入此 Starter 時,SpringBoot 會自動裝配,將例項化的 bean 放入 Spring 容器。但我們是否可控制 bean 要不要例項化並放入容器呢?答案是可以做到的。

我們只需要在自動裝配類或者類內的方法,通過 @ConditionalOnXXX 註解就能控制。例如如下所示,使用 Starter 使用者在他的專案的配置檔案中填寫 nobody.json.enable 的值為 false,則就不會自動生成 MyJsonService 例項了。預設不填或者 nobody.json.enable 的值為 true 時,能自動生成 bean 放入容器。這樣使用者就能自己控制 bean 的例項化了。

// 建立MyJsonService物件,注入到Spring容器中
@Bean
@ConditionalOnProperty(name = "nobody.json.enable", matchIfMissing = true)
@ConditionalOnMissingBean(MyJsonService.class) // 當容器沒有此bean時,才註冊
public MyJsonService myJsonService() {
    MyJsonService myJsonService = new MyJsonService();
    myJsonService.setPrefixName(myJsonProperties.getPrefixName());
    myJsonService.setSuffixName(myJsonProperties.getSuffixName());
    return myJsonService;
}

此演示專案已上傳到Github,如有需要可自行下載,歡迎 Star 。

https://github.com/LucioChn/myjson-spring-boot-starter

相關文章