Spring Boot 3 Upgrade
前言
介绍一下Spring Boot 低版本升级到Spring Boot 3 的一些内容。
配置属性迁移
-
在 Spring Boot 2.0 中,许多配置属性被重命名/删除,开发人员需要相应地更新他们的
application.properties/application.yml
。 -
为了帮助您解决这个问题,Spring Boot 提供了一个新的
spring-boot-properties-migrator
模块。 一旦添加为项目的依赖项,这不仅会在启动时分析应用程序的环境并打印诊断信息,还会在运行时为您临时迁移属性。 这是您的应用程序迁移过程中必须具备的:
Maven版本
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-properties-migrator</artifactId>
<scope>runtime</scope>
</dependency>
Gradle版本
runtime("org.springframework.boot:spring-boot-properties-migrator")
切记:完成迁移后,请确保从项目的依赖项中删除这个依赖。
从Spring Boot 2.x 升级到Spring Boot 3.0.x
如果想将自己项目的Spirng Boot 2.x 升级到SpringBoot 3.x,那么建议先升级到2.7.x 版本确定没问题后再继续操作。
依赖检查
我们需要自己查看和评估 Spring Boot 2.7.x 的依赖和Spring Boot 3.0.x 版本的依赖变化对自身项目的影响。
image.png
Spring Security 重大升级
- Spring Boot 3.0 使用了 Spring Security 6.0。
- Spring Security 团队发布了 Spring Security 5.8 以简化向 Spring Security 6.0 的升级。
- 在升级到 Spring Boot 3.0 之前,请考虑将您的 Spring Boot 2.7 应用程序升级到 Spring Security 5.8。
- Spring Security 团队已经制作了一份迁移指南,可以帮助我们这样做。
https://docs.spring.io/spring-security/reference/5.8/migration/index.html
WebSecurityConfigurerAdapter
is deprecated in Spring 3.
This can be change from:
@Configuration
open class SecurityConfiguration: WebSecurityConfigurerAdapter() {
@Override
override fun configure(val http: HttpSecurity) {
http {
authorizeHttpRequests {
authorize(anyRequest, authenticated)
}
httpBasic {}
}
}
}
to
@Configuration
open class SecurityConfiguration {
@Bean
fun filterChain(http: HttpSecurity): SecurityFilterChain {
http {
authorizeHttpRequests {
authorize(anyRequest, authenticated)
}
httpBasic {}
}
return http.build()
}
}
and In-Memory Authentication
can be changed from
@Configuration
open class SecurityConfiguration: WebSecurityConfigurerAdapter() {
override fun configure(auth: AuthenticationManagerBuilder auth) {
auth.inMemoryAuthentication()
.withUser("username")
.password("{noop}pass")
.authorities(listOf())
}
}
to
@Configuration
open class SecurityConfiguration {
@Bean
fun userDetailService():InmemoryUserDetailsManager {
val user: UserDetails = User.withDefaultPasswordEncoder()
.username("username")
.password("pass")
.authorities(listOf())
.build()
return InMemoryUserDetailsManager(user)
}
}
Note: You can't use {noop}
to indicate the encryptor
Refer to these guides for more information:
- Configuration Migrations
The new requestMatchers methods were added to authorizeHttpRequests , authorizeRequests, CSRF configuration…docs.spring.io
and
- Spring Security without the WebSecurityConfigurerAdapter
In Spring Security 5.7.0-M2 we deprecated the WebSecurityConfigurerAdapter, as we encourage users to move towards a… spring.io
检查系统需要
Spring Boot 3.0.x 需要Java 17 或更高版本以及Spring Framework 6,JDK8 不再支持。
查看 Spring Boot 2.x 的弃用
Spring Boot 2.x 中弃用的类、方法和属性已在此版本中删除。 请确保在升级之前没有调用已弃用的方法。
配置属性迁移
在 Spring Boot 3.0 中,一些配置属性被重命名/删除,开发人员需要相应地更新他们的 application.properties/application.yml。
为了帮助我们,Spring Boot 提供了一个 spring-boot-properties-migrator 模块。
一旦添加为项目的依赖项,这不仅会在启动时分析应用程序的环境并打印诊断信息,还会在运行时为您临时迁移属性。
- Maven版本
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-properties-migrator</artifactId>
<scope>runtime</scope>
</dependency>
- Gradle版本
runtime("org.springframework.boot:spring-boot-properties-migrator")
切记:完成迁移后,请确保从项目的依赖项中删除这个依赖。
升级到 Spring Framework 6.x
Spring 版本升级到 Spring Framework 6.x
Spring Boot 3.x 依赖 Jakarta EE 规范
Spring Boot 3.0 已升级到 Jakarta EE 10 中包含的版本。
例如,Spring Boot 3.0 使用 Servlet 6.0 和 JPA 3.1 规范。
如果您正在管理自己的依赖项,而不依赖于我们的入门 POM,则应确保已适当更新 Maven 或 Gradle 文件。 您需要特别注意旧的 Java EE 依赖项不再直接或可传递地用于您的构建中。 例如,如果您应该始终使用
jakarta.servlet:jakarta.servlet-api
jakarta.persistence.EmbeddedId
jakarta.persistence.Entity
jakarta.persistence.Lob
jakarta.persistence.Table
而不是
javax.servlet:javax.servlet-api
javax.persistence.EmbeddedId
javax.persistence.Entity
javax.persistence.Lob
javax.persistence.Table
核心改变
Spring Boot 3.x 中Spring Boot 核心的一些东西也发生了很大变化。
图片Banner不再支持
-
Spring Boot 启动时候 Banner ,已不再支持图片这种类型。
image.png
banner.gif、banner.jpg 和 banner.png 文件现在将被忽略,应替换为基于文本的 banner.txt 文件。
日期格式输出
-
Logback
和Log4j2
日志消息的日期和时间组件的默认格式已更改以符合ISO-8601
标准。 -
新的默认格式
yyyy-MM-dd'T'HH:mm:ss.SSSXXX
使用 T 代替空格字符分隔日期和时间,并在末尾添加时区偏移量。 -
LOG_DATEFORMAT_PATTERN
环境变量或logging.pattern.dateformat
属性可用于恢复以前的默认值yyyy-MM-dd HH:mm:ss.SSS
。
类级别不再需要@ConstructingBinding
在@ConfigurationProperties
类的类型级别不再需要@ConstructorBinding,应该将其删除。
当一个类或记录有多个构造函数时,它仍然可以用在构造函数上以指示应该使用哪个构造函数进行属性绑定。
YamlJsonParser 已经被移除
YamlJsonParser
已被删除,因为 SnakeYAML
的 JSON 解析与其他解析器实现不一致。
如果我们直接使用的 YamlJsonParser
,请迁移到其他 JsonParser
实现之一
自动配置文件
Spring Boot 2.7 介绍了一个新的META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
用于注册自动配置,同时保持与 spring.factories
中注册的向后兼容性。
在Spring Boot 3.x 版本中,已删除对在 spring.factories
中注册自动配置的支持,以支持导入文件。
web 应用程序变化
如果我们升级的是一个web应用,那么需要好好看看这些改变。
Spring MVC 和WebFlux URL匹配变化
从 Spring Framework 6.0 开始,尾部斜杠匹配配置选项已被弃用,其默认值设置为 false。
这意味着以前,以下控制器将同时匹配“GET /some/greeting
”和“GET /some/greeting/
”:
@RestController
public class MyController {@GetMapping("/some/greeting")public String greeting {return "Hello";}}
在这个改变中,GET /some/greeting/
将会匹配失败返回404错误
开发人员应该改为通过代理、Servlet/web 过滤器配置显式重定向/重写,甚至在控制器处理程序上显式声明附加路由(如 @GetMapping("/some/greeting", "/some/greeting/")
更有针对性的案例。
在我们的应用程序完全适应这种变化之前,您可以使用以下全局配置更改默认值:
@Configuration
public class WebConfiguration implements WebMvcConfigurer {@Overridepublic void configurePathMatch(PathMatchConfigurer configurer) {configurer.setUseTrailingSlashMatch(true);}}
server.max-http-header-size
以前,server.max-http-header-size
在四个受支持的嵌入式 Web 服务器中的处理方式不一致。
-
使用 Jetty、Netty 或 Undertow 时,它将配置最大 HTTP 请求标头大小。
-
使用 Tomcat 时,它会配置最大 HTTP 请求和响应标头大小。
为了解决这种不一致,已弃用 server.max-http-header-size
并引入了替代品 server.max-http-request-header-size
。
这两个属性现在仅适用于请求标头大小,与底层 Web 服务器无关。
要限制 Tomcat 或 Jetty(仅有的两个支持此类设置的服务器)上 HTTP 响应的最大标头大小,请使用 WebServerFactoryCustomizer
。
Jetty
Jetty 不再支持Servlet 6.
要在Spring Boot 3.x 中使用Jetty,需要下载servlet API 更新到5.0
我们也可以使用jakarta-servlet.version
Actuator 健康检查变化
如果项目中用了actuator 依赖,则需要关注如下变化。
- JMX Endpoint Exposure
- 默认情况下,现在只有健康端点通过 JMX 公开,以与默认的 Web 端点公开保持一致。
- 这可以通过配置
management.endpoints.jmx.exposure.include
和management.endpoints.jmx.exposure.exclude
属性来改变。
-
httptrace
端点重命名为httpexchanges
- httptrace 端点和相关基础设施记录并提供对最近
HTTP
请求-响应交换信息的访问。- 在引入对
Micrometer Tracing
的支持后,名称httptrace
可能会引起混淆。- 为了减少这种可能的混淆,端点已重命名为
httpexchanges
。- 端点响应的内容也受到此重命名的影响。
- 相关的基础设施类也已重命名。 例如,
HttpTraceRepository
现在被命名为HttpExchangeRepository
并且可以在org.springframework.boot.actuate.web.exchanges
包中找到
- Actuator JSON
- Spring Boot 附带的执行器端点的响应现在使用隔离的
ObjectMapper
实例来确保结果一致。- 如果您想恢复到旧行为并使用应用程序 ObjectMapper,您可以将
management.endpoints.jackson.isolated-object-mapper
设置为false
。- 如果您开发了自己的端点,您可能希望确保响应实现
OperationResponseBody
接口。- 这将确保在将响应序列化为
JSON
时考虑隔离的ObjectMapper
。
- Acutator 端点敏感值
- 由于
/env
和 /configprops 端点可以包含敏感值,因此默认情况下始终屏蔽所有值。
- 这曾经只适用于被认为是敏感的密钥。
- 相反,此版本选择了更安全的默认设置。
* 基于键的方法已被删除,取而代之的是基于角色的方法,类似于健康端点详细信息。 * 是否显示未过滤的值可以使用属性 `management.endpoint.env.show-values` 或 `management.endpoint.configprops.show-values` 进行配置,它们可以具有以下值: * NEVER - 所有值都经过清理(默认值)。 * ALWAYS - 所有值都出现在输出中(将应用清理功能)。 * WHEN_AUTHORIZED - 仅当用户获得授权时,值才会出现在输出中(将应用清理功能)。
- 对于 JMX,用户始终被视为已授权。 对于 HTTP,如果用户通过身份验证并具有指定的角色,则认为他们已获得授权。
- QuartzEndpoint 的清理也可以使用属性
management.endpoint.quartz.show-values
以相同的方式配置。
数据访问层变化
- Hibernate 6.1 变化
- Spring Boot 3.0 默认使用 Hibernate 6.1。
- 请参阅 Hibernate 6.0 和 6.1 迁移指南以了解这对您的应用程序有何影响。
- 依赖管理和
spring-boot-starter-data-jpa starter
已更新为使用新的org.hibernate.orm
组 ID 作为其 Hibernate 依赖项。spring.jpa.hibernate.use-new-id-generator-mappings
配置属性已被删除,因为 Hibernate 不再支持切换回旧的 ID 生成器映射。
其他移除
Spring Boot 3.0 中删除了对以下依赖项的支持:
- Apache ActiveMQ
- Atomikos
- EhCache 2
- Hazelcast 3
已删除对
Apache Solr
的支持,因为其基于Jetty
的客户端Http2SolrClient
与Jetty 11
不兼容。
Spring-Kafka
Transaction-Id
The
transactional.id
property of each producer istransactionIdPrefix
+n
, wheren
starts with0
and is incremented for each new producer. In previous versions of Spring for Apache Kafka, thetransactional.id
was generated differently for transactions started by a listener container with a record-based listener to support fencing zombies.This is not necessary anymore, with
EOSMode.V2
being the only option starting with 3.0. For applications running with multiple instances, thetransactionIdPrefix
must be unique per instance.
The transaction-id-prefix
design has been changed since 3.0. Refer to this commit:
In spring-kafka 3, we need to assign a unique Transaction-Id-Prefix
for each application instance. The usual approach is to use a random string/hostname, as shown below:
fun uniqueTransactionIdPrefix(producerOnly: String = "TX-") {
return InetAddress.getLocalHost().getHostName() + this.transactionIdPrefix + producerOnly
}
Note: the producerOnly
is used to differentiate a consumer-initiated producer or a producer-only.
It is not an ideal solution. As in the Kubernetes environment, the hostname
could change after crash/restart. However, the restart might take longer than the [transaction.timeout.ms](https://kafka.apache.org/documentation/#producerconfigs_transaction.timeout.ms)
, so it should be fine.
Refer to the following Stack Overflow question for more information:
KafkaTemplate
changes
In version 3.0, the methods that previously returned ListenableFuture
have been changed to return CompletableFuture
. To facilitate the migration, the 2.9 version added a method usingCompletableFuture()
that provided the same methods with CompletableFuture
return types. This method is no longer available.
It changes from this:
kafkaTemplate.send(producerRecord).addCallback(
{
result: SendResult<Any?, Any?>? ->
log.info("xxxx")
},
{
ex: Throwable? ->
log.error("xxx")
}
)
to
kafkaTemplate.send(producerRecord).whenComplete { result, ex ->
if (ex!=null) {
// exception
} else {
// successfully sent
}
}
JacksonObjectMapper
Problem
No Serializer found for class org.springframework.http.HttpMethod and no properties discovered to create BeanSerializer
The HttpMethod
member variable name
is changed to private
in Spring 3, hence ObjectMapper
is unable to serialize the private variable
Solution
Customize the serializer or allow jacksonObjectMapper
to serialize private properties.
jacksonObjectMapper().setVisibility(PropertyAccessor.FIELD, JsonAutoDetect.Visibility.ANY)
遇到的问题例子
找不到 javax.servlet
这是因为 javax 已经捐赠给了 eclipse 基金会,已经更名为 jakarta,而 springboot3 使用到了而已。虽然说在升级过程中会遇到这个问题,但其实这个变更与springboot3 关联性不大,即使你不使用 springboot3,也可能会遇到这个问题.
初始化 ServletRegistrationBean 失败
这里会有两个可能出现的问题:
由于项目中使用到了 Druid 连接池,因此这里使用到的对象是 StatViewServlet.
第一个问题是实例化ServletRegistrationBean时传参失败,需要将 StatViewServlet 强转为 jakarta.servlet .
ServletRegistrationBean servletRegistrationBean = new ServletRegistrationBean(new StatViewServlet(),"/druid/*");
第二个问题是 StatViewServlet 这个对象内部还是使用 javax.servlet,导致失败,这种情况需要等对应的依赖包升级到使用 jakarta.servlet 的版本才行.
目前将 druid 包从 druid-spring-boot-starter 更新为 druid-spring-boot-3-starter, 是否解决还有待确认.
class file for org.apache.hc.client5.http.classic.HttpClient not found
为 RestTemplate 对象设置 RequestFactory 时使用到了 HttpComponentsClientHttpRequestFactory 对象,为其设置 httpClient 时报错了,提示需要HttpClient 类型而我传参提供的是 CloseableHttpClient 类型的,这里简单起见我是直接不设置这个 httpClient 了,因为我使用的也是默认值,没有特别的参数设置. 如果你想要传递准确的类型的话点进去 spring 框架里面看看这个接口都有哪些内部的实现就好了,没有的话再查下资料看看哪些对象实现了这个接口.
CloseableHttpClient httpClient = httpClientBuilder.build();
HttpComponentsClientHttpRequestFactory factory = new HttpComponentsClientHttpRequestFactory();
factory.setHttpClient(httpClient);
cannot access javax.servlet.http.HttpServletRequest
这种错误就是升级 javax.servlet 后带来的,依赖包依然在使用 javax.servlet,而传参却是 jakarta.servlet, 这种情况就需要等待依赖包支持 jakarta.servlet.
public static final boolean isMultipartContent(javax.servlet.http.HttpServletRequest request) {
return !"POST".equalsIgnoreCase(request.getMethod()) ? false : FileUploadBase.isMultipartContent(new ServletRequestContext(request));
}
我这里的错误是由 commons-fileupload 1.5 中的 ServletFileUpload 对象引起的.
解决方案: TDB
1.5 是 2023 年最新版本了.
看了下项目源码似乎有 commons-fileupload2,但在中央仓库没有找到对应的包. TODO 去社区问问
CommonsMultipartFile 对象没有了
spring6 删除了这个对象,需要使用另外的方法代替,spring6没有提供替换参考.
解决方案: TDB
WebSecurityConfigurerAdapter 对象没有了
spring6 删除了这个对象.
解决方案: TDB
javax.mail
解决方案: javax.mail 更新为 jakarta.mail
incompatible types: @org.checkerframework.checker.nullness.qual.NonNull java.lang.Object cannot be converted to K
使用 caffeine cache 时封装了一层,之前的版本支持当前泛型为K,使用与 caffeine 库相同的 @NonNull 为传参限制不为空这个注解时支持传参为 Object 类型,现在必须与泛型相同,不确认是 jdk17 的原因还是这个注解/caffeine 库高版本的问题.
解决方案: 更新传参 Object 类型为泛型
java.lang.NoSuchMethodError: ‘boolean com.google.protobuf.GeneratedMessageV3.isStringEmpty(java.lang.Object)’
这个似乎是因为项目中使用的 protobuf 版本太低了,升级了较新的版本后依然存在.
临时解决方案: 关闭了自动注入的 otlp.
management:
otlp:
metrics:
export:
enabled: false
required a bean of type ‘org.springframework.cloud.openfeign.FeignContext’ that could not be found.
解决方案:升级了 springcloud 和 openfeign 版本
JDK 17: InaccessibleObjectException when deserialized class has java.time.Instant field
使用 gson 反序列化时对象中有 java.time.Instant 类型字段, jdk17 中 gson 不支持这个类型,需要自己添加自定义反序列化器处理.
相关 issue: - https://github.com/google/gson/issues/1996 - https://github.com/google/gson/issues/1996 - https://github.com/google/gson/issues/1526
解决方案: 重新审查了项目代码,这个字段是不必要的,优化了代码避免了这个问题.
springfox 报错 (报错堆栈忘记保留了)
原因是 springfox 2020年已经停止维护了.
解决方案: 使用 springdoc 代替.
Flink 本地任务执行报序列化错误
Flink 任务还不支持 jdk17,master分支已经支持 还没发布.