世界今頭條!手把手帶你開發starter,點對點帶你講解原理

2023-01-03 12:12:08 來源:51CTO博客
京東物流 孔祥東
_____            _             ____              _     / ____|          (_)           |  _ \            | |   | (___  _ __  _ __ _ _ __   __ _| |_) | ___   ___ | |_   ___ | "_ | "__| | "_ \ / _` |  _ < / _ \ / _ | __|  ____) | |_) | |  | | | | | (_| | |_) | (_) | (_) | |_  |_____/| .__/|_|  |_|_| |_|__, |____/ ___/ ___/ __|        | |                  __/ |                              |_|                 |___/

1. 為什么要用Starter?

現在我們就來回憶一下,在還沒有Spring-boot框架的時候,我們使用Spring 開發項目,如果需要某一個框架,例如mybatis,我們的步驟一般都是:

到maven倉庫去找需要引入的mybatis jar包,選取合適的版本(易發生沖突)

到maven倉庫去找mybatis-spring整合的jar包,選取合適的版本(易發生沖突)


(相關資料圖)

在spring的applicationContext.xml文件中配置dataSource和mybatis相關信息

假如所有工作都到位,一般可以一氣呵成;但很多時候都會花一堆時間解決jar 沖突,配置項缺失,導致怎么都啟動不起來等等,各種問題。

所以在2012 年 10 月,一個叫 Mike Youngstrom 的人在 Spring Jira 中創建了一個功能請求,要求在 Spring Framework 中支持無容器 Web 應用程序體系結構,提出了在主容器引導 Spring 容器內配置 Web 容器服務;這件事情對 SpringBoot 的誕生應該說是起到了一定的推動作用。

所以SpringBoot 設計的目標就是簡化繁瑣配置,快速建立Spring 應用。

然后在開發Spring-boot 應用的是時候, 經??梢钥吹轿覀兊膒om 文件中引入了spring-boot-starter-web、spring-boot-starter-data-redis、mybatis-spring-boot-starter 這樣的依賴,然后幾乎不用任何配置就可以使用這些依賴的功能,真正的感受到了開箱即用的爽。

下面我們就先來嘗試自己開發一個Starter。

2. 命名規范

在使用spring-boot-starter,會發現,有的項目名稱是 XX-spring-boot-starter,有的是spring-boot-starter-XX,這個項目的名稱有什么講究呢?從springboot官方文檔摘錄:

這段話的大概意思就是,麻煩大家遵守這個命名規范:

Srping官方命名格式為:spring-boot-starter-{name}

非Spring官方建議命名格式:{name}-spring-boot-starter

3. 開發示例

下面我就以記錄日志的一個組件為示例來講述開發一個starter 的過程。

3.1 新建工程

首先新建一個maven 工程,名稱定義為jd-log-spring-boot-starter

3.2 Pom 引入依賴

  4.0.0      org.springframework.boot    spring-boot-starter-parent    2.5.13         com.jd  jd-log-spring-boot-starter  1.0-SNAPSHOT  jd-log-spring-boot-starter  http://www.example.com      UTF-8    1.8    1.8                  org.springframework.boot      spring-boot-autoconfigure                  org.springframework.boot      spring-boot-configuration-processor                  org.springframework.boot      spring-boot-starter-aop              org.projectlombok      lombok                          org.apache.maven.plugins        maven-source-plugin        2.2.1                              attach-sources                          jar-no-fork                                          

這邊稍微解釋一下這幾個依賴:

spring-boot-autoconfigure :提供自動化裝配功能,是為了Spring Boot 應用在各個模塊提供自動化配置的作用;即加入對應 pom,就會有對應配置其作用;所以我們想要自動裝配功能,就需要引入這個依賴。

spring-boot-configuration-processor:將自定義的配置類生成配置??元數據??,所以在引用自定義STARTER的工程的YML文件中,給自定義配置初始化時,會有屬性名的提示;確保在使用@ConfigurationProperties注解時,可以優雅的讀取配置信息,引入該依賴后,IDEA不會出現“spring boot configuration annotation processor not configured”的錯誤;編譯之后會在META-INF 下生成一個spring-configuration-metadata.json 文件,大概內容就是定義的配置的元數據;效果如下截圖。

spring-boot-starter-aop :這個就不用解釋了,因為示例是記錄日志,我們用到切面的功能,所以需要引入。

3.3 定義屬性配置

/** * @author kongxiangdong2 * @Title: LogProperties * @ProjectName jd-log-spring-boot-starter * @Description: TODO * @date 2022/9/110:04 */@ConfigurationProperties(prefix = "jd")@Datapublic class LogProperties {    /**     * 是否開啟日志     */    private boolean enable;    /**     * 平臺:不同服務使用的區分,默認取 spring.application.name     */    @Value("${spring.application.name:#{null}}")    private String platform;

@ConfigurationProperties:該注解和@Value 注解作用類似,用于獲取配置文件中屬性定義并綁定到Java Bean 或者屬性中;換句話來說就是將配置文件中的配置封裝到JAVA 實體對象,方便使用和管理。

這邊我們定義兩個屬性,一個是是否開啟日志的開關,一個是標識平臺的名稱。

3.4 定義自動配置類

/** * @author kongxiangdong2 * @Title: JdLogAutoConfiguration * @ProjectName jd-log-spring-boot-starter * @Description: TODO * @date 2022/9/110:06 */@Configuration@ComponentScan("com.jd")@ConditionalOnProperty(prefix = "jd",name = "enable",havingValue = "true",matchIfMissing = false)@EnableConfigurationProperties({LogProperties.class})public class JdLogAutoConfiguration {   //}

這個類最關鍵了,它是整個starter 最重要的類,它就是將配置自動裝載進spring-boot的;具體是怎么實現的,下面在講解原理的時候會再詳細說說,這里先完成示例。

@Configuration :這個就是聲明這個類是一個配置類

@ConditionalOnProperty:??作用是可以指定prefix.name?? 配置文件中的屬性值來判定configuration是否被注入到Spring,就拿上面代碼的來說,會根據配置文件中是否配置jd.enable 來判斷是否需要加載JdLogAutoConfiguration 類,如果配置文件中不存在或者配置的是等于false 都不會進行加載,如果配置成true 則會加載;指定了havingValue,要把配置項的值與havingValue對比,一致則加載Bean;配置文件缺少配置,但配置了matchIfMissing = true,加載Bean,否則不加載。

在這里稍微擴展一下經常使用的Condition

注解

類型

說明

@ConditionalOnClass

Class Conditions類條件注解

當前classpath下有指定類才加載

@ConditionalOnMissingClass

Class Conditions類條件注解

當前classpath下無指定類才加載

@ConditionalOnBean

Bean ConditionsBean條件注解

當期容器內有指定bean才加載

@ConditionalOnMissingBean

Bean ConditionsBean條件注解

當期容器內無指定bean才加載

@ConditionalOnProperty

Property Conditions環境變量條件注解(含配置文件)

prefix 前綴name 名稱havingValue 用于匹配配置項值matchIfMissing 沒找指定配置項時的默認值

@ConditionalOnResource

ResourceConditions 資源條件注解

有指定資源才加載

@ConditionalOnWebApplication

Web Application Conditionsweb條件注解

是web才加載

@ConditionalOnNotWebApplication

Web Application Conditionsweb條件注解

不是web才加載

@ConditionalOnExpression

SpEL Expression Conditions

符合SpEL 表達式才加載

@EnableConfigurationProperties使@ConfigurationProperties 注解的類生效。

3.5 配置EnableAutoConfiguration

在resources/META-INF/ 目錄新建spring.factories 文件,配置內容如下;

org.springframework.boot.autoconfigure.EnableAutoConfiguration=com.jd.JdLogAutoConfiguration

好了,至此自定義Starter 大體框架已經好了,下面就是我們記錄日志的功能。

3.6 業務功能實現

首先我們先定義一個注解Jdlog

/** * @author kongxiangdong2 * @Title: Jdlog * @ProjectName jd-log-spring-boot-starter * @Description: TODO * @date 2022/9/110:04 */@Target(ElementType.METHOD)@Retention(RetentionPolicy.RUNTIME)public @interface Jdlog {}

定義切面執行邏輯,這邊就簡單的打印一下配置文件的屬性值+目標執行方法+耗時。

import com.jd.annotation.Jdlog;import com.jd.config.LogProperties;import lombok.AllArgsConstructor;import lombok.extern.slf4j.Slf4j;import org.aspectj.lang.ProceedingJoinPoint;import org.aspectj.lang.annotation.Around;import org.aspectj.lang.annotation.Aspect;import org.aspectj.lang.annotation.Pointcut;import org.springframework.stereotype.Component;/** * @author kongxiangdong2 * @Title: LogAspectjProcess * @ProjectName jd-log-spring-boot-starter * @Description: TODO * @date 2022/9/111:12 */@Aspect@Component@Slf4j@AllArgsConstructorpublic class LogAspectjProcess {    LogProperties logProperties;    /**     * 定義切點     */    @Pointcut("@annotation(com.jd.annotation.Jdlog)")    public void pointCut(){}    /**     * 環繞通知     *     * @param thisJoinPoint     * @param jdlog     * @return     */    @Around("pointCut() && @annotation(jdlog)")    public Object around(ProceedingJoinPoint thisJoinPoint, Jdlog jdlog){        //執行方法名稱        String taskName = thisJoinPoint.getSignature()                .toString().substring(                        thisJoinPoint.getSignature()                                .toString().indexOf(" "),                        thisJoinPoint.getSignature().toString().indexOf("("));        taskName = taskName.trim();        long time = System.currentTimeMillis();        Object result = null;        try {            result = thisJoinPoint.proceed();        } catch (Throwable throwable) {            throwable.printStackTrace();        }        log.info("{} -- method:{} run :{} ms",logProperties.getPlatform(), taskName,                (System.currentTimeMillis() - time));        return result;

整體項目結構就是這樣子

好了,現在就可以打包編譯安裝

3.7 測試使用

然后就可以在其他項目中引入使用了;下面以一個簡單的spring-boot web 項目做個測試,在pom 中引入下面的依賴配置。

      com.jd      jd-log-spring-boot-starter      1.0-SNAPSHOT    

增加一個http 訪問的方法,標注上@Jdlog 注解

application.yaml 文件中配置

jd:  enable: true  platform: "測試項目"

啟動測試,訪問地址??http://localhost:8080/test/method1,控制臺打印如下:??

咋樣,自定義的Starter是不是特別的簡單啊,快動手試試吧!

上面我們講的都是怎么去開發一個starter,但是到底為什么要這樣,spring-boot 是如何去實現的?是不是還不知道?那下面我們就來說說;

代碼示例地址:

4. 原理講解

我們上面已經看到一個starter,只需要引入到pom 文件中,再配置一下(其實都可以不配置)jd.enable=true,就可以直接使用記錄日志的功能了,Spring-boot 是怎么做到的?

在開始的時候說過,Spring-boot 的好處就是可以自動裝配。那下面我就來說說自動裝配的原理。

相比于傳統Spring 應用,我們搭建一個SpringBoot 應用,我們只需要引入一個注解(前提:引入springBoot y依賴)@SpringBootApplication,就可以直接運行;所以我們就從這個注解開始入手,看看這個注解到底做了寫什么?

SpringBootApplication 注解

點開@SpringBootApplication注解可以看到包含了@SpringBootConfiguration,@EnableAutoConfiguration,@ComponentScan三個注解。

前面的四個注解就不用過多敘述了,是定義注解最基本的,關鍵在于后面的三個注解:@SpringBootConfiguration,@EnableAutoConfiguration,@ComponentScan,其實也就是說在啟動類上如果不使用@SpringBootApplication 這個復合注解,直接使用者三個注解一樣可以達到相同的效果。

@SpringBootConfiguration 注解:我們再次點進去看這個注解,其實它就是一個@Configuration 注解。

@ComponentScan 注解

@ComponentScan 注解:配置包掃描定義的掃描路徑,把符合掃描規則的類裝配到spring容器

@EnableAutoConfiguration 注解

@EnableAutoConfiguration 打開自動裝配(自動配置著重來看該注解)

注解

作用

解釋

@SpringBootConfiguration

標記當前類為配置類

加上這個注解就是為了讓當前類作為一個配置類交由 Spring 的 IOC 容器進行管理,因為前面我們說了,SpringBoot 本質上還是 Spring,所以原屬于 Spring 的注解 @Configuration 在 SpringBoot 中也可以直接應用

@ComponentScan

配置包掃描定義的掃描路徑,把符合掃描規則的類裝配到spring容器

用于定義 Spring 的掃描路徑,等價于在 xml 文件中配置 ,假如不配置掃描路徑,那么 Spring 就會默認掃描當前類所在的包及其子包中的所有標注了 @Component,@Service,@Controller 等注解的類。

@EnableAutoConfiguration

打開自動裝配

下面著重講解

我們再次點擊@EnableAutoConfiguration進入查看,它是一個由 @AutoConfigurationPackage 和 @Import 注解組成的復合注解;

首先我們先來看@Import 這個注解,這個是比較關鍵的一個注解;

在說這個注解之前我們先舉個例子,假如我們有一個類Demo,它是一個不在啟動配置類目錄之下的,也就意味著它不會被掃描到,Spring 也無法感知到它的存在,那么如果需要能將它被掃描到,是不是我們可以通過加@Import 注解來導入Demo 類,類似如下代碼

@Configuration@Import(Demo.class)public class MyConfiguration {}

所以,我們可以知道@Import 注解其實就是為了去導入一個類。所以這里@Import({AutoConfigurationImportSelector.class}) 就是為了導入AutoConfigurationImportSelector 類,那我們繼續來看這個類,AutoConfigurationImportSelector實現的是??DeferredImportSelector??接口,這是一個延遲導入的類;再細看會有一個方法比較顯眼,根據注解元數據來選擇導入組件,當注解元數據空,直接返回一個空數組;否則就調用getAutoConfigurationEntry,方法中會使用AutoConfigurationEntry的getConfigurations(),configurations是一個??List??,那么我們看下AutoConfigurationEntry是怎么生成的。

進入到getAutoConfigurationEntry 方法中可以看到主要是getCandidateConfigurations 來獲取候選的 Bean,并將其存為一個集合;后續的方法都是在去重,校驗等一系列的操作。

我們繼續往getCandidateConfigurations 方法里看,最終通過SpringFactoriesLoader.loadFactoryNames來獲取最終的configurations,并且可以通過斷言發現會使用到META-INF/spring.factories文件,那么我們再進入SpringFactoriesLoader.loadFactoryNames()中來看下最終的實現。

SpringFactoriesLoader.loadFactoryNames()方法會讀取META-INF/spring.factories文件下的內容到Map中,再結合傳入的factoryType=EnableAutoConfiguration.class,因此會拿到 org.springframework.boot.autoconfigure.EnableAutoConfiguration為key對應的各個XXAutoConfiguration的值,然后springboot在結合各個starter中的代碼完成對于XXAutoConfiguration中的Bean的加載動作。

這邊再擴展一下這個內容,通過 SpringFactoriesLoader 來讀取配置文件 spring.factories 中的配置文件的這種方式是一種 SPI 的思想。

@AutoConfigurationPackage 注解

進入這個注解看,其實它就是導入了Registrar 這個類

再進入這個類查看,它其實是一個內部類,看代碼的大概意思就是讀取到我們在最外層的 @SpringBootApplication 注解中配置的掃描路徑(沒有配置則默認當前包下),然后把掃描路徑下面的Bean注冊到容器中;

總結

好了,現在我們大概來理一下整個自動裝配的流程:

啟動類中通過使用@SpringBootApplication實現自動裝配的功能;

實際注解@SpringBootApplication是借助注解@EnableAutoConfiguration的功能。

在注解@EnableAutoConfiguration中又有兩個注解,@AutoConfigurationPackage,@EnableAutoConfiguration。

通過@AutoConfigurationPackage實現對于當前項目中Bean的進行加載;

@EnableAutoConfiguration通過@Import({AutoConfigurationImportSelector.class})實現對于Pom引入的start中的XXAutoConfiguration的加載;

@AutoConfigurationImportSelector類中通過SpringFactoriesLoader讀取 META-INF/spring.factories中key為org.springframework.boot.autoconfigure.EnableAutoConfiguration對應的各個XXAutoConfiguration的值,然后springboot在結合各個start中的代碼完成對于XXAutoConfiguration中的Bean的加載動作;

到這里是不是已經可以很了然對我們之前開發starter中的定義了啊,趕緊試試吧

標簽: 配置文件 自動裝配 記錄日志

上一篇:嵌入式:ARM中斷系統設計全解
下一篇:環球微速訊:第十九章《類的加載與反射》第2節:類加載器