Spring Boot 4 是 Spring 生态的新一代版本,它内置了 Spring Framework 7.0,带来了多个实用的新特性,大幅提升了开发体验和系统的稳定性、可维护性。
一、API 版本控制原生支持
Spring Framework 7.0 为 Spring Boot 4 带来了 API 版本控制的原生支持,这是现代 Web 开发中非常实用的功能,能够帮助开发者在不破坏现有客户端的情况下管理 API 的演进。
1. 基础使用:RequestMapping version 属性
在@RequestMapping及其变体注解中新增了version属性,同时可以通过配置来设置版本的解析策略。
@Configuration
public class WebConfiguration implements WebMvcConfigurer {
@Override
public void configureApiVersioning(ApiVersionConfigurer configurer) {
// 方式 1:使用请求参数(默认参数名为 "version")
configurer.useRequestParam("version");
// 方式 2:使用请求头
// configurer.useRequestHeader("API-Version");
// 方式 3:使用路径变量
// configurer.usePathVariable("version");
}
}
2. 常见版本控制策略
请求参数方式:
?version=1请求头方式:
API-Version: 1路径变量方式:
/api/v1/usersAccept 头方式:
Accept: application/vnd.api+json;version=1
3. 调用测试
使用请求参数进行版本控制的测试示例:
# 调用版本 1 的 API
curl "http://localhost:8080/?version=1"
# 输出:API Version 1.0.0
# 调用版本 2 的 API
curl "http://localhost:8080/?version=2"
# 输出:API Version 2.0.0
# 不指定版本(可能返回默认版本或错误)
curl "http://localhost:8080/"
4. 高级配置:自定义版本解析器
对于复杂的业务场景,可以实现自定义的版本解析逻辑:
@Configuration
public class WebConfiguration implements WebMvcConfigurer {
@Override
public void configureApiVersioning(ApiVersionConfigurer configurer) {
configurer.useVersionResolver(new ApiVersionResolver() {
@Override
public @Nullable String resolveVersion(HttpServletRequest request) {
// 示例 1:从用户代理字符串解析版本
String userAgent = request.getHeader("User-Agent");
if (userAgent != null && userAgent.contains("mobile")) {
return "mobile";
}
// 示例 2:基于客户端 IP 或其他业务规则
String clientIp = getClientIp(request);
if (isTestEnvironment(clientIp)) {
return "beta";
}
// 默认版本
return "1";
}
});
}
}
二、空安全特性:消灭空指针异常
Spring Boot 4 引入了 JSpecify 注解规范,替代了老旧的 JSR-305 注解体系,将空安全检查从运行时提升到编译期,从根源上减少空指针异常的出现。
1. JSpecify 核心理念
JSpecify 的核心是让类型系统携带空值信息,在编译期进行验证,通过@NullMarked和@Nullable注解的组合,配合静态分析工具(如 NullAway),可以实现:
编译期捕获潜在的 NPE
显式化空值契约,方法签名明确告知调用者哪些值可能为 null
减少防御性代码,无需过度的空值检查
提升代码可维护性,团队成员可以通过注解理解 API 的空值语义
2. 默认非空规则
Spring Boot 4 引入了 “默认非空” 的规则,在包或类上使用@NullMarked注解后,除非用@Nullable明确标注,否则所有类型都是非空的。
// Spring Boot 4 之前 - 返回值是否可空?无从知晓!
@Service
public class PigUserService {
public PigUser findUserByUsername(String username) {
return pigUserRepository.findByUsername(username); // 可能返回 null
}
}
// Spring Boot 4 使用 JSpecify - 显式标注可空性
@Service
@NullMarked// 默认所有类型为非空
public class PigUserService {
@Nullable
public PigUser findUserByUsername(String username) {
return pigUserRepository.findByUsername(username); // 明确表示可能返回 null
}
}
3. 集合类型的空安全处理
JSpecify 还可以处理集合中的可空元素,清晰表达集合的空值语义:
@Service
public class PigReviewService {
// 列表本身非空,但可以包含空元素
public List<@Nullable String> getProductReviews() {
List<@Nullable String> reviews = new ArrayList<>();
reviews.add("商品质量很好"); // 评价 1:已填写
reviews.add(null); // 评价 2:留空
reviews.add("lengleng 的服务态度非常棒"); // 评价 3:已填写
return reviews;
}
public int calculateReviewRate(List<@Nullable String> reviews) {
long completed = reviews.stream()
.filter(Objects::nonNull)
.count();
return (int) ((completed * 100) / reviews.size());
}
}
4. 编译期安全检查:NullAway 集成
集成 NullAway 后,可以在编译期就捕获空指针问题,将潜在的运行时错误转化为构建失败:
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.14.0</version>
<configuration>
<release>17</release>
<encoding>UTF-8</encoding>
<fork>true</fork>
<compilerArgs>
<arg>-XDcompilePolicy=simple</arg>
<arg>--should-stop=ifError=FLOW</arg>
<arg>-Xplugin:ErrorProne -Xep:NullAway:ERROR -XepOpt:NullAway:OnlyNullMarked</arg>
<arg>-J--add-exports=jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED</arg>
<arg>-J--add-exports=jdk.compiler/com.sun.tools.javac.file=ALL-UNNAMED</arg>
<arg>-J--add-exports=jdk.compiler/com.sun.tools.javac.main=ALL-UNNAMED</arg>
<arg>-J--add-exports=jdk.compiler/com.sun.tools.javac.model=ALL-UNNAMED</arg>
<arg>-J--add-exports=jdk.compiler/com.sun.tools.javac.parser=ALL-UNNAMED</arg>
<arg>-J--add-exports=jdk.compiler/com.sun.tools.javac.processing=ALL-UNNAMED</arg>
<arg>-J--add-exports=jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED</arg>
<arg>-J--add-exports=jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED</arg>
<arg>-J--add-opens=jdk.compiler/com.sun.tools.javac.code=ALL-UNNAMED</arg>
<arg>-J--add-opens=jdk.compiler/com.sun.tools.javac.comp=ALL-UNNAMED</arg>
</compilerArgs>
<annotationProcessorPaths>
<path>
<groupId>com.google.errorprone</groupId>
<artifactId>error_prone_core</artifactId>
<version>2.38.0</version>
</path>
<path>
<groupId>com.uber.nullaway</groupId>
<artifactId>nullaway</artifactId>
<version>0.12.7</version>
</path>
</annotationProcessorPaths>
</configuration>
</plugin>
</plugins>
</build>
5. @Nullable 与 Optional 的对比
相比于 Optional,@Nullable注解有以下优势:
API 兼容性:为现有方法添加
@Nullable不会破坏现有调用者,而改为返回 Optional 会破坏兼容性运行时开销:
@Nullable没有运行时成本,而 Optional 会产生额外的对象分配开销使用场景更灵活:Optional 主要设计为返回类型,
@Nullable可以用于参数、字段等场景代码更简洁:简单的空值检查场景下,
@Nullable的代码更简洁
三、BeanRegistrar:简化动态 Bean 注册
Spring Boot 4 引入了新的BeanRegistrar接口,替代了过去繁琐的ImportBeanDefinitionRegistrar,大幅简化了动态 Bean 注册的代码。
1. BeanRegistrar 接口定义
BeanRegistrar的接口非常简洁,只包含一个方法:
public interface BeanRegistrar {
void register(BeanRegistry registry, Environment environment);
}
其中BeanRegistry用于注册 Bean,Environment可以直接获取配置信息,无需额外实现接口。
2. 新旧方式对比
基础注册
新方式(BeanRegistrar):
registry.registerBean("pigUserService", PigUserService.class);
旧方式(ImportBeanDefinitionRegistrar):
BeanDefinitionBuilder builder = BeanDefinitionBuilder
.genericBeanDefinition("com.pig4cloud.pigx.admin.service.impl.PigUserService");
registry.registerBeanDefinition("pigUserService", builder.getBeanDefinition());
带配置的注册
新方式:
registry.registerBean("pigOrderService", PigOrderService.class, spec -> spec
.prototype() // 原型作用域,每次获取都是新实例
.lazyInit() // 延迟初始化,用到才创建
.description("PIG 订单服务") // 自定义描述
);
旧方式:
BeanDefinitionBuilder builder = BeanDefinitionBuilder
.genericBeanDefinition("com.pig4cloud.pigx.mall.service.impl.PigOrderService");
builder.setScope("prototype"); // 要记住是"prototype"字符串
builder.setLazyInit(true); // setter 方式
builder.setDescription("PIG 订单服务"); // 又是 setter
registry.registerBeanDefinition("pigOrderService", builder.getBeanDefinition());
自定义创建逻辑(带依赖注入)
新方式:
registry.registerBean("pigGoodsService", PigGoodsService.class, spec -> spec
.supplier(context -> {
// context 可以获取其他已注册的 Bean
PigUserService userService = context.bean(PigUserService.class);
return new PigGoodsService(userService);
})
);
旧方式:
BeanDefinitionBuilder builder = BeanDefinitionBuilder
.genericBeanDefinition("com.pig4cloud.pigx.mall.service.impl.PigGoodsService");
// 需要手动设置构造函数参数或属性引用
builder.addConstructorArgReference("pigUserService"); // 还得知道 Bean 的名字
// 或者用更复杂的方式
builder.setFactoryMethod("createInstance");
builder.addPropertyReference("userService", "pigUserService");
registry.registerBeanDefinition("pigGoodsService", builder.getBeanDefinition());
条件注册
新方式:
// Environment 直接就在参数里
if (environment.matchesProfiles("production")) {
registry.registerBean(PigCacheService.class);
}
// 支持任意复杂的逻辑
if (environment.getProperty("pig.cache.enabled", Boolean.class, false)) {
String cacheType = environment.getProperty("pig.cache.type", "redis");
if ("redis".equals(cacheType)) {
registry.registerBean(PigRedisCacheService.class);
} else {
registry.registerBean(PigLocalCacheService.class);
}
}
旧方式:
// 先得实现 EnvironmentAware 接口
private Environment environment;
@Override
public void setEnvironment(Environment environment) {
this.environment = environment;
}
// 然后在 registerBeanDefinitions 方法里
@Override
public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
if (environment.matchesProfiles("production")) {
BeanDefinitionBuilder builder = BeanDefinitionBuilder
.genericBeanDefinition("com.pig4cloud.pigx.common.cache.PigCacheService");
registry.registerBeanDefinition("pigCacheService", builder.getBeanDefinition());
}
}
3. 实战案例:动态消息服务注册
在多租户 SaaS 项目中,需要根据租户的配置动态注册消息服务,使用 BeanRegistrar 可以大幅简化代码:
/**
* 消息服务注册器 - 新方式
*
* @author lengleng
*/
public class PigMessageServiceRegistrar implements BeanRegistrar {
@Override
public void register(BeanRegistry registry, Environment environment) {
String messageType = environment.getProperty("pig.message.type", "email");
switch (messageType.toLowerCase()) {
case "email" -> registry.registerBean(
"pigMessageService",
PigEmailMessageService.class,
spec -> spec.description("PIG 邮件消息服务")
);
case "sms" -> registry.registerBean(
"pigMessageService",
PigSmsMessageService.class,
spec -> spec.description("PIG 短信消息服务")
);
default -> throw new IllegalArgumentException("未知的消息类型:" + messageType);
}
}
}
只需要 27 行代码就可以完成过去 54 行代码的功能,并且代码的可读性和可维护性大幅提升。
四、总结
Spring Boot 4 的这些新特性从不同维度提升了开发体验和系统质量:API 版本控制让 API 演进更优雅,空安全特性从根源减少空指针异常,BeanRegistrar 简化了动态 Bean 注册的代码。这些特性都是 Spring 框架对现代开发场景的适配,能够帮助开发者更高效地构建稳定、可维护的应用。
评论区