一个好用的springboot starter是如何炼成的?

本文以调度中间件PowerJob为例,介绍为什么要自定义一个starter?starter是做什么用的?如何去定义一个starter?带着这些疑问,会对starter理解的更深刻。

1. 背景

最近在研究一个调度中间件PowerJob:新一代分布式任务调度与计算框架。

在研究其框架原理的同时,我发现在SpringBoot应用中使用PowerJob,需要手动构造他的配置类:


作为新一代调度中间件,怎么能没有一个好用的 Spring Boot starter (以下简称starter)呢?所以,决定为PowerJob增加一个starter,让用户能更方便的使用PowerJob。

2. starter的前世今生

那么starter是干嘛的呢?在没有starter之前,引入一个功能需要做:

  1. 依赖该功能的jar;
  2. 在xml或配置类里做一系列的配置;
  3. 调试代码+Google,直到功能正常。

上述步骤需要在每个项目中做配置,麻烦的很,对新人也不友好。

starter的主要目的就是为了解决上述的问题。starter的理念就是:脏活累活都交给我,什么依赖啊、配置啊,starter都给你封装好了,对外暴露的是一个能力,你直接引入starter的依赖就行了。比如引入spring-boot-starter-web依赖,项目就是一个web服务器,非常方便。

3. 自定义一个starter

创建一个starter,基本上包含以下几步:

3.1 引入依赖

一个starter模块需要依赖spring-boot-autoconfigure模块,也可以依赖spring-boot-starter模块。同时还需要依赖你的功能模块,这里是powerjob-worker模块(没有的话可以不依赖)。

<dependency>
  <groupId>com.github.kfcfans</groupId>
  <artifactId>powerjob-worker</artifactId>
  <version>${powerjob.worker.version}</version>
</dependency>

<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter</artifactId>
  <version>${springboot.version}</version>
  <scope>compile</scope>
</dependency>

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<String> 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即可。

<dependencies>
  <dependency>
    <groupId>com.github.kfcfans</groupId>
    <artifactId>powerjob-worker-spring-boot-starter</artifactId>
    <version>3.2.1</version>
  </dependency>
  
</dependencies>

4. 最后

本文的示例源码地址:
Github:https://github.com/KFCFans/PowerJob
Gitee:https://gitee.com/KFCFans/PowerJob


各位客官且慢,原创不易,点个赞再走呗。关注公众号 【读钓的YY】 可以白嫖😘,别下次一定了,就这次🤣

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,088评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,715评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,361评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,099评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 60,987评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,063评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,486评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,175评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,440评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,518评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,305评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,190评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,550评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,880评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,152评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,451评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,637评论 2 335