SpringBoot(八)Starter機制 - 自定義Starter

前言

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

開始之前呢,希望大家?guī)е鴰讉€問題去學習:
1芥炭、SpringBoot Starter 是什么漓库?
2、這個功能有什么用园蝠?
3渺蒿、怎么實現(xiàn)的?
4彪薛、這個功能能應用在工作中茂装?
這是對自我的提問怠蹂,我認為帶著問題去學習佩厚,是一種更好的學習方式氢烘,有利于加深理解。好了咱筛,接下來進入主題彼妻。

1嫌佑、起源

????????在 Spring 時代,搭建一個 Web 應用通常需要在 pom 文件中引入多個 Web 模塊相關的 Maven 依賴侨歉,如 SpringMvc屋摇、Tomcat 等依賴,而 SpringBoot 則只需引入 spring-boot-starter-web 依賴即可幽邓。這就是 SpringBoot 的 Starter 特性炮温,用來簡化項目初始搭建以及開發(fā)過程,它是一個功能模塊的所有 Maven 依賴集合體颊艳。接下來茅特,我們進行詳細討論。

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

2棋枕、SpringBoot Starter 原理

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

名稱 功能
spring-boot-starter-web 支持 Web 開發(fā),包括 Tomcat 和 spring-webmvc
spring-boot-starter-redis 支持 Redis 鍵值存儲數(shù)據(jù)庫重斑,包括 spring-redis
spring-boot-starter-test 支持常規(guī)的測試依賴兵睛,包括 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數(shù)據(jù)庫
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 依賴的文件夾位置:

image

打開該 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 文件中已經(jīng)定義好了 Web 模塊需要的各個組件倦踢。之后送滞,引入的 Starter 依賴可以與 SpringBoot 的自動裝配特性、外部化配置特性進行無縫銜接硼一,來達到快速開發(fā)的目的累澡。關于 SpringBoot 自動裝配和外部化配置大家可以分別參考《SpringBoot 自動裝配(二)》《SpringBoot 外部化配置(二)》這兩篇文章。接下來般贼,通過實現(xiàn)自定義的 Starter 來理解整體邏輯。

3奥吩、自定義 Starter

????????先創(chuàng)建一個項目哼蛆,在該項目中定義 Starter 的內容,然后通過 Maven 將其打成 jar 包霞赫,之后在另一個項目中使用該 Starter 腮介。

3.1 創(chuàng)建 Starter

1、創(chuàng)建一個 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 配置類,用于保存外部化配置文件中定義的配置數(shù)據(jù)抵代,其中配置文件包括 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;
    }
}

關于外部化配置底層實現(xiàn),大家可以參考《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劈榨、創(chuàng)建自動配置類,在該配置類中完成 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 自動裝配特性不可或缺的一環(huán)邑闺,關于 SpringBoot 自動裝配底層實現(xiàn),大家可以參考《SpringBoot 自動裝配(二)》這篇文章棕兼。

5陡舅、自定義初始化器和監(jiān)聽器,這是 SpringBoot 提供的擴展點伴挚,主要在 SpringBoot 的不同生命周期執(zhí)行相應操作靶衍。

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 監(jiān)聽 ApplicationStartingEvent 事件");
        }
    }
}

關于初始化器和監(jiān)聽器大家可以參考《Spring Boot SpringApplication啟動類(一)》的 2.2 和 2.3 小節(jié) 灾炭。

6、在 src/main/resources 目錄下創(chuàng)建 META-INF 文件夾颅眶,并在文件夾中創(chuàng)建 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 的規(guī)約,當我們自定義的初始化器涛酗、監(jiān)聽器及自動配置類需要被 SpringBoot 讀取時铡原,必須定義成該格式。關于原理商叹,在前幾篇文章說過燕刻,這里不再敘述。

最后剖笙,所有的類已經(jīng)定義完成卵洗,項目結構如下:

image

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

image

之后过蹂,自定義的 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 文件中定義配置數(shù)據(jù):

demo.name = loong
demo.date = 2020.01.01

3晚岭、在啟動類中鸥印,獲取 DemoService Bean ,并調用它的 getDate 和 getName 方法獲取配置文件中的數(shù)據(jù):

@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 監(jiān)聽 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

可以看到库说,結果正確輸出,且初始化器和監(jiān)聽器都已被加載片择。這里只是一個簡單的演示潜的,Starter 較為簡單,大家可以根據(jù)實際情況實現(xiàn)一個更為復雜的字管。

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

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末亡呵,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子硫戈,更是在濱河造成了極大的恐慌锰什,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,607評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異汁胆,居然都是意外死亡梭姓,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,239評論 3 395
  • 文/潘曉璐 我一進店門嫩码,熙熙樓的掌柜王于貴愁眉苦臉地迎上來誉尖,“玉大人,你說我怎么就攤上這事铸题≌∷。” “怎么了?”我有些...
    開封第一講書人閱讀 164,960評論 0 355
  • 文/不壞的土叔 我叫張陵回挽,是天一觀的道長没咙。 經(jīng)常有香客問我,道長千劈,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,750評論 1 294
  • 正文 為了忘掉前任牌捷,我火速辦了婚禮墙牌,結果婚禮上,老公的妹妹穿的比我還像新娘暗甥。我一直安慰自己喜滨,他們只是感情好,可當我...
    茶點故事閱讀 67,764評論 6 392
  • 文/花漫 我一把揭開白布撤防。 她就那樣靜靜地躺著虽风,像睡著了一般。 火紅的嫁衣襯著肌膚如雪寄月。 梳的紋絲不亂的頭發(fā)上辜膝,一...
    開封第一講書人閱讀 51,604評論 1 305
  • 那天,我揣著相機與錄音漾肮,去河邊找鬼厂抖。 笑死,一個胖子當著我的面吹牛克懊,可吹牛的內容都是我干的忱辅。 我是一名探鬼主播,決...
    沈念sama閱讀 40,347評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼谭溉,長吁一口氣:“原來是場噩夢啊……” “哼墙懂!你這毒婦竟也來了?” 一聲冷哼從身側響起扮念,我...
    開封第一講書人閱讀 39,253評論 0 276
  • 序言:老撾萬榮一對情侶失蹤损搬,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體场躯,經(jīng)...
    沈念sama閱讀 45,702評論 1 315
  • 正文 獨居荒郊野嶺守林人離奇死亡谈为,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,893評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了踢关。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片伞鲫。...
    茶點故事閱讀 40,015評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖签舞,靈堂內的尸體忽然破棺而出秕脓,到底是詐尸還是另有隱情,我是刑警寧澤儒搭,帶...
    沈念sama閱讀 35,734評論 5 346
  • 正文 年R本政府宣布吠架,位于F島的核電站,受9級特大地震影響搂鲫,放射性物質發(fā)生泄漏傍药。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,352評論 3 330
  • 文/蒙蒙 一魂仍、第九天 我趴在偏房一處隱蔽的房頂上張望拐辽。 院中可真熱鬧,春花似錦擦酌、人聲如沸俱诸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,934評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽睁搭。三九已至,卻和暖如春笼平,著一層夾襖步出監(jiān)牢的瞬間园骆,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,052評論 1 270
  • 我被黑心中介騙來泰國打工出吹, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留遇伞,地道東北人。 一個月前我還...
    沈念sama閱讀 48,216評論 3 371
  • 正文 我出身青樓捶牢,卻偏偏與公主長得像鸠珠,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子秋麸,可洞房花燭夜當晚...
    茶點故事閱讀 44,969評論 2 355

推薦閱讀更多精彩內容