写在前面
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> listRepos(@Path("user") String user);
@GET("group/{id}/users")
Call> groupList(@Path("id") int groupId, @Query("sort") String sort);
@POST("users/new")
Call
@FormUrlEncoded
@POST("user/edit")
Call
}调用示例:
代码语言:javascript代码运行次数:0运行复制Retrofit retrofit = new Retrofit.Builder()
.baseUrl("https://api.github.com/")
.build();
GitHubService service = retrofit.create(GitHubService.class);
Call> repos = service.listRepos("octocat");弃用部分xml配置
弃用了以 但 空安全 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 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 return ResponseEntity.ok("query api v1.0.0..."); } @GetMapping(value = "/query", version = "2.0.0") public ResponseEntity 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 = "https://api.example.com") public interface OrderService { @PostExchange("/orders") Order createOrder(@RequestBody OrderRequest request); } @HttpExchange(url = "https://payment.example.com") 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("https://api.example.com").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!"))); } } }看起来还挺简单的。你觉得呢?