SpringCloudgateWay升级到3.1.1版本你遇到这些坑了吗?

11,663 阅读6分钟

「Offer 驾到,掘友接招!我正在参与2022春招系列活动-经验复盘,点击查看 活动详情

同学们,最近我在搭建一套个人网站,目前涉及到网关的服务搭建。《SpringCloudGateway爆漏洞,快看看你的服务中招没?》,所以我决定将我的网关服务直接升级到3.1.1版本,但是不升级不知道,坑是真多,此处记录一下遇到的坑,希望你们在遇到后有个参照依据。

项目源码地址:gitee.com/wei_rong_xi…

项目改造背景:juejin.cn/post/707224…

一、 项目简介

首先给大家看下我目前的整体项目构成:

image.png

  • 父级项目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版本,其内部的依赖有哪些变化:

image.png

如上所示,组要是两点:

  • 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仓库看其版本:

image.png

正常约在上面的版本,其内部的依赖应该版本越高,但是在这个依赖当中:

  • 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作为接口文档:

image.png

经过我半天的处理,发现仍然不能解决,最终得出的结论: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的服务注册

image.png

  • 网关访问接口文档

image.png

  • 网关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,可能我也不会接触这些新版本的组件,总体来说也算是收获颇丰。