Spring Boot 升级3.x 指南
1. 升级思路
先创建一个parent项目,打包类型为pom,继承自spring boot的parent项目
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.x</version>
</parent>
然后把版本集中放在这个pom里面,示例如下
<properties>
<!-- 建议添加全局变量 java.version,maven.compiler.source, maven.compiler.target-->
<java.version>17</java.version>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
<spring-cloud-dependencies.version>2022.0.4</spring-cloud-dependencies.version>
<spring-cloud-starter-netflix.version>2.2.10.RELEASE</spring-cloud-starter-netflix.version>
</properties>
然后添加dependencyManagement
节点,示例如下:
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud-dependencies.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
<version>${spring-cloud-starter-netflix.version}</version>
<exclusions>
<exclusion>
<artifactId>jsr311-api</artifactId>
<groupId>javax.ws.rs</groupId>
</exclusion>
<exclusion>
<artifactId>jackson-annotations</artifactId>
<groupId>com.fasterxml.jackson.core</groupId>
</exclusion>
<exclusion>
<artifactId>jackson-core</artifactId>
<groupId>com.fasterxml.jackson.core</groupId>
</exclusion>
<exclusion>
<artifactId>jackson-databind</artifactId>
<groupId>com.fasterxml.jackson.core</groupId>
</exclusion>
<exclusion>
<artifactId>guava</artifactId>
<groupId>com.google.guava</groupId>
</exclusion>
</exclusions>
</dependency>
<dependencies>
</dependencyManagement>
其它服务都继承这个pom文件,这样各个组件的版本就能统一起来了,将来如果某个组件要升级,直接升级这个项目的版本,其它的重新打包发布即可。
注意:
- 动手前一定要调研项目中使用的组件,某些组件是没法升级的。比如ElasticSearch,驱动版本和ElasticSearch server版本要一致,升级了就会报错。其它常见组件的有Nacos,Kafka,Mysql,RocketMQ,需要调研是否兼容低版本。
- 如果之前使用了Zuul 1.x作为Gateway,Zuul 2.x不开源并且难以升级,建议升级到Spring Cloud Gateway
- 如果使用了Zuul1.x的作为Proxy嵌入服务中,有两个思路,一个是使用Filter+HttpClient手动写转发代码,第二个思路是调研调用接口,使用Feign做转发
- Spring Boot 3.x 需要JDK17,建议使用OpenJDK17,Oracle的JDK17可能存在授权问题
2. 遇到的问题
-
Zuul1.x 升级Spring Cloud Gateway,可以参考 Zuul迁移至Spring Cloud Gateway踩坑记录,
此记录中服务使用的是k8s部署,转发直接配置uri,如果使用的是Nacos,网络上博客比较多,这里不再赘述。 -
如果是Spring Boot 1.x升级上来的,可能要注意循环依赖,添加以下可以解决:
spring: main: allow-circular-references: true
-
Spring Boot 3.x 支持优雅退出,添加以下配置开启
# 打开优雅退出 server: shutdown: graceful # 多长时间后强制杀掉进程 spring: lifecycle: timeout-per-shutdown-phase: 30s
-
Beancopier
可能没法用了,可以使用BeanUtil.copyProperties
替换 -
JDK8升级到JDK17,javax包变成了jakarta,需要替换所有的javax.annotation和javax.validation等,但javax.mail没有变,当jakarta.xxx不存在时,还是使用javax.xxx即可
-
如果引入外部配置文件,使用
spring.cloud.bootstrap.additional-location=/data/config/bootstrap.yml,/data/config/bootstrap2.yml
即可 -
如果依赖的一些jar中依赖一些类但由于升级,依赖类已经不存在了,典型的就是
WebMvcConfigurerAdapter.class
,之前是继承WebMvcConfigurerAdapter
,Spring Boot 3.x 已经改成了实现接口WebMvcConfigurer
,可能会出现FileNotFoundException
,此时可能难以定位是哪个jar,参考SpringBoot版本升级引起的FileNotFoundException——WebMvcConfigurerAdapter.class -
Spring Cloud Gateway 配置文件参考(可能遇到的问题已经写在了注释中):
spring: cloud: gateway: # 默认过滤器 default-filters: # 将path中第一个/xxx去掉 比如请求是 https://www.xxx.com/a/b/c?d=1 # 经过这个过滤器之后就是 https://www.xxx.com/b/c?d=1 - StripPrefix=1 # 下面这两个过滤器是gateway和后面的服务都配置了跨域头,防止返同样的回头有多个导致跨域失败 # 典型的 access-control-allow-credentials: true,true 返回到前端导致跨域失败 - DedupeResponseHeader=access-control-allow-credentials,RETAIN_UNIQUE - DedupeResponseHeader=access-control-allow-origin,RETAIN_UNIQUE routes: # 服务名 - id: user # 转发到的url 下面的示例是k8s内部转发 # 如果使用服务名转发 开头应该是lb:xxx # 这个端口后不要加任何东西 因为转发的时候会忽略掉 # 比如 http://service-user.inner:8080/aaa最后拼接出来是http://service-user.inner:8080,/aaa就忽略了 uri: http://service-user.inner:8080 predicates: # 匹配的请求url中的path 下面这个会匹配到 http://www.xxx.com/gateway/user/login?userName=AAA - Path=/gateway/user/** filters: # StripPrefix:去除原始请求路径中的前1级路径 # 会把 http://www.xxx.com/gateway/service1/login中的service1去掉 - StripPrefix=1 # 在转发后的url添加的前缀 经过这个filter 转发url就变成了 http://service-user.inner:8080/service-user - PrefixPath=/service-user # 这里讲一下全流程 # 以请求为 http://www.xxx.com/gateway/user/login?userName=AAA为例 这个url是要登录,登录服务名为service-user # 断言规则 spring.cloud.routes > predicates > Path=/gateway/user/** 能匹配到url http://www.xxx.com/gateway/user/login?userName=AAA # 第一步是默认过滤器 经过 spring.cloud.gateway.default-filters > StripPrefix=1 这个配置后就变成了 http://www.xxx.com/user/login?userName=AAA # 第二步是routers过滤器 spring.cloud.routes下的id=user的 filters > StripPrefix=1会将 http://www.xxx.com/user/login?userName=AAA的/user去掉,变成了 http://www.xxx.com/login?userName=AAA 变成 # 第三步是routers过滤器 spring.cloud.routes下的id=user的 filters > PrefixPath=/service-user 会将 http://www.xxx.com/login?userName=AAA 变成 http://service-user.inner:8080/service-user服务/login?userName=AAA # 经过上面的处理后,最终会转发到 service-user服务 # response中的header如果有跨域header 会经过 spring.cloud.gateway.default-filters > DedupeResponseHeader过滤器将重复的header去掉