Spring 7.0更新特性大全,一次全学会
写在前面
Spring 7.0暂未发布正式版(要等),特性可能随着时间变化而变化。
计划兼容JDK 17-27,提升了一部分依赖的组件版本。
比如Tomcat,最低要求Tomcat 11(现在一般都是Spring Boot,几乎不需要自己关注Tomcat)。
话不多说,下面进入正题。
移除Spring JCL
这个版本中spring-jcl这个包被移除了。
替代选项是 Apache Commons Logging 1.3.0 ,对于应用开发来说,没有太大影响。
移除了对javax.annotation和javax.inject注解的支持
javax.annotation 和 javax.inject 包中的注释不再受支持。
这包括 @javax.annotation.Resource、
@javax.annotation.PostConstruct、@javax.inject.Inject 等注释。
如果您的应用程序仍然使用这些包中的注释,您需要迁移到 jakarta.annotation 和 jakarta.inject 包中的等效注释。
大概是升级Spring Boot 3.0的时候,Servlet的依赖包就已经变了(换成jakarta了)。
路径映射选项被移除
变更如下:
1. 后缀模式匹配
(suffixPatternMatch/registeredSuffixPatternMatch)
通过URL后缀匹配请求(如.json、.xml),移除后@RequestMapping("/user.{format}")不再匹配/user.json。
修复办法,改用内容协商(Content Negotiation)或显式路径定义。
历史代码:
代码语言:javascript代码运行次数:0运行复制@GetMapping("/user.{format}") // 匹配/user.json或/user.xml
public User getUser(@PathVariable String format) {
return new User("张三");
}
新方案:
代码语言:javascript代码运行次数:0运行复制// 通过请求参数或Accept头协商内容类型
@GetMapping(value = "/user", produces = {MediaType.APPLICATION_JSON_VALUE, MediaType.APPLICATION_XML_VALUE})
public User getUser(@RequestHeader("Accept") String accept) {
return new User("张三");
}
2. 结尾斜杠匹配(trailingSlashMatch)
控制是否匹配结尾的斜杠(如:/user和/user/),移除之后路径/user和/user/被视为不同请求。
修复方案可以直接使用一个,或者一次定义两个路径。
如下:
代码语言:javascript代码运行次数:0运行复制@GetMapping({"/user", "/user/"})
public User getUser() {
return new User("张三");
}
3. 路径扩展内容协商
(favorPathExtension/ignoreUnknownPathExtensions)
根据URL扩展名(如.json)决定响应格式。移除之后,/user.json不再自动返回JSON。
修复方案,用@RestController注解。
4. 可选结尾分隔符
(matchOptionalTrailingSeparator)
在路径模式中允许可选的结尾分隔符(如/user/?),修复方案,使用正则表达式定义请求路径。
OkHttp3支持
官方提供了OkHttp3支持。
但更多时候,我在使用Retrofit。
Retrofit基于OkHttp封装,通过注解(如 @GET、@POST)简化接口定义。
自动将 HTTP 响应转换为 Java/Kotlin 对象(如 Gson 解析)。
Retrofit接口定义示例:
代码语言:javascript代码运行次数:0运行复制public interface GitHubService{
@GET("users/{user}/repos")
Call<List<Repo>> listRepos(@Path("user") String user);
@GET("group/{id}/users")
Call<List<User>> groupList(@Path("id") int groupId, @Query("sort") String sort);
@POST("users/new")
Call<User> createUser(@Body User user);
@FormUrlEncoded
@POST("user/edit")
Call<User> updateUser(@Field("first_name") String first, @Field("last_name") String last);
}
调用示例:
代码语言:javascript代码运行次数:0运行复制Retrofit retrofit = new Retrofit.Builder()
.baseUrl("/")
.build();
GitHubService service = retrofit.create(GitHubService.class);
Call<List<Repo>> repos = service.listRepos("octocat");
弃用部分xml配置
弃用了以<mvc:*开头的XML配置命名空间,需要使用Java配置。
但<bean>不会被弃用。
空安全
Spring框架中的JSR 305语义的空值注解已被弃用,需要使用JSpecify。
具体是下面这几个注解:
@Nullable 、 @NonNull 、 @NonNullApi 和 @NonNullFields
虽然距离被彻底删除还有一段时间,但可以先看一下JSpecify。
官方给出的弃用理由是:
- Spring的注解仅适用于字段、参数、返回值。
- JSpecify注解在Spring注解基础上增加了类型、类。
JSpecify的例子:
代码语言:javascript代码运行次数:0运行复制// @Nullable: 返回值可能为空
// @NonNull: 入参不能为空
static @Nullable String emptyToNull(@NonNull String x) {
return x.isEmpty() ? null : x;
}
// @Nullable: 入参可空
// @NonNull: 返回值不能为空
static @NonNull String nullToEmpty(@Nullable String x) {
return x == null ? "" : x;
}
// @NullMarked:
//
// 一旦某个类或包被标记为@NullMarked,则其中所有未使用@Nullable明确标记的参数、返回值或字段都将被视为不可为null。
// 在代码中,@NullMarked应用于整个方法声明,意味着该方法内部的所有类型使用(如参数list和toRemove)若未特别标注,则默认不可为null,除非显式使用@Nullable
//
@NullMarked
public static <T> List<@Nullable T> nullOutMatches(List<T> list, T toRemove) {
List<@Nullable T> copy = new ArrayList<>(list);
for (int i = 0; i < copy.size(); i++) {
if (copy.get(i).equals(toRemove)) {
copy.set(i, null);
}
}
return copy;
}
HttpHeaders更改
在Spring 7.0中,HttpHeaders类不再继承MultiValueMap接口 。
原因 :
HTTP头信息本质是不区分大小写的键值对集合 ,而传统的Map类操作(如get()/put())在以下场景表现不佳:
1. 性能问题 :MultiValueMap的哈希表结构对大小写不敏感的头字段匹配效率低下;
2. 语义冲突 :Map的键唯一性约束与HTTP头允许重复键(如Set-Cookie)的特性矛盾。
类文件API修改
这个特性跟普通CRUD关系不大。
Spring 通过读取类字节码来收集代码元数据。
之前他们使用了一个简化的 ASM 分支来实现这一目的,
通过
org.springframework.core.type.classreading
包中的 MetadataReaderFactory 和 MetadataReader 类型。
尽管基于 Spring 应用程序通常不会直接接触到这个 API,但在解析 @Configuration 类或查找应用程序代码上的注解时,这特别有用。
Java 24 引入了一个新的类文件API,用于读取和写入类作为Java字节码。
Spring Framework 7.0 采用这一特性。
针对Java 24+(注意:JDK 24非长期支持版本)应用程序,在spring-core中实现了新的ClassFileMetadataReader。
API版本支持
从7.0版本开始,服务器Web应用程序可以通过在@RequestMapping注解中声明版本范围。
将特定版本的路径映射到不同的控制器方法。
版本可以从多个来源解析,如请求URL路径、头部值等。
代码语言:javascript代码运行次数:0运行复制@GetMapping(value = "/query", version = "1.0.0")
public ResponseEntity<String> queryV1() {
return ResponseEntity.ok("query api v1.0.0...");
}
@GetMapping(value = "/query", version = "2.0.0")
public ResponseEntity<String> queryV2() {
return ResponseEntity.ok("query api v2.0.0...");
}
当客户端请求 API 时,若 v 参数值与 version 定义的值匹配,则该接口会被调用。例如:
- 访问 /api/query?v=1.0.0,返回 query api v1.0.0...
- 访问 /api/query?v=2.0.0,返回 query api v2.0.0...
若请求的 v 参数不匹配任何版本,则会抛出 InvalidApiVersionException。
HTTP接口代理
HttpServiceProxyFactory 使得创建 HTTP 接口的代理变得简单。
一句话来说,就是可以将你的Service直接发布成接口。
示例:
代码语言:javascript代码运行次数:0运行复制// 1. 定义多个HTTP服务接口
@HttpExchange(url = ";)
public interface OrderService {
@PostExchange("/orders")
Order createOrder(@RequestBody OrderRequest request);
}
@HttpExchange(url = ";)
public interface PaymentService {
@GetExchange("/payments/{id}")
Payment getPayment(@PathVariable String id);
}
// 2. 自动注册代理Bean(Spring Boot 3+)
@Configuration
public class HttpConfig {
@Bean
public OrderService orderService(WebClient.Builder builder) {
return HttpServiceProxyFactory.builder(builder.baseUrl(";).build())
.build()
.create(OrderService.class);
}
// PaymentService同理...
}
更优雅的Bean注册
老代码,有时候会在@Configuration类中的单个@Bean方法内尝试注册多个Bean。
代码不优雅,又影响逻辑。
因此这个版本增加了一个特性,可以更灵活的注册Bean。
示例代码如下:
代码语言:javascript代码运行次数:0运行复制@Configuration
@Import(MyBeanRegistrar.class)
class MyConfiguration {
}
class MyBeanRegistrar implements BeanRegistrar {
@Override
public void register(BeanRegistry registry, Environment env) {
registry.registerBean("foo", Foo.class);
registry.registerBean("bar", Bar.class, spec -> spec
.prototype()
.lazyInit()
.description("Custom description")
.supplier(context -> new Bar(context.bean(Foo.class))));
if (env.matchesProfiles("baz")) {
registry.registerBean(Baz.class, spec -> spec
.supplier(context -> new Baz("Hello World!")));
}
}
}
看起来还挺简单的。你觉得呢?
本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。原始发表:2025-04-24,如有侵权请联系 cloudcommunity@tencent 删除代理接口配置注解spring