自定义 SpringBoot Starter
前言
作为一个有架构梦想的程序员,自定义 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
结语
如果这篇文章对你有帮助,记得点赞加关注。你的支持就是我继续创作的动力!
转载自:https://juejin.cn/post/6979845038347927583