一个好用的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模块(没有的话可以不依赖)。


  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】 可以白嫖,别下次一定了,就这次

你可能感兴趣的:(一个好用的springboot starter是如何炼成的?)