还记得 2022 年底不?当时spring boot 3 和 spring framework 6 一出来,直接给整个 spring 生态来了个 “大换血”, 这可是自 spring 诞生以来动静最大的一次更新。不仅把 java 17 设为了最低要求,还把以前的 javax.* 换成了 jakarta.*,连 graalvm 原生镜像也开始初步支持了。
转眼到 2025 年,新一代版本马上要来了:就是 spring boot 4 和 spring framework 7。
这俩版本没跑偏,接着推进生态现代化。一方面用上了最新的 java 特性,跟 jakarta ee 11 的适配也更紧密;另一方面还帮开发者省了不少事,而且默认就支持打造 “抗造” 的应用,不用额外折腾太多。
接下来我就跟大家捋捋这俩版本的核心亮点,再配点说明和代码例子,让大家清楚升级后能用到啥好东西。
基线升级
在说具体功能之前,得先明确新版本对 “基础依赖” 的要求,这很关键。
现在行业里用 java 17 的还挺多,所以它依然是最低要求版本。不过官方特别建议用 java 21 或 25,因为能用上虚拟线程这类新的 jvm 特性。关于这点,spring 博客里有官方说明,大家想细看可以去那看。
spring framework 7 现在完全适配 jakarta ee 11 了,也就是说,它依赖的技术标准都升级了:servlet 6.1、jpa 3.2,还有 bean validation 3.1。
另外,kotlin 现在支持 2.2 及以上版本了。这么一来,写协程会更顺,处理响应式代码也感觉更 “自然”,不用绕弯子。
spring boot 4
作为第四个大版本,spring boot 这次加了不少实用改进,重点提一下:性能变快了、更容易 “监控” 应用了、维护起来更省心了,连配置支持都变强了。这些改动让它作为 “现代云原生 java 应用的基础框架”,地位更稳了。
原生镜像更给力了
spring boot 4 还在使劲推进 graalvm 原生镜像的支持,现在已经完全跟 graalvm 24 对齐了。而且 “提前编译(aot)” 的能力也优化了 —— 编译速度更快,启动时占的内存也更少了。
举个例子,spring data 新增了aot 仓库,简单说就是:通过 aot 处理,能把查询方法转成源代码,然后跟应用一起编译。这样一来,运行效率会更高。
监控应用更方便:micrometer 2 + opentelemetry
云原生应用想跑稳,“可观测性”(就是能看到应用的运行状态)特别重要。spring boot 3 之前就加了spring 可观测性,这次spring boot 4 直接升级到 micrometer 2,还集成了 opentelemetry 的启动器。这样一来,“trace日志”、“普通日志” 和 “性能指标(metrics)” 就能无缝配合,不用再自己凑一套了。
ssl 证书快过期?现在能清楚看到了
以前看 ssl 证书状态有点麻烦,现在改进了:如果证书链里有快过期的证书,会专门新增一个 expiringchains 条目来显示它们。而且之前的 “will_expire_soon” 状态没了,快过期的证书会统一标成 “valid”。
这样一来,团队在生产环境监控 ssl 证书时,能更清楚哪些要过期了,还不会出现没必要的 “误报”,省了不少排查时间。
代码拆得更细了:模块化改造
spring boot 4 刚启动开发时,第一个重要目标就是把自己的代码库拆成更 “模块化” 的结构。
以前 spring boot 3 里,很多核心模块(比如自动配置、启动器依赖、构建工具这些)都打包在大的 “构件” 里。虽然用着方便,但有时候依赖管理会很乱,类路径扫描也慢,连原生镜像的体积都会变大。
从 spring boot 4 开始,团队把 “自动配置” 和 “支持代码” 拆成了更小的模块,每个模块只负责一小块功能。这么改有啥好处?
- 构建和生成原生镜像更快:graalvm 的 aot 处理不用管那些没用的 “提示信息” 和 “元数据” 了。
- 依赖管理更清爽:像 micrometer、opentelemetry 这些可选的功能,不再跟核心代码打包在一起,而是单独放一个模块,想用就加,不想用也不占地方。
- spring 团队维护起来更轻松:模块和功能一一对应,找问题、改代码都方便,贡献代码的人也更容易上手。
对咱们开发者来说,平时写 pom.xml 或 build.gradle 可能感觉不到变化。只要用 “启动器依赖”(比如 spring-boot-starter-data-jpa),就不用改任何东西。比如要用到 jpa 和 hibernate,直接在 pom.xml 里加这段依赖就行:
<dependency>
<groupid>org.springframework.boot</groupid>
<artifactid>spring-boot-starter-data-jpa</artifactid>
</dependency>
真正的变化在 “底层”:jpa 的自动配置、hibernate 的集成、还有校验相关的配置,现在都在不同的小模块里了。这样框架在运行时或者 aot 编译时,能更精准地加载需要的配置,不浪费资源。
新增 @configurationpropertiessource 注解:跨模块配置更省心
为了让模块化更好用,这次还加了个新注解 @configurationpropertiessource。注意哦,它不会改变 “运行时配置属性怎么绑定”,主要是在 “构建阶段” 给 spring-boot-configuration-processor(配置处理器)提个醒。
平时配置处理器给 @configurationproperties 类生成 “元数据” 时,只会从这个类所在的模块里找信息。但在模块化项目里,有时候会用到其他模块的 “嵌套类型” 或者 “基类”,而这些模块的源代码在构建时可能拿不到 —— 这样生成的元数据就会不完整,比如少了属性描述或者默认值。
现在给类加个 @configurationpropertiessource 注解,就能告诉处理器:“就算这个类没标 @configurationproperties,也得给它生成完整的元数据”。简单说,以后跨模块开发时,再也不用操心元数据缺失的问题了,处理器会自动搞定。
spring framework 7
spring framework 7 这次既加了很多用户盼了好久的功能,还在测试、api 设计、核心基础功能上做了不少细节优化。一方面让框架更 “现代”,另一方面也帮咱们少写很多重复的模板代码。
测试功能更灵活了
spring 在测试时会用 “上下文缓存”—— 就是为了平衡 “测试速度” 和 “环境隔离”(避免不同测试互相影响)。关于这个缓存的细节、可能踩的坑,还有怎么解决,大家可以看这篇文章。
这次 spring framework 7 加了个 “测试上下文暂停” 的功能。以前跑长时间的集成测试,就算测试闲着没干活,也会占着资源;现在不一样了,spring 能把缓存里的 “上下文” 暂停,要用的时候再恢复 —— 这样能省内存,跑大量测试的时候速度也会变快。比如处理 jms 监听器容器、定时任务这些场景,这个功能特别有用。
另外,还新增了一个resttestclient,用它测试 rest 接口特别方便 —— 用法跟 webtestclient 差不多,但不用额外装 “响应式基础设施”。
给大家看个例子,就能明白它多简单了:
@springboottest(webenvironment = springboottest.webenvironment.random_port)
class helloworldapiintegrationtest {
resttestclient client;
@beforeeach
void setup(webapplicationcontext context) {
client = resttestclient.bindtoapplicationcontext(context)
.build();
}
@test
void shouldfetchhellov1() {
client.get()
.uri("/api/v1/hello")
.exchange()
.expectstatus()
.isok()
.expectheader()
.contenttypecompatiblewith(mediatype.text_plain)
.expectbody(string.class)
.consumewith(message -> assertthat(message.getresponsebody()).containsignoringcase("hello"));
}
}api 版本控制
很多人一直想要的 “api 版本控制”,这次终于成了框架的一级功能,不用再自己瞎折腾了。
以前咱们要搞 api 版本,得自己想办法:比如在 url 里加 /v1/,或者自定义请求头、改媒体类型。现在框架原生就支持了,直接给 @getmapping 加个 version 属性就行,看例子:
@restcontroller
@requestmapping("/hello")
public class helloworldcontroller {
// 版本1的接口,返回“hello world”
@getmapping(version = "1", produces = mediatype.text_plain_value)
public string sayhellov1() {
return "hello world";
}
// 版本2的接口,返回“hi world”
@getmapping(version = "2", produces = mediatype.text_plain_value)
public string sayhellov2() {
return "hi world";
}
}也可以在控制器级别统一设版本,比如下面这个 “版本 3” 的控制器:
@restcontroller
@requestmapping(path = "/hello", version = "3")
public class helloworldv3controller {
// 不用再给方法加version了,默认用控制器的版本3
@getmapping(produces = mediatype.text_plain_value)
public string sayhello() {
return "hey world";
}
}不过得配置一下 “版本怎么映射到请求上”,有四种方式可选:
- 路径映射:比如 /api/v1/hello 和 /api/v2/hello
- 查询参数:比如 /hello?version=1 和 /hello?version=2
- 请求头:比如请求头里加 x-api-version: 1 或 2
- 媒体类型头:比如请求头 accept: application/json; version=1
下面是 “路径映射” 的配置例子,大家可以参考:
@configuration
public class apiconfig implements webmvcconfigurer {
@override
public void configureapiversioning(apiversionconfigurer configurer) {
// 用路径的第1段作为版本(比如/v1/里的1)
configurer.usepathsegment(1);
}
@override
public void configurepathmatch(pathmatchconfigurer configurer) {
// 给所有@restcontroller的接口加前缀/api/v{version}
configurer.addpathprefix("/api/v{version}", handlertypepredicate.forannotation(restcontroller.class));
}
}配置完之后,spring 会自动识别请求里的版本,对应到正确的接口。这样迭代 api 的时候,就不用担心影响老用户了。
@httpserviceclient:写 http 客户端更简单
还有个好用的功能:声明式 http 客户端。灵感来自 feign,但更轻量,而且跟 spring 融得更紧。
以前用 spring,要调用其他服务的 http 接口,得给 httpinterface 创建代理,想搞智能点还得自己写代码。比如这个仓库里,就有个自定义的 @httpclient 注解和 bean 注册器的例子(spring framework 7 还优化了 bean 注册器,这篇文章有说)。
现在不用自己写了,框架直接给了 @httpserviceclient 注解,开箱即用。看个例子就懂了:
// 给这个接口加注解,指定服务名叫“christmasjoy”
@httpserviceclient("christmasjoy")
public interface christmasjoyclient {
// 用@getexchange指定请求地址,相当于发get请求到/greetings?random
@getexchange("/greetings?random")
string getrandomgreeting();
}然后要配置一下 “扫描这个接口” 和 “服务的基础地址”:
@configuration
// 导入自定义的注册器
@import(httpclientconfig.helloworldclienthttpserviceregistrar.class)
public class httpclientconfig {
// 自定义注册器,负责扫描并注册httpserviceclient接口
static class helloworldclienthttpserviceregistrar extends abstractclienthttpserviceregistrar {
@override
protected void registerhttpservices(groupregistry registry, annotationmetadata metadata) {
// 扫描com.baeldung.spring.mvc包下的httpserviceclient接口
findandregisterhttpserviceclients(registry, list.of("com.baeldung.spring.mvc"));
}
}
// 配置“christmasjoy”这个服务的基础地址
@bean
restclienthttpservicegroupconfigurer christmasjoyservicegroupconfigurer() {
return groups -> {
groups.filterbyname("christmasjoy") // 找到名叫christmasjoy的服务组
.foreachclient((group, clientbuilder) -> {
// 设置基础url
clientbuilder.baseurl("https://christmasjoy.dev/api");
});
};
}
}配置完之后,这个 christmasjoyclient 就能像普通 bean 一样注入到其他组件里用了,比如下面这个控制器:
@restcontroller
@requestmapping(path = "/hello", version = "4")
@requiredargsconstructor // lombok注解,自动生成构造器
public class helloworldv4controller {
// 注入christmasjoyclient
private final christmasjoyclient christmasjoy;
@getmapping(produces = mediatype.text_plain_value)
public string sayhello() {
// 调用客户端的方法,获取随机问候语
return this.christmasjoy.getrandomgreeting();
}
}弹性能力内置:加个注解就有重试、限流
spring retry 虽然在生态里好多年了,但一直像个 “外挂”,不是框架核心功能。这次 spring framework 7 直接把 “弹性能力”(比如重试、限流)做成内置的了—— 给 spring 组件的方法加个注解,就能实现重试逻辑或者并发限制,特别方便。
看个例子,还是刚才的 christmasjoyclient,加两个注解就行:
@httpserviceclient("christmasjoy")
public interface christmasjoyclient {
@getexchange("/greetings?random")
// 重试:最多试3次,第一次等100ms,之后每次等的时间翻倍,最多等1000ms
@retryable(maxattempts = 3, delay = 100, multiplier = 2, maxdelay = 1000)
// 并发限制:最多3个请求同时调用
@concurrencylimit(3)
string getrandomgreeting();
}不过要注意:这些注解默认是不生效的,得在某个配置类上加 @enableresilientmethods 才能启用。
这样一来,不用再依赖 resilience4j 这类额外库(当然,想集成也能正常用),就能实现弹性功能,省了不少配置功夫。而且还能在运行时验证这些策略有没有生效,不用担心注解白加了。
支持多个 taskdecorator:异步任务能加多个 “钩子”
以前用 spring 的时候,想自定义异步任务的执行逻辑,顶多只能在 threadpooltaskexecutor 上挂一个 taskdecorator。比如把安全上下文(securitycontext)或者日志里的 mdc 信息传到异步线程里,靠它还能实现。但要是想同时搞好几件事,比如:又传上下文又打日志,就得自己手动写个 “复合装饰器”,特别麻烦。
好在从 spring framework 7 开始,咱们能在应用上下文里声明多个 taskdecorator bean 了!spring 会自动把它们串成一个 “装饰链”,按 bean 定义的顺序或者 @order 注解标的顺序,一个接一个生效。
举个实际例子,咱们有个异步的事件监听器:
@component
@slf4j
public class helloworldeventlogger {
@async // 标了这个就是异步执行
@eventlistener
void loghelloworldevent(helloworldevent event) {
log.info("hello world event: {}", event.message());
}
}要是想给这个异步任务加个简单日志(记录开始结束),再算个执行时间,不用写复杂代码,直接注册两个 taskdecorator bean 就行:
@configuration
@slf4j
public class taskdecoratorconfiguration {
// 第一个装饰器:打任务开始/结束日志,@order标了2
@bean
@order(2)
taskdecorator loggingtaskconfigurator() {
return runnable -> () -> {
log.info("running task: {}", runnable); // 任务开始日志
try {
runnable.run(); // 执行实际任务
} finally {
log.info("finished task: {}", runnable); // 任务结束日志
}
};
}
// 第二个装饰器:算执行时间,@order标了1(比上面先执行)
@bean
@order(1)
taskdecorator measuringtaskconfigurator() {
return runnable -> () -> {
final var ts1 = system.currenttimemillis(); // 开始时间
try {
runnable.run(); // 执行实际任务
} finally {
final var ts2 = system.currenttimemillis(); // 结束时间
log.info("finished within {}ms (task: {})", ts2 - ts1, runnable); // 输出耗时
}
};
}
}最后日志会输出这样的内容:
running task: com.baeldung.spring.mvc.taskdecoratorconfiguration$$lambda/0x00000ff0014325f8@57e8609
hello world event: "happy christmas"
finished within 0ms (task: java.util.concurrent.futuretask@bb978d6[completed normally])
finished task: com.baeldung.spring.mvc.taskdecoratorconfiguration$$lambda/0x00000ff0014325f8@57e8609
这波改进是真省心,不用再写一堆重复的复合装饰器代码了,想给异步任务加多个功能(比如日志、计时、权限校验),直接加 bean 就行,特别方便。
用 jspecify 搞定空安全,终于不混乱了
之前 java 生态里的空值注解随处可见(@nonnull、@nullable、@notnull 等),现在 spring framework 7 干脆定了标准:用 jspecify,示例长这样:
这改进了 ide 工具和 kotlin 互操作性,减少了大型代码库中 nullpointerexception 的风险。
总结
spring boot 4 和 spring framework 7 真不是 “小打小闹的更新”,而是 spring 特意朝着 java 开发的 “现代化、模块化、云原生” 方向迈的一大步:
- 有了 api 版本控制和弹性注解,应用既能轻松升级,又能扛住各种异常
- 靠 jspecify 的空安全和 kotlin 支持,运行时出错能少很多。
- 声明式 http 客户端,服务之间调用不用写一堆请求代码了。
- 原生镜像支持和可观测性工具加强,往云环境上部署更顺了。
跟所有大版本升级一样,关键是早点拿项目测。尤其是依赖升级和旧 api 替换这两块,早发现问题早解决。不过话说回来,升级后开发效率变高、性能变好、维护也省心,这点麻烦还是值得的。
到此这篇关于spring boot 4 与 spring framework 7 全面解析:新特性、升级要点与实战指南的文章就介绍到这了,更多相关spring boot 4 与 spring framework 7 新特性内容请搜索代码网以前的文章或继续浏览下面的相关文章希望大家以后多多支持代码网!
发表评论