本文以调度中间件PowerJob为例,介绍为什么要自定义一个starter?starter是做什么用的?如何去定义一个starter?带着这些疑问,会对starter理解的更深刻。
1. 背景
最近在研究一个调度中间件PowerJob:新一代分布式任务调度与计算框架。
在研究其框架原理的同时,我发现在SpringBoot应用中使用PowerJob,需要手动构造他的配置类:
作为新一代调度中间件,怎么能没有一个好用的 Spring Boot starter (以下简称starter)呢?所以,决定为PowerJob增加一个starter,让用户能更方便的使用PowerJob。
2. starter的前世今生
那么starter是干嘛的呢?在没有starter之前,引入一个功能需要做:
- 依赖该功能的jar;
- 在xml或配置类里做一系列的配置;
- 调试代码+Google,直到功能正常。
上述步骤需要在每个项目中做配置,麻烦的很,对新人也不友好。
starter的主要目的就是为了解决上述的问题。starter的理念就是:脏活累活都交给我,什么依赖啊、配置啊,starter都给你封装好了,对外暴露的是一个能力,你直接引入starter的依赖就行了。比如引入spring-boot-starter-web
依赖,项目就是一个web服务器,非常方便。
3. 自定义一个starter
创建一个starter,基本上包含以下几步:
3.1 引入依赖
一个starter模块需要依赖spring-boot-autoconfigure
模块,也可以依赖spring-boot-starter
模块。同时还需要依赖你的功能模块,这里是powerjob-worker
模块(没有的话可以不依赖)。
com.github.kfcfans
powerjob-worker
${powerjob.worker.version}
org.springframework.boot
spring-boot-starter
${springboot.version}
compile
3.2 创建配置项
如果starter需要提供配置项,供用户填写(比如server.port等),需要定义一个唯一的命名空间powerjob
/**
* PowerJob 配置项
*
* @author songyinyin
* @since 2020/7/26 16:37
*/
@Data
@ConfigurationProperties(prefix = "powerjob")
public class PowerJobProperties {
/**
* 应用名称,需要提前在控制台注册,否则启动报错
*/
private String appName;
/**
* 启动 akka 端口
*/
private int akkaPort = RemoteConstant.DEFAULT_WORKER_PORT;
/**
* 调度服务器地址,ip:port 或 域名,多个用英文逗号分隔
*/
private String serverAddress;
/**
* 本地持久化方式,默认使用磁盘
*/
private StoreStrategy storeStrategy = StoreStrategy.DISK;
/**
* 最大返回值长度,超过会被截断
* {@link ProcessResult}#msg 的最大长度
*/
private int maxResultLength = 8096;
/**
* 启动测试模式,true情况下,不再尝试连接 server 并验证appName。
* true -> 用于本地写单元测试调试; false -> 默认值,标准模式
*/
private boolean enableTestMode = false;
}
@ConfigurationProperties
表示自动获取配置文件中前缀为powerjob的属性。
3.3 编写自动装配类
装配的配置类中,编写需要纳入spring bean管理的对象。
/**
* PowerJob 自动装配
*
* @author songyinyin
* @since 2020/7/26 16:37
*/
@Configuration
@EnableConfigurationProperties(PowerJobProperties.class)
public class PowerJobAutoConfiguration {
@Bean
@ConditionalOnMissingBean
public OhMyWorker initPowerJob(PowerJobProperties properties) {
// 服务器HTTP地址(端口号为 server.port,而不是 ActorSystem port),请勿添加任何前缀(http://)
CommonUtils.requireNonNull(properties.getServerAddress(), "serverAddress can't be empty!");
List serverAddress = Arrays.asList(properties.getServerAddress().split(","));
// 1. 创建配置文件
OhMyConfig config = new OhMyConfig();
// 可以不显式设置,默认值 27777
config.setPort(properties.getAkkaPort());
// appName,需要提前在控制台注册,否则启动报错
config.setAppName(properties.getAppName());
config.setServerAddress(serverAddress);
// 如果没有大型 Map/MapReduce 的需求,建议使用内存来加速计算
// 有大型 Map/MapReduce 需求,可能产生大量子任务(Task)的场景,请使用 DISK,否则妥妥的 OutOfMemory
config.setStoreStrategy(properties.getStoreStrategy());
// 启动测试模式,true情况下,不再尝试连接 server 并验证appName
config.setEnableTestMode(properties.isEnableTestMode());
// 2. 创建 Worker 对象,设置配置文件
OhMyWorker ohMyWorker = new OhMyWorker();
ohMyWorker.setConfig(config);
return ohMyWorker;
}
}
-
@Configuration
:标识本类是配置类,同时本类也将是一个spring bean; -
@EnableConfigurationProperties
:使 @ConfigurationProperties 注解的类生效,并PowerJobProperties加入到spring容器中; -
@ConditionalOnMissingBean
:当spring容器中没有该类型的bean,才会创建该类OhMyWorker的对象,为用户自定义OhMyWorker对象提供了扩展。
最后,在resources/META-INF下创建spring.factories文件,指定需要自动装配的全类名
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.github.kfcfans.powerjob.worker.autoconfigure.PowerJobAutoConfiguration
3.4 增加配置项的IDEA智能提示
实际上,到上一步,一个starter基本完成了,但是为了更好地用户体验,我们需要在META-INF/spring-autoconfigure-metadata.properties
文件中添加自动配置的元信息,如果改文件存在,springboot将能更早的找出配置文件中的不匹配项,这将提升应用的启动时间。
该json文件可以使用spring-boot-autoconfigure-processor
生成。
{
"groups": [
{
"name": "powerjob",
"type": "com.github.kfcfans.powerjob.worker.autoconfigure.PowerJobProperties",
"sourceType": "com.github.kfcfans.powerjob.worker.autoconfigure.PowerJobProperties"
}
],
"properties": [
{
"name": "powerjob.app-name",
"type": "java.lang.String",
"description": "应用名称,需要提前在控制台注册,否则启动报错",
"sourceType": "com.github.kfcfans.powerjob.worker.autoconfigure.PowerJobProperties"
},
{
"name": "powerjob.max-result-length",
"type": "java.lang.Integer",
"description": "最大返回值长度,超过会被截断 {@link ProcessResult}#msg 的最大长度",
"sourceType": "com.github.kfcfans.powerjob.worker.autoconfigure.PowerJobProperties",
"defaultValue": 8096
},
{
"name": "powerjob.akka-port",
"type": "java.lang.Integer",
"description": "启动 akka 端口",
"sourceType": "com.github.kfcfans.powerjob.worker.autoconfigure.PowerJobProperties"
},
{
"name": "powerjob.server-address",
"type": "java.lang.String",
"description": "调度服务器地址,ip:port 或 域名,多值用英文逗号分隔",
"sourceType": "com.github.kfcfans.powerjob.worker.autoconfigure.PowerJobProperties"
},
{
"name": "powerjob.store-strategy",
"type": "com.github.kfcfans.powerjob.worker.common.constants.StoreStrategy",
"description": "本地持久化方式,默认使用磁盘",
"sourceType": "com.github.kfcfans.powerjob.worker.autoconfigure.PowerJobProperties"
},
{
"name": "powerjob.enable-test-mode",
"type": "java.lang.Boolean",
"description": "启动测试模式,true情况下,不再尝试连接 server 并验证appName。true -> 用于本地写单元测试调试; false -> 默认值,标准模式",
"sourceType": "com.github.kfcfans.powerjob.worker.autoconfigure.PowerJobProperties",
"defaultValue": false
}
],
"hints": []
}
3.5 项目中使用
到此一个starter就编写完成了,整体文件如下:
Spring建议所有非官方的starter,使用xx-spring-boot-starter的命名方式,因此PowerJob的starter命名为:
powerjob-worker-spring-boot-starter
。
其他项目中使用,仅需依赖powerjob-worker-spring-boot-starter
即可。
com.github.kfcfans
powerjob-worker-spring-boot-starter
3.2.1
4. 最后
本文的示例源码地址:
Github:https://github.com/KFCFans/PowerJob
Gitee:https://gitee.com/KFCFans/PowerJob
各位客官且慢,原创不易,点个赞再走呗。关注公众号 【读钓的YY】 可以白嫖,别下次一定了,就这次