自定义SpringBootStarter

前言

作为一个有架构梦想的程序员,自定义 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 的坐标和程序包名

image.png

之后删掉不需要的文件,创建 META-INF/spring.factories 文件,保留下图的文件目录结构即可

image.png

然后在 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
复制代码

最终项目结构

image.png

使用我们制作的 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 的依赖提供的,是不是很香~~

image.png

配置写好之后启动 SpringBoot 项目,启动过程中会自动去把我们 starter 中的 XxlJobSpringExecutor 注入到 Spring 。

与 RedisTemplate 的区别

值得注意的是,对于 xxl-job 我们自动配置注入了 XxlJobSpringExecutor 对象到 Spring。 但是没有像 RedisTemplate 或者 RabbitTemplatge 显示去用,因为两者用法不一样,XxlJobSpringExecutor 是必须要注入到 Spring 容器隐式给 @XxlJob 提供作用的,RedisTemplate 相当于是直接提供了一个工具模板类。

源码地址

Github 完整源码地址 xxl-job-springboot-starter

结语

如果这篇文章对你有帮助,记得点赞加关注。你的支持就是我继续创作的动力!