如何从零开始写一个xxx-spring-boot-stop
发布时间:2025-05-19 00:09:10
作者:益华网络
来源:undefined
浏览量(1)
点赞(1)
摘要:来源:WU双 不好意思,是xxx-spring-boot-starter。Spring Boot与Spring最大的区别就是自动配置了,那你知道Spring Boot是如何完成自动配置的吗?我们又如何编写自己的xxx-spring-boot-starter,进而完成自定义的自动配置。又要如何测试自定义的starter在不
来源:WU双
不好意思,是xxx-spring-boot-starter。
Spring Boot与Spring最大的区别就是自动配置了,那你知道Spring Boot是如何完成自动配置的吗?我们又如何编写自己的xxx-spring-boot-starter,进而完成自定义的自动配置。又要如何测试自定义的starter在不同集成环境下的表现呢?今天的文章给大家一一道来。1
从auto configuration说起Spring Boot和Spring最大的区别在哪,答案是自动配置。Spring的概念很大,这里的Spring只是指 Spring Framework。实际现在的开发应该没有直接用Spring的了,大都是通过Spring Boot来间接使用Spring。包括官方也是这么建议的。If you are just getting started with Spring, you may want to begin using the Spring Framework by creating a Spring Boot-based application. Spring Boot provides a quick (and opinionated) way to create a production-ready Spring-based application. 上面这段话的意思是建议你直接通过Spring Boot来开始。我们下面将一步步地来讲解Spring Boot是如何做到自动配置的。熟悉Spring的小伙伴应该都熟悉一个概念,叫做Beans。Beans是什么呢,在Spring中,你代码中的所有被Spring IoC容器所管理的实例,就叫做Beans。Spring IoC容器,是Spring框架最核心的概念。

2
常见的spring-boot-starter-xxx我们以spring-boot-starter-data-redis为例,看下该starter是如何与spring-boot-autoconfigure模块来配合,进而自动注入Redis相关Bean的。我们打开spring-boot-starter-data-redis jar包,会发现jar包里什么代码都没有?
1. The autoconfigure module that contains the auto-configuration code for "acme". 2. The starter module that provides a dependency to the autoconfigure module as well as "acme" and any additional dependencies that are typically useful.
上面文档说明,完整的starter一般是由两部分组成,一个是autoconfigure模块,另一个是starter模块,autoconfigure模块包含自动配置的代码,starter模块只包括依赖引用。对应这里的示例,autoconfigure模块对应的是spring-boot-autoconfigure,starter模块对应的是spring-boot-starter-data-redis。那就让我们看看spring-boot-starter-data-redis包括了哪些引用,spring-boot-starter-data-redis的pom文件依赖如下:<?xml version="1.0" encoding="UTF-8"?><project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> <!-- This module was also published with a richer model, Gradle metadata, --> <!-- which should be used instead. Do not delete the following line which --> <!-- is to indicate to Gradle or any Gradle module metadata file consumer --> <!-- that they should prefer consuming it instead. --> <!-- do_not_remove: published-with-gradle-metadata --> <modelVersion>4.0.0</modelVersion> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> <version>2.4.2</version> <name>spring-boot-starter-data-redis</name> <description>Starter for using Redis key-value data store with Spring Data Redis and the Lettuce client</description> <url>https://spring.io/projects/spring-boot</url> <organization> <name>Pivotal Software, Inc.</name> <url>https://spring.io</url> </organization> <licenses> <license> <name>Apache License, Version 2.0</name> <url>https://www.apache.org/licenses/LICENSE-2.0</url> </license> </licenses> <developers> <developer> <name>Pivotal</name> <email>info@pivotal.io</email> <organization>Pivotal Software, Inc.</organization> <organizationUrl>https://www.spring.io</organizationUrl> </developer> </developers> <scm> <connection>scm:git:git://github.com/spring-projects/spring-boot.git</connection> <developerConnection>scm:git:ssh://git@github.com/spring-projects/spring-boot.git</developerConnection> <url>https://github.com/spring-projects/spring-boot</url> </scm> <issueManagement> <system>GitHub</system> <url>https://github.com/spring-projects/spring-boot/issues</url> </issueManagement> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter</artifactId> <version>2.4.2</version> <scope>compile</scope> </dependency> <dependency> <groupId>org.springframework.data</groupId> <artifactId>spring-data-redis</artifactId> <version>2.4.3</version> <scope>compile</scope> </dependency> <dependency> <groupId>io.lettuce</groupId> <artifactId>lettuce-core</artifactId> <version>6.0.2.RELEASE</version> <scope>compile</scope> </dependency> </dependencies></project>可以看到spring-boot-starter-data-redis模块主要是引入了Spring Redis及Lettuce相关的依赖,那么这些依赖到底是如何与spring-boot-autoconfigure配合从而完成自动注入的?
3
写一个自己的
xxx-spring-boot-starter既然Spring Boot的starter这么好用,那么我们如何写个自己的starter呢?当然我们在写自己的starter之前,应该了解starter的目标及使用场景,在我看来starter的主要使用场景就是方便项目开发及集成,减少冗余代码。官方建议starter需要包含configure模块及starter模块,一个模块负责自动配置的代码,另一个模块只负责依赖的引入,但如果我们的starter需求简单的话,也无需分成两个模块,写在一个模块也就可以了。3.1 关于starter的命名Spring官方的starter命名规范为spring-boot-starter-xxx,非官方的starter命名规范为xxx-spring-boot-starter。像jasypt-spring-boot-starter、knife4j-spring-boot-starter等都是常见的第三方starter。所以我们如果要自己写starter一定要遵循相应的规范。在这节的示例中,我将会写一个定时任务的starter,命名为scheduler-spring-boot-starter,该starter在其他模块引入后将会自动开启运行特定的定时任务,同时支持任务名称和运行间隔时间的自定义配置。3.2 scheduler-spring-boot-starter关键代码直接看代码,关键代码如下,完整项目代码获取方式在文末。SchedulerProperties配置类:@ConfigurationProperties(prefix = "scheduler")public class SchedulerProperties { /** * 定时任务调度时间,单位ms,默认值1000ms */ private long period = 1000L; /** * 定时任务名称 */ private String taskName; public long getPeriod() { return period; } public void setPeriod(long period) { this.period = period; } public String getTaskName() { return taskName; } public void setTaskName(String taskName) { this.taskName = taskName; }}SchedulerAutoConfiguration自动配置类:@Configuration@EnableConfigurationProperties(SchedulerProperties.class)@Import(SchedulerExecutorConfiguration.class)public class SchedulerAutoConfiguration { @Bean public Scheduler schedulerTask(ScheduledThreadPoolExecutor scheduledThreadPoolExecutor, SchedulerProperties schedulerProperties) { return new Scheduler(scheduledThreadPoolExecutor, schedulerProperties); }}SchedulerExecutorConfiguration配置类:@Configurationpublic class SchedulerExecutorConfiguration { @Bean public ScheduledThreadPoolExecutor scheduledThreadPoolExecutor() { return new ScheduledThreadPoolExecutor(1); }}Scheduler类:public class Scheduler { public static final Logger LOGGER = LoggerFactory.getLogger(Scheduler.class); private final ScheduledThreadPoolExecutor scheduledThreadPoolExecutor; private final SchedulerProperties schedulerProperties; public Scheduler(ScheduledThreadPoolExecutor scheduledThreadPoolExecutor, SchedulerProperties schedulerProperties) { this.scheduledThreadPoolExecutor = scheduledThreadPoolExecutor; this.schedulerProperties = schedulerProperties; this.init(); } public void init() { scheduledThreadPoolExecutor.scheduleAtFixedRate(() -> { LOGGER.info("scheduler task [{}], period [{}ms], currentTime [{}]", schedulerProperties.getTaskName(), schedulerProperties.getPeriod(), LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"))); }, 0, schedulerProperties.getPeriod(), TimeUnit.MILLISECONDS); }}spring.factories配置文件:org.springframework.boot.autoconfigure.EnableAutoConfiguration=\com.example.scheduler.SchedulerAutoConfigurationscheduler-spring-boot-starter模块编写完成并且打包上传到仓库后,其他项目只需引入相应的依赖,就能实现自动配置运行定时任务处理的功能。依赖示例如下:<dependency> <groupId>com.example</groupId> <artifactId>scheduler-spring-boot-starter</artifactId> <version>${scheduler-spring-boot-starter-version}</version></dependency>依赖后,项目无需任何处理,将自动运行定时任务:2023-10-11 14:22:20.683 INFO 25680 --- [pool-1-thread-1] com.example.scheduler.Scheduler : scheduler task [test-example], period [5000ms], currentTime [2023-10-11 14:22:20]2023-10-11 14:22:25.689 INFO 25680 --- [pool-1-thread-1] com.example.scheduler.Scheduler : scheduler task [test-example], period [5000ms], currentTime [2023-10-11 14:22:25]2023-10-11 14:22:30.685 INFO 25680 --- [pool-1-thread-1] com.example.scheduler.Scheduler : scheduler task [test-example], period [5000ms], currentTime [2023-10-11 14:22:30]2023-10-11 14:22:35.681 INFO 25680 --- [pool-1-thread-1] com.example.scheduler.Scheduler : scheduler task [test-example], period [5000ms], currentTime [2023-10-11 14:22:35]并且还可以通过配置文件来灵活配置任务名和定时任务调度时间:
4
测试你的spring-boot-starter
除了真正的将starter引入到项目中进行集成测试外,starter模块可以自测吗?毕竟外部项目非常的复杂,starter在不同的项目集成环境中可能表现的不一样,那么我们怎么进行starter自测,来看starter在不同环境下的表现呢?我们可以通过模拟不同的运行环境,来测试starter相应的表现,实际就是测试相应的自动配置有没有生效。还是直接看代码:class SchedulerAutoConfigurationTest { // 通过contextRunner来模拟运行环境,这里是模拟配置了SchedulerAutoConfiguration类的应用环境,实际也就是引用了scheduler-spring-boot-starter后生效的配置 private final ApplicationContextRunner contextRunner = new ApplicationContextRunner() .withConfiguration(AutoConfigurations.of(SchedulerAutoConfiguration.class)); @Test void testAutoConfiguration() { this.contextRunner.run((context) -> { // 测试自动配置有没有注入ScheduledThreadPoolExecutor Bean assertThat(context).hasSingleBean(ScheduledThreadPoolExecutor.class); // 测试自动配置有没有注入SchedulerProperties Bean assertThat(context).hasSingleBean(SchedulerProperties.class); // 测试自动配置有没有注入Scheduler Bean assertThat(context).hasSingleBean(Scheduler.class); }); } @Test void testProperties() { // 模拟环境配置了相应的参数 this.contextRunner.withPropertyValues("scheduler.period=5000", "scheduler.task-name=test-example") .run((context) -> { // 测试对应参数设置是否生效 assertThat(context.getBean(SchedulerProperties.class).getPeriod()).isEqualTo(5000); assertThat(context.getBean(SchedulerProperties.class).getTaskName()).isEqualTo("test-example"); }); }}可以看到,通过模拟不同的集成环境,我们可以测试自定义starter在不同环境中的自动配置情况,非常的简单高效。扫一扫,关注我们
声明:本文由【益华网络】编辑上传发布,转载此文章须经作者同意,并请附上出处【益华网络】及本页链接。如内容、图片有任何版权问题,请联系我们进行处理。
1