前言
作為一個有架構夢想的程式員,自定義 springboot-starter 是我們必須要掌握的技能。企業中很多項目都會有自己封裝 starter 的需求。這也是我 2019 年底出去面試被問過的面試題,當時作為一個剛畢業半年的小白,隻會用官方制作好的,的确沒有自己去實作過。希望這篇文章能對還不會制作 starter 的同學有幫助~~
什麼是 springboot-starter & 工作原理
我們都知道 SpringBoot 這麼火的核心原因之一就是它提供了一系列的啟動場景 starter ,這裡面做好了各種開源元件的封裝,引入依賴即可使用。可以回想一下我們項目中整合 Redis 用到的 RedisTemplate ,整合 RabbitMQ 用到的 RabbitTemplate 等,你有沒有想過為什麼在 application.yml 中配置一些屬性就可以直接在程式中注入然後用這些模闆類呢?
@Autowired
private StringRedisTemplate redisTemplate;
@Autowired
private RabbitTemplate rabbitTemplate;
其實原理還是比較容易了解的,以 RedisTemplate 自動配置為例,閱讀 RedisAutoConfiguration 類的源碼就會發現 ,簡單來說就是源碼中讀取 application.yml 中的相關配置,将這些配置設定到 RedisTemplate 中,然後再将 RedisTemplate 對象注入到 Spring 容器。這樣一來,我們需要用的時候,直接從 Spring 容器中拿就可以了。
使用 starter 的好處
以使用分布式任務排程架構 xxl-job 為例,我們參考官網的給的 demo ,首先要在配置檔案中配置 address、ip、port 等屬性,然後要寫個配置類接受這些屬性,設定到 XxlJobSpringExecutor 對象,然後再将 XxlJobSpringExecutor 對象注入到 Spring 容器中。
@Configuration
public class XxlJobConfig {
@Value("${xxl.job.admin.addresses}") //讀取配置檔案中的值
private String adminAddresses;
/**
* 注入其他屬性省略...
* */
//注入 XxlJobSpringExecutor 到 Spring
@Bean
public XxlJobSpringExecutor xxlJobExecutor() {
XxlJobSpringExecutor executor = new XxlJobSpringExecutor();
executor.setAdminAddresses(adminAddresses);
executor.setAppname(appname);
executor.setAddress(address);
executor.setIp(ip);
executor.setPort(port);
executor.setAccessToken(accessToken);
executor.setLogPath(logPath);
executor.setLogRetentionDays(logRetentionDays);
return executor;
}
}
編寫定時任務
@XxlJob("demoJobHandler")
public void demoJobHandler() throws Exception {
/**
* 業務省略.....
* */
}
這樣的話每個項目需要用 xxl-job 都得寫一遍這些代碼,這顯然違背了 DRY (Don’t Repeat Yourself) 原則。是以我們可以把這些代碼寫在制作的 starter 中,以後如果有項目需要用 xxl-job ,直接引入 starter ,在配置檔案中配置相關屬性即可,這不就是引入了一個 Maven 依賴嗎? starter 的好處就是讓我們少寫重複代碼!下面我們就開始動手制作 xxl-job-springboot-starter。
動手制作一個 xxl-job-springboot-starter
初始化 starter 項目結構
首先使用 Spring Initializer 建立一個項目,寫好 maven 的坐标和程式包名
之後删掉不需要的檔案,建立 META-INF/spring.factories 檔案,保留下圖的檔案目錄結構即可
然後在 pom.xml 中添加兩個依賴
<!--xxl-job 依賴-->
<dependency>
<groupId>com.xuxueli</groupId>
<artifactId>xxl-job-core</artifactId>
<version>2.3.0</version>
</dependency>
<!--引入這個依賴,等會你會知道它的作用-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
編寫自動配置代碼
首先寫一個 XxlJobProperties 類接受 application.yml 中的配置,使用 SpringBoot 提供的 @ConfigurationProperties 注解,指定 yml 檔案中的配置字首即可,無需再用 @Value 寫 EL 表達式指派
@ConfigurationProperties(prefix = XxlJobProperties.PREFIX)
@Data
public class XxlJobProperties {
public static final String PREFIX = "xxl-job";
/**
* 排程中心配置
*/
private Admin admin = new Admin();
/**
* 執行器配置
*/
private Executor executor = new Executor();
/**
* 執行器通訊TOKEN [選填]:非空時啟用
*/
private String accessToken;
//...省略
}
然後再寫一個 XxlJobAutoConfiguration 類讀取配置,注入 XxlJobSpringExecutor 對象到 Spring 容器。
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(IJobHandler.class)
@EnableConfigurationProperties(XxlJobProperties.class)
public class XxlJobAutoConfiguration {
/**
* 預留初始化和銷毀方法
* */
@Bean(initMethod = "start", destroyMethod = "destroy")
/**
* 當程式中沒有注入 XxlJobExecutor 時才會将我們這個注入到 Spring
* */
@ConditionalOnMissingBean
public XxlJobExecutor xxlJobExecutor(XxlJobProperties xxlJobProperties,
ObjectProvider<XxlJobExecutorCustomizer> customizers) {
XxlJobExecutor xxlJobExecutor = new XxlJobSpringExecutor();
// 排程中心配置
xxlJobExecutor.setAdminAddresses(xxlJobProperties.getAdmin().getAddresses());
// 執行器配置
xxlJobExecutor.setAppname(xxlJobProperties.getExecutor().getAppName());
xxlJobExecutor.setIp(xxlJobProperties.getExecutor().getIp());
xxlJobExecutor.setPort(xxlJobProperties.getExecutor().getPort());
xxlJobExecutor.setAccessToken(xxlJobProperties.getAccessToken());
xxlJobExecutor.setLogPath(xxlJobProperties.getExecutor().getLogPath());
xxlJobExecutor.setLogRetentionDays(xxlJobProperties.getExecutor().getLogRetentionDays());
// 預留的 customizer 配置
customizers.orderedStream().forEach(customizer -> customizer.customize(xxlJobExecutor));
return xxlJobExecutor;
}
}
參考官方的 starter 預留一個開發者擴充配置
/**
* 預留一個自定義配置的接口
*/
@FunctionalInterface
public interface XxlJobExecutorCustomizer {
void customize(final XxlJobExecutor xxlJobExecutor);
}
這裡有幾個注解解釋下:
- @ConfigurationProperties —— 讀取 yml 檔案中的配置設定到被此注解标注的類屬性
- @EnableConfigurationProperties —— 讓 Spring 掃描被 @ConfigurationProperties 标注的類
- @ConditionalOnClass —— 隻有 IJobHandler 類存在時這個配置類才有效
- @ConditionalOnMissingBean —— 隻有 Spring 中不存在 XxlJobExecutor 類型的 Bean 才會注入,也就是說如果在程式中開發者已經自己建構了 XxlJobExecutor 類型的 Bean ,那麼我們這個 starter 中的将不會被注入到 Spring 容器
讓 SpringBoot 能掃描到我們的 starter 注入 Bean
在 spring.factories 檔案中寫入以下配置,這是 SpringBoot 官方約定的寫法
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.syc.xxljob.autoconfigure.XxlJobAutoConfiguration
最終項目結構
使用我們制作的 stater
将我們上一步的項目用 Maven install 到本地倉庫,然後在一個其他 SpringBoot 項目中引入此依賴坐标
<dependency>
<groupId>com.syc</groupId>
<artifactId>xxl-job-springboot-starter</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
在 yml 檔案中配置相關屬性值将會自動給出之前的注釋提示,這個提示功能是之前 spring-boot-configuration-processor 的依賴提供的,是不是很香~~
配置寫好之後啟動 SpringBoot 項目,啟動過程中會自動去把我們 starter 中的 XxlJobSpringExecutor 注入到 Spring 。
與 RedisTemplate 的差別
值得注意的是,對于 xxl-job 我們自動配置注入了 XxlJobSpringExecutor 對象到 Spring。 但是沒有像 RedisTemplate 或者 RabbitTemplatge 顯示去用,因為兩者用法不一樣,XxlJobSpringExecutor 是必須要注入到 Spring 容器隐式給 @XxlJob 提供作用的,RedisTemplate 相當于是直接提供了一個工具模闆類。
源碼位址
Github 完整源碼位址 xxl-job-springboot-starter