SpringBoot 不同版本开发和使用 Starter 的方法
前言
SpringBoot 不同版本开发和使用 Starter 有区别,这里记录下开发的一些前置知识、实现步骤和一些遇到的问题。
SpringBoot 不同版本自动装配的原理及细微区别
开发 Starter 很重要的一个目的就是自动装配,而不同版本的 SpringBoot 在装配细节上有着区别。
SpringBoot 通过 @EnableAutoConfiguration
开启自动装配,通过 SpringFactoriesLoader
或 ImportCandidates
加载 META-INF 下配置文件(不同版本有区别)中的自动配置类实现自动装配,自动配置类其实就是通过 @Conditional
按需加载的配置类,想要其生效必须引入 spring-boot-starter-xxx
包(官方 starter)或者 xxx-spring-boot-starter
(第三方 starter) 实现起步依赖。
不同版本具体自动装配的方式有所区别,可通过阅读源码中 org.springframework.boot.autoconfigure.AutoConfigurationImportSelector#getCandidateConfigurations
找到区别:
2.6 最后一个版本 2.6.15 通过 SpringFactoriesLoader
加载 META-INF/spring.factories
完成自动装配:
1// 2.6.15
2protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
3 List<String> configurations = SpringFactoriesLoader.loadFactoryNames(this.getSpringFactoriesLoaderFactoryClass(), this.getBeanClassLoader());
4 Assert.notEmpty(configurations, "No auto configuration classes found in META-INF/spring.factories. If you are using a custom packaging, make sure that file is correct.");
5 return configurations;
6}
从 2.7.0 开始,额外通过 ImportCandidates
加载 META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
找到添加了 @AutoConfiguration
注解的类完成装配,这时还是保证了向下兼容:
1// 2.7.0
2protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
3 List<String> configurations = new ArrayList(SpringFactoriesLoader.loadFactoryNames(this.getSpringFactoriesLoaderFactoryClass(), this.getBeanClassLoader()));
4 ImportCandidates.load(AutoConfiguration.class, this.getBeanClassLoader()).forEach(configurations::add);
5 Assert.notEmpty(configurations, "No auto configuration classes found in META-INF/spring.factories nor in META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports. If you are using a custom packaging, make sure that file is correct.");
6 return configurations;
7}
从 3.0.0 大版本开始,不在使用 SpringFactoriesLoader
,需要特别注意:
1// 3.0.0
2protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
3 List<String> configurations = ImportCandidates.load(AutoConfiguration.class, this.getBeanClassLoader()).getCandidates();
4 Assert.notEmpty(configurations, "No auto configuration classes found in META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports. If you are using a custom packaging, make sure that file is correct.");
5 return configurations;
6}
以下用 A 指代 META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
,B 指代 spring.factories
,总结版本间的一些区别如下,开发和使用 Starter 时需要特别注意:
- 3.0.0 及以上版本自动装配类通过 A 配置,而 2.6.15 及以下版本通过 B 完成,2.7.0 以上到 3.0.0 之前 AB 均可;
- A 配置文件为每行一个 class 的格式,B 为
org.springframework.boot.autoconfigure.EnableAutoConfiguration=class1, class2, ...
的格式 - 3.0.0 及以上版本自动装配类需要添加
@AutoConfiguration
注解,而 2.6.15 及以下版本对应是添加@Configuration
,2.7.0 以上到 3.0.0 则根据使用情况而定即可
至于为什么将自动配置类文件进行更改?可以从 Github 上的这个 issue 找到答案:Move away from spring.factories for auto-configurations,阅读后可以总结一下,就是为了避免在配置文件 spring.factories
中手动添加类,通过 @AutoConfiguration
注解自动完成配置,而具体实现是要让编译器扫描添加了 @AutoConfiguration
的类并自动生成 META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
文件,但是这个功能还未实现,至少在 2024.2 最新版本 3.2.2 还未实现,可通过这个 issue 追踪进展。
不同版本 Starter 使用上需要注意的地方
了解前文对于原理的解释后,就可以总结出一些开发 starter 时需要注意的地方了。
如果项目使用了 SpringBoot 3.X,但是调用了一个第三方的通过 2.X 开发的 Starter,且是通过 spring.factories
加载类,那么将无法完成自动装配,需要手动配置相关 bean 完成,比如使用 rocketmq-spring-boot-starter-2.2.3 将无法自动装配 RocketMQTemplate
,通过如下代码解决即可:
1@Configuration
2public class RocketMQConfig {
3 @Value("${rocketmq.producer.group}")
4 private String producerGroup;
5 @Value("${rocketmq.name-server}")
6 private String namesrvAddr;
7
8 @Bean(name = "rocketMQTemplate")
9 public RocketMQTemplate rocketMQTemplate() {
10 RocketMQTemplate template = new RocketMQTemplate();
11 DefaultMQProducer producer = new DefaultMQProducer();
12 producer.setProducerGroup(producerGroup);
13 producer.setNamesrvAddr(namesrvAddr);
14 template.setProducer(producer);
15 return template;
16 }
17}
通过 @Bean
和 @Configuration
完成 RocketMQTemplate
的注入。
开发一个适应性更好的 Starter
为了让所有版本都能自动完成自动装配,我们可以同时适应两种自动装配方法。注意 @AutoConfiguration
也包含了 @Configuration
注解,所以可以很简单的完成适应,只是需要多准备一份配置文件。
下面使用 3.2.2 版本的 SpringBoot 开发一个模拟式的邮件服务 Starter:
新建项目 email-spring-boot
编辑 pom.xml
:
1<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
2 xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
3 <modelVersion>4.0.0</modelVersion>
4
5 <groupId>xdu.zh</groupId>
6 <artifactId>email-spring-boot</artifactId>
7 <version>0.0.1</version>
8 <packaging>pom</packaging>
9
10 <modules>
11 <module>email-spring-boot-autoconfigure</module>
12 <module>email-spring-boot-starter</module>
13 </modules>
14</project>
新建子模块 email-spring-boot-autoconfigure
编辑 pom.xml
:
1<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
2 xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
3 <modelVersion>4.0.0</modelVersion>
4 <parent>
5 <groupId>xdu.zh</groupId>
6 <artifactId>email-spring-boot</artifactId>
7 <version>0.0.1</version>
8 </parent>
9 <artifactId>email-spring-boot-autoconfigure</artifactId>
10 <version>0.0.1</version>
11 <packaging>jar</packaging>
12
13 <dependencyManagement>
14 <dependencies>
15 <dependency>
16 <groupId>org.springframework.boot</groupId>
17 <artifactId>spring-boot-dependencies</artifactId>
18 <version>3.2.2</version>
19 <type>pom</type>
20 <scope>import</scope>
21 </dependency>
22 </dependencies>
23 </dependencyManagement>
24
25 <dependencies>
26 <!-- Spring Boot自动配置依赖 -->
27 <dependency>
28 <groupId>org.springframework.boot</groupId>
29 <artifactId>spring-boot-autoconfigure</artifactId>
30 </dependency>
31 <!-- 元数据配置处理器 -->
32 <dependency>
33 <groupId>org.springframework.boot</groupId>
34 <artifactId>spring-boot-configuration-processor</artifactId>
35 <optional>true</optional>
36 </dependency>
37 </dependencies>
38</project>
通过 EmailProperties
定义一些自动配置项,指定配置前缀为 email.service
:
1@ConfigurationProperties("email.service")
2public class EmailProperties {
3 private boolean enable = true;
4 private String host;
5 private Integer port;
6 private String name;
7 private String password;
8
9 // getter and setter...
10}
具体邮件服务实现:
1public class EmailService {
2 private final EmailProperties emailProperties;
3
4 public EmailService(EmailProperties emailProperties) {
5 this.emailProperties = emailProperties;
6 }
7
8 public void send(String content) {
9 System.out.println("开始发送邮件:");
10 System.out.println(String.format("基本信息: host: %s, port: %s", emailProperties.getHost(), emailProperties.getPort()));
11 System.out.println("发送内容: " + content);
12 System.out.println("发送成功!");
13 }
14}
实现自动配置,当存在 EmailService.class
且 email.service
为 true
且容器中不存在 EmailService
这个 bean 时自动装配 EmailService
:
1/**
2 * @author zh
3 * @date 2024/2/24
4 */
5@AutoConfiguration
6@ConditionalOnClass(EmailService.class)
7@EnableConfigurationProperties(value = EmailProperties.class)
8public class EmailAutoConfiguration {
9 @Bean
10 @ConditionalOnMissingBean
11 @ConditionalOnProperty(prefix = "email.service", value = "enable", havingValue = "true")
12 public EmailService mailService(EmailProperties mailProperties) {
13 return new EmailService(mailProperties);
14 }
15}
编辑 META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
:
1xdu.zh.EmailAutoConfiguration
编辑 META-INF/spring.factories
:
1org.springframework.boot.autoconfigure.EnableAutoConfiguration=xdu.zh.EmailAutoConfiguration
新建子模块 email-spring-boot-starter
编辑 pom.xml
:
1<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
2 xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
3 <modelVersion>4.0.0</modelVersion>
4 <parent>
5 <groupId>xdu.zh</groupId>
6 <artifactId>email-spring-boot</artifactId>
7 <version>0.0.1</version>
8 </parent>
9 <artifactId>email-spring-boot-starter</artifactId>
10 <packaging>jar</packaging>
11
12 <dependencies>
13 <dependency>
14 <groupId>xdu.zh</groupId>
15 <artifactId>email-spring-boot-autoconfigure</artifactId>
16 <version>0.0.1</version>
17 </dependency>
18 </dependencies>
19</project>
该模块只完成封装,包含 email-spring-boot-autoconfigure
模块,以及其它一些可能的依赖项(可选),调用 starter 时只需引入此依赖。
进行多版本功能测试
为进行更实际的测试,在此之前将 email-spring-boot
进行发布,在 IDEA 内 maven 命令行面板通过 mvn install
命令安装到本地 maven 仓库。
接着新建测试项目,编辑 pom.xml
:
1<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
2 xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
3 <modelVersion>4.0.0</modelVersion>
4
5 <groupId>xdu.test</groupId>
6 <artifactId>test</artifactId>
7 <version>0.0.1</version>
8 <packaging>jar</packaging>
9
10 <parent>
11 <groupId>org.springframework.boot</groupId>
12 <artifactId>spring-boot-starter-parent</artifactId>
13<!-- <version>3.0.0</version>-->
14<!-- <version>2.7.0</version>-->
15 <version>2.6.15</version>
16 <relativePath/>
17 </parent>
18
19 <dependencies>
20 <dependency>
21 <groupId>xdu.zh</groupId>
22 <artifactId>email-spring-boot-starter</artifactId>
23 <version>0.0.1</version>
24 </dependency>
25 </dependencies>
26</project>
进行一些必要配置:
1# resources/application.properties
2email.service.enable=true
3email.service.host=mail.qq.com
4email.service.port=965
5email.service.name=admin
6email.service.password=admin
新建 Spring 应用,注入 EmailService
,执行 send
方法进行功能测试:
1@SpringBootApplication
2public class EmailStarterTestApplication implements CommandLineRunner {
3 @Autowired
4 private EmailService emailService;
5
6 public static void main(String[] args) {
7 SpringApplication.run(EmailStarterTestApplication.class, args);
8 }
9
10 @Override
11 public void run(String... args) throws Exception {
12 emailService.send("test");
13 }
14}
测试结果:
1...
2开始发送邮件:
3基本信息: host: mail.qq.com, port: 965
4发送内容: test
5发送成功!
再分别将 SpringBoot 版本改为 2.7.0、3.0.0,测试均通过。