1. 程式人生 > >SpringBoot Starter機制 - 自定義Starter

SpringBoot Starter機制 - 自定義Starter

目錄

  • 前言
  • 1、起源
  • 2、SpringBoot Starter 原理
  • 3、自定義 Starter
    • 3.1 建立 Starter
    • 3.2 測試自定義 Starter

前言

        最近在學習Spring Boot相關的課程,過程中以筆記的形式記錄下來,方便以後回憶,同時也在這裡和大家探討探討,文章中有漏的或者有補充的、錯誤的都希望大家能夠及時提出來,本人在此先謝謝了!

開始之前呢,希望大家帶著幾個問題去學習:
1、SpringBoot Starter 是什麼?
2、這個功能有什麼用?
3、怎麼實現的?
4、這個功能能應用在工作中?
這是對自我的提問,我認為帶著問題去學習,是一種更好的學習方式,有利於加深理解。好了,接下來進入主題。

1、起源

        在 Spring 時代,搭建一個 Web 應用通常需要在 pom 檔案中引入多個 Web 模組相關的 Maven 依賴,如 SpringMvcTomcat 等依賴,而 SpringBoot 則只需引入 spring-boot-starter-web

依賴即可。這就是 SpringBoot 的 Starter 特性,用來簡化專案初始搭建以及開發過程,它是一個功能模組的所有 Maven 依賴集合體。接下來,我們進行詳細討論。

注:本篇文章所用到的 Spring Boot版本是 2.0.3.RELEASE

2、SpringBoot Starter 原理

        SpringBoot 提供了非常多的 Starter,下面列出常用的幾個:

名稱 功能
spring-boot-starter-web 支援 Web 開發,包括 Tomcat 和 spring-webmvc
spring-boot-starter-redis 支援 Redis 鍵值儲存資料庫,包括 spring-redis
spring-boot-starter-test 支援常規的測試依賴,包括 JUnit、Hamcrest、Mockito 以及 spring-test 模組
spring-boot-starter-aop 支援面向切面的程式設計即 AOP,包括 spring-aop 和 AspectJ
spring-boot-starter-data-elasticsearch 支援 ElasticSearch 搜尋和分析引擎,包括 spring-data-elasticsearch
spring-boot-starter-jdbc 支援JDBC資料庫
spring-boot-starter-data-jpa 支援 JPA ,包括 spring-data-jpa、spring-orm、Hibernate

可以看到這些 Starter 的名稱都是以 spring-boot-starter 為開頭,後面跟著具體的模組名,所有官方的 Starter 遵循相似的命名模式。這些 Starter 其實不包含 Java 程式碼,核心是它的 pom 檔案。我們以 spring-boot-starter-web 為例,來看看該 Starter 的 pom 檔案包含的內容。

先在專案中引入以下依賴:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
    <version>2.0.3.RELEASE</version>
</dependency>

然後找到引入的 spring-boot-starter-web 依賴的資料夾位置:

開啟該 pom 檔案進行檢視:

<?xml version="1.0" encoding="UTF-8"?>
<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starters</artifactId>
        <version>2.0.3.RELEASE</version>
    </parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
    <version>2.0.3.RELEASE</version>
    <name>Spring Boot Web Starter</name>
    
    ...
    
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
            <version>2.0.3.RELEASE</version>
            <scope>compile</scope>
        </dependency>

        <!-- 對 json 解析的支援 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-json</artifactId>
            <version>2.0.3.RELEASE</version>
            <scope>compile</scope>
        </dependency>

        <!-- 提供 Tomcat 容器。通過這可以看到  ServletWeb 預設的容器是 Tomcat -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-tomcat</artifactId>
            <version>2.0.3.RELEASE</version>
            <scope>compile</scope>
        </dependency>

        <!-- hibernate 的校驗框架 -->
        <dependency>
            <groupId>org.hibernate.validator</groupId>
            <artifactId>hibernate-validator</artifactId>
            <version>6.0.10.Final</version>
            <scope>compile</scope>
        </dependency>

        <!-- 提供了核心 HTTP 整合,用於整合其它 web 框架的基礎結構 -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-web</artifactId>
            <version>5.0.7.RELEASE</version>
            <scope>compile</scope>
        </dependency>

        <!-- 提供了對 Spring MVC 的支援 -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-webmvc</artifactId>
            <version>5.0.7.RELEASE</version>
            <scope>compile</scope>
        </dependency>
    </dependencies>
</project>

可以看到,在該 pom 檔案中已經定義好了 Web 模組需要的各個元件。之後,引入的 Starter 依賴可以與 SpringBoot 的自動裝配特性、外部化配置特性進行無縫銜接,來達到快速開發的目的。關於 SpringBoot 自動裝配和外部化配置大家可以分別參考《SpringBoot 自動裝配(二)》和《SpringBoot 外部化配置(二)》這兩篇文章。接下來,通過實現自定義的 Starter 來理解整體邏輯。

3、自定義 Starter

        先建立一個專案,在該專案中定義 Starter 的內容,然後通過 Maven 將其打成 jar 包,之後在另一個專案中使用該 Starter 。

3.1 建立 Starter

1、建立一個 Maven 專案,在其 pom 檔案中引入自動裝配的依賴,並定義好 Starter 的名稱。非官方的 Starter 命名需遵循 xxx-spring-boot-starter 的格式。

<?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.loong</groupId>
    <artifactId>demo-spring-boot-starter</artifactId>
    <version>1.0.0.RELEASE</version>
    <name>demo</name>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-autoconfigure</artifactId>
            <version>2.0.3.RELEASE</version>
        </dependency>
    </dependencies>

</project>

2、新建一個 Properties 配置類,用於儲存外部化配置檔案中定義的配置資料,其中配置檔案包括 properties 或 yml 。

// 定義配置檔案中的屬性字首
@ConfigurationProperties(prefix = "demo")
public class DemoProperties {

    private String name;

    private String date;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getDate() {
        return date;
    }

    public void setDate(String date) {
        this.date = date;
    }
}

關於外部化配置底層實現,大家可以參考《SpringBoot 外部化配置(二)》這篇文章。

3、新建一個功能類,主要用來返回 DemoProperties 中的 name 和 date 屬性。

public class DemoService {

    private DemoProperties demoProperties;

    public DemoService(DemoProperties demoProperties) {
        this.demoProperties = demoProperties;
    }

    public String getName() {
        return demoProperties.getName();
    }

    public String getDate() {
        return demoProperties.getDate();
    }
}

4、建立自動配置類,在該配置類中完成 Starter 的功能。這裡,通過構造器注入 DemoProperties 配置類物件,並初始化 DemoService 功能類。

@Configuration
@EnableConfigurationProperties(value = DemoProperties.class)
public class DemoAutoConfiguration {

    private final DemoProperties demoProperties;

    public DemoAutoConfiguration(DemoProperties demoProperties) {
        this.demoProperties = demoProperties;
    }

    @Bean
    // 當前專案是否包含 DemoService Class 
    @ConditionalOnMissingBean(DemoService.class)
    public DemoService demoService() {
        return new DemoService(demoProperties);
    }
}

自動配置類是 SpringBoot 自動裝配特性不可或缺的一環,關於 SpringBoot 自動裝配底層實現,大家可以參考《SpringBoot自動裝配(二)》這篇文章。

5、自定義初始化器和監聽器,這是 SpringBoot 提供的擴充套件點,主要在 SpringBoot 的不同生命週期執行相應操作。

public class DemoApplicationContextInitializer implements
        ApplicationContextInitializer<ConfigurableApplicationContext> {

    @Override
    public void initialize(ConfigurableApplicationContext configurableApplicationContext) {
        System.out.println(" DemoApplicationContextInitializer 初始化成功 ");
    }
}
public class DemoApplicationListener implements ApplicationListener<SpringApplicationEvent> {

    @Override
    public void onApplicationEvent(SpringApplicationEvent springApplicationEvent) {
        if (springApplicationEvent instanceof ApplicationStartingEvent) {
            System.out.println(" DemoApplicationListener 監聽 ApplicationStartingEvent 事件");
        }
    }
}

關於初始化器和監聽器大家可以參考《Spring Boot SpringApplication啟動類(一)》的 2.2 和 2.3 小節 。

6、在 src/main/resources 目錄下建立 META-INF 資料夾,並在資料夾中建立 spring.factories 檔案,定義如下內容:

# Initializers
org.springframework.context.ApplicationContextInitializer=\
com.loong.demo.context.DemoApplicationContextInitializer

# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.loong.demo.configuration.DemoAutoConfiguration

# Application Listeners
org.springframework.context.ApplicationListener=\
com.loong.demo.listener.DemoApplicationListener

這是 SpringBoot 的規約,當我們自定義的初始化器、監聽器及自動配置類需要被 SpringBoot 讀取時,必須定義成該格式。關於原理,在前幾篇文章說過,這裡不再敘述。

最後,所有的類已經定義完成,專案結構如下:

開啟通過右側的 Maven 工具欄,點選 install 打包到本地的 Maven 庫。

之後,自定義的 Starter 就可以使用,我們來測試一下。

3.2 測試自定義 Starter

1、在另一個專案中引入該 Starter 的 Maven 依賴:

<dependency>
    <groupId>com.loong</groupId>
    <artifactId>demo-spring-boot-starter</artifactId>
    <version>1.0.0.RELEASE</version>
</dependency>

2、在 properties 檔案中定義配置資料:

demo.name = loong
demo.date = 2020.01.01

3、在啟動類中,獲取 DemoService Bean ,並呼叫它的 getDate 和 getName 方法獲取配置檔案中的資料:

@SpringBootApplication
public class DiveInSpringBootApplication {
    public static void main(String[] args) {
        ConfigurableApplicationContext run = SpringApplication.run(DiveInSpringBootApplication.class, args);

        DemoService bean = run.getBean(DemoService.class);
        System.out.println(bean.getDate() + " === " + bean.getName());

    }
}

最後,檢視控制檯的輸出:

/Library/Java/JavaVirtualMachines/jdk1.8.0_201.jdk/Contents/Home/bin/java "-javaagent:/Applications/IntelliJ IDEA CE.app..."
 DemoApplicationListener 監聽 ApplicationStartingEvent 事件

  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::        (v2.0.3.RELEASE)

 DemoApplicationContextInitializer 初始化成功 
2020-01-01 13:14:02.023  INFO 55657 --- [           main] o.s.w.s.handler.SimpleUrlHandlerMapping  : Mapped URL path [/**/favicon.ico] onto handler of type [class org.springframework.web.servlet.resource.ResourceHttpRequestHandler]
2020-01-01 13:14:02.189  INFO 55657 --- [           main] s.w.s.m.m.a.RequestMappingHandlerAdapter : Looking for @ControllerAdvice: org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext@6b19b79: startup date [Wed Jan 01 13:13:59 CST 2020]; root of context hierarchy
2020-01-01 13:14:02.257  INFO 55657 --- [           main] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/hello],methods=[GET]}" onto public java.lang.String com.loong.diveinspringboot.Chapter1.controller.HelloWorldController.helloWorld(java.lang.String)
2020-01-01 13:14:02.260  INFO 55657 --- [           main] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/error]}" onto public org.springframework.http.ResponseEntity<java.util.Map<java.lang.String, java.lang.Object>> org.springframework.boot.autoconfigure.web.servlet.error.BasicErrorController.error(javax.servlet.http.HttpServletRequest)
2020-01-01 13:14:02.261  INFO 55657 --- [           main] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/error],produces=[text/html]}" onto public org.springframework.web.servlet.ModelAndView org.springframework.boot.autoconfigure.web.servlet.error.BasicErrorController.errorHtml(javax.servlet.http.HttpServletRequest,javax.servlet.http.HttpServletResponse)
2020-01-01 13:14:02.296  INFO 55657 --- [           main] o.s.w.s.handler.SimpleUrlHandlerMapping  : Mapped URL path [/webjars/**] onto handler of type [class org.springframework.web.servlet.resource.ResourceHttpRequestHandler]
2020-01-01 13:14:02.296  INFO 55657 --- [           main] o.s.w.s.handler.SimpleUrlHandlerMapping  : Mapped URL path [/**] onto handler of type [class org.springframework.web.servlet.resource.ResourceHttpRequestHandler]
2020-01-01 13:14:02.341  WARN 55657 --- [           main] ion$DefaultTemplateResolverConfiguration : Cannot find template location: classpath:/templates/ (please add some templates or check your Thymeleaf configuration)
2020-01-01 13:14:02.718  INFO 55657 --- [           main] o.s.b.a.e.web.EndpointLinksResolver      : Exposing 2 endpoint(s) beneath base path '/actuator'
2020-01-01 13:14:02.726  INFO 55657 --- [           main] s.b.a.e.w.s.WebMvcEndpointHandlerMapping : Mapped "{[/actuator/health],methods=[GET],produces=[application/vnd.spring-boot.actuator.v2+json || application/json]}" onto public java.lang.Object org.springframework.boot.actuate.endpoint.web.servlet.AbstractWebMvcEndpointHandlerMapping$OperationHandler.handle(javax.servlet.http.HttpServletRequest,java.util.Map<java.lang.String, java.lang.String>)
2020-01-01 13:14:02.727  INFO 55657 --- [           main] s.b.a.e.w.s.WebMvcEndpointHandlerMapping : Mapped "{[/actuator/info],methods=[GET],produces=[application/vnd.spring-boot.actuator.v2+json || application/json]}" onto public java.lang.Object org.springframework.boot.actuate.endpoint.web.servlet.AbstractWebMvcEndpointHandlerMapping$OperationHandler.handle(javax.servlet.http.HttpServletRequest,java.util.Map<java.lang.String, java.lang.String>)
2020-01-01 13:14:02.728  INFO 55657 --- [           main] s.b.a.e.w.s.WebMvcEndpointHandlerMapping : Mapped "{[/actuator],methods=[GET],produces=[application/vnd.spring-boot.actuator.v2+json || application/json]}" onto protected java.util.Map<java.lang.String, java.util.Map<java.lang.String, org.springframework.boot.actuate.endpoint.web.Link>> org.springframework.boot.actuate.endpoint.web.servlet.WebMvcEndpointHandlerMapping.links(javax.servlet.http.HttpServletRequest,javax.servlet.http.HttpServletResponse)
2020-01-01 13:14:02.766  INFO 55657 --- [           main] o.s.j.e.a.AnnotationMBeanExporter        : Registering beans for JMX exposure on startup
2020-01-01 13:14:02.822  INFO 55657 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat started on port(s): 8080 (http) with context path ''
2020-01-01 13:14:02.826  INFO 55657 --- [           main] c.l.d.C.DiveInSpringBootApplication      : Started DiveInSpringBootApplication in 3.607 seconds (JVM running for 3.984)
2020.01.01 === loong

可以看到,結果正確輸出,且初始化器和監聽器都已被載入。這裡只是一個簡單的演示,Starter 較為簡單,大家可以根據實際情況實現一個更為複雜的。

SpringBoot Starter 的內容就介紹到這,如果文章中有錯誤或者需要補充的請及時提出,本人感激不