「Offer 驾到,掘友接招!我正在参与 2022 春招系列活动 - 经验复盘,点击查看 活动详情
同学们,最近我在搭建一套个人网站,目前涉及到网关的服务搭建。《SpringCloudGateway 爆漏洞,快看看你的服务中招没?》,所以我决定将我的网关服务直接升级到3.1.1
版本,但是不升级不知道,坑是真多,此处记录一下遇到的坑,希望你们在遇到后有个参照依据。
项目源码地址:gitee.com/wei_rong_xi…
项目改造背景:juejin.cn/post/707224…
一、 项目简介
首先给大家看下我目前的整体项目构成:
- 父级项目
bsolver
- 网关
bsolver-gateway
- 代码生成器
bsolver-generator
- 公共依赖
bsolver-starter
- 页面
bsolver-ui
- 用户服务
bsolver-user
- 网关
我的项目版本依赖如下:
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.3.12.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
<version>2.2.7.RELEASE</version>
</dependency>
二、升级方案
基于整体项目的架构,目前有两种升级方案:
- 方案一:将所有工程全部升级
- 方案二:将 gateway 单独升级
我最终选择方案二
,为什么?必然经过尝试后遇到了更多的坑。
我们先看下 gateway 升级到 3.1.1 版本,其内部的依赖有哪些变化:
如上所示,组要是两点:
- springcloud 的版本达到了 3.1.1
- springboot 的版本达到 2.6.3
这与我前面使用的 springboot 版本2.3.12.RELEASE
差了十万八千里,中间相隔了 29 个版本。
2.1 方案一升级方式
下面开始方案一的升级方式,我的所有子工程都是依赖于父工程 bsolver:
<parent>
<groupId>com.wjbgn</groupId>
<artifactId>bsolver</artifactId>
<version>0.0.1-SNAPSHOT</version>
</parent>
所以我们只需要更改 bsolver 的版本就好了,这里直接使用2.6.3
了:
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.6.3</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
相应的,既然 springboot 升级了,那么我们使用的nacos
的相关依赖也是要升级的,此处就有一个小坑,我们在 maven 仓库看其版本:
正常约在上面的版本,其内部的依赖应该版本越高,但是在这个依赖当中:
2021.1
对应 springboot 的版本是2.4.2
2021.0.1.0
对应 springboot 的版本是2.6.3
至于为什么我也不想去追究了,所以此处显而易见,我们要选择2021.0.1.0
:
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
<version>2021.0.1.0</version>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
<version>2021.0.1.0</version>
</dependency>
到此为止,我以为已经大功告成了,于是启动项目看看,好家伙,直接报出以下的异常:
Failed to start bean ‘documentationPluginsBootstrapper’; nested exception is java.lang.NullPointerException
经过一番百度,这是swagger2
报出的错误。
没错我的项目使用了knife4j
作为接口文档:
经过我半天的处理,发现仍然不能解决,最终得出的结论:swagger2针对springboot的高版本根本没做适配
。
如果我要使用可以去降低 springboot 的版本,但是如此一来就针对 springgateway 的升级 3.1.1 版本就做不到了。
接口文档很有必要,所以我最终放弃了方案一,尝试只在网关服务进行改造升级。
也正是如此,可以说是因祸得福
吧,因为后面的坑还有呢!
2.2 方案二升级方式
此方案主要是针对网关服务的升级:
- 好处:其他服务保持版本不变,不影响接口文档的使用。
- 坏处:需要自己维护一套依赖,即使和其他服务重复的也不能共用了。
下面开始改造:
2.2.1 修改网关服务的父依赖:
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.6.3</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
2.2.2 引入 gateway 3.1.1 版本依赖
<!-- https://mvnrepository.com/artifact/org.springframework.cloud/spring-cloud-starter-gateway -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
<version>3.1.1</version>
</dependency>
2.2.3 单独引入 nacos 的依赖:
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
<version>2021.0.1.0</version>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
<version>2021.0.1.0</version>
</dependency>
2.2.4 引入 bootstrapstrap 依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-bootstrap</artifactId>
<version>3.1.1</version>
</dependency>
为什么要单独引入?
我们在使用 springcloud 的时候,习惯于将配置文件由properties
修改成yaml
格式,但是此时升级后就不好使了,因为在 gateway 版本2.4.5
后,bootstrap.yml 不加载了,这会导致我们的服务启动读取不到配置。
2.2.5 引入loadbalancer
<!-- Feign Client for loadBalancing -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-loadbalancer</artifactId>
<version>3.1.1</version>
</dependency>
为什么要单独引入?
原本我是不知道要引入的,但是出问题了,所有通过网关访问的接口都不能访问
,会报出503
错误,即使是接口文档都不能幸免。
2.2.6 网关调用其他服务
在通常的 springcloud 微服务中,如果需要调用其他服务接口,只需要引入openFeign
就可以了,但是在我们网关升级版本到 3.1.1 就不行了。
如果你正常引入openFeign
,并完成全部的相关配置,添加服务的feign Client
接口,并且进行调用,你会发现你的网关服务在启动阶段就被卡住了
,是不是很神奇?
我找了很久的问题所在,最后发现在 SpringCloudGateway 的 github 当中的issues
当中有过这个 bug,并且有大神已经给出了结局方案:在 2020 版本 springCloudGateway 集成 RestTemplate 和 Feign 失败问题
问题在于:SpringCloudGateway 是异步的,而 Feign 调用时同步的,新版本不被允许
解决方案就是我们需要使用WebClient
进行调用,至于是什么我就不介绍了,我直接给出使用示例:
@Autowired
private WebClient.Builder webClientBuilder;
@Bean
@LoadBalanced
public WebClient.Builder loadBalancedWebClientBuilder() {
return WebClient.builder();
}
/**
* 用户注册
*
* @param userDTO
* @return com.wjbgn.bsolver.gateway.util.dto.Result
* @author weirx
* @date: 2022/3/11
*/
@PostMapping("/register")
public Result register(@RequestBody UserDTO userDTO) {
Mono<Result> monoInfo = webClientBuilder
.build().post().uri("http://bsolver-user/user/save")
.body(BodyInserters.fromValue(userDTO)).header(HttpHeaders.CONTENT_TYPE, "application/json")
.retrieve().bodyToMono(Result.class);
// 异步调用block方法,否则会报错,因为block的内部方法blockingGet是同步方法。
CompletableFuture<Result> voidCompletableFuture = CompletableFuture.supplyAsync(monoInfo::block);
try {
return voidCompletableFuture.get();
} catch (Exception e) {
e.printStackTrace();
}
return Result.failed("注册失败");
}
需要注意的是收结果时,要使用异步调用 block,否则会报错。因为这个 block 是个阻塞方法。
2.3 改造成果
除网关以外的其他服务不用调整,我们直接启动所有的服务看结果:
- nacos 的服务注册
- 网关访问接口文档
-
网关 pom.xml
我把整个文件放在这里,有些是设计 JWT 等等的依赖,还有一些打包的配置等等,但是不是本文重点,需要了解的朋友可以去开篇提到的源码仓库去下载。
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.6.3</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.wjbgn</groupId>
<artifactId>bsolver-gateway</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>bsolver-gateway</name>
<description>网关</description>
<properties>
<java.version>1.8</java.version>
</properties>
<packaging>jar</packaging>
<dependencies>
<!-- https://mvnrepository.com/artifact/org.springframework.cloud/spring-cloud-starter-gateway -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
<version>3.1.1</version>
</dependency>
<!--升级到2.4.5后,bootstrap.yml 不加载了 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-bootstrap</artifactId>
<version>3.1.1</version>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
<version>2021.0.1.0</version>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
<version>2021.0.1.0</version>
</dependency>
<!-- Feign Client for loadBalancing -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-loadbalancer</artifactId>
<version>3.1.1</version>
</dependency>
<!-- SpringBoot Actuator -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<!-- https://mvnrepository.com/artifact/com.github.xiaoymin/knife4j-spring-boot-starter -->
<dependency>
<groupId>com.github.xiaoymin</groupId>
<artifactId>knife4j-spring-boot-starter</artifactId>
<version>2.0.9</version>
</dependency>
<!-- jwt -->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.1</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-data-redis -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
<version>2.3.12.RELEASE</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
<version>2.11.1</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.projectlombok/lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.22</version>
</dependency>
<!-- https://mvnrepository.com/artifact/cn.hutool/hutool-all -->
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.7.22</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<!-- springboot使用maven打包的插件 -->
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-assembly-plugin</artifactId>
<version>3.1.1</version>
<configuration>
<descriptors>
<descriptor>./src/main/resources/package/package.xml</descriptor>
</descriptors>
</configuration>
<executions>
<execution>
<id>make-assembly</id>
<phase>package</phase>
<goals>
<goal>single</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
<resources>
<resource>
<!-- 指定配置文件的位置 -->
<directory>src/main/resources</directory>
<includes>
<!-- 读取resources下的所有文件,include表示指定文件内的,相对的还有excludes ,排除其下的文件 -->
<include>**/*</include>
</includes>
<!-- 开启替换标签,比如我们的'@env'就是通过这个替换的 -->
<filtering>true</filtering>
</resource>
</resources>
</build>
<!--配置不同的profile,对应不同的生产环境-->
<profiles>
<profile>
<!--开发-->
<id>dev</id>
<activation>
<!--默认开发环境-->
<activeByDefault>true</activeByDefault>
</activation>
<properties>
<!-- 自定义的变量名称env作为标签,标签内是我们配置文件不同环境的后缀 -->
<env>dev</env>
</properties>
</profile>
<profile>
<!--生产-->
<id>pro</id>
<properties>
<env>pro</env>
</properties>
</profile>
<profile>
<!--测试-->
<id>test</id>
<properties>
<env>test</env>
</properties>
</profile>
</profiles>
</project>
三、总结
关于此次升级改造,遇到很多问题,也学习到了很多。但是目前工程还在不断地完善丰富,后面可能会因为本次的改造碰到更多的问题,会在此处进行更新的。
一点心得:现在的技术变化太大了,我们在有限的时间中,还是需要一些时间去了解这些变化,这样在遇到的时候才能得心应手。
本次改造主要是因为网关爆出的超危 bug,如果没有此次 bug,可能我也不会接触这些新版本的组件,总体来说也算是收获颇丰。