【SpringCloud笔记】(8)服务网关之GateWay

news2025/1/13 10:00:29

GateWay

概述简介

官网地址:

上一代网关Zuul 1.x:https://github.com/Netflix/zuul/wiki(有兴趣可以了解一下)
gateway:https://cloud.spring.io/spring-cloud-static/spring-cloud-gateway/2.2.1.RELEASE/reference/html/

SpringCloud Gateway是Spring Cloud的一个全新项目,基于Spring 5.0+Spring Boot 2.0和Project Reactor等技术开发的网关,它旨在为微服务架构提供一种简单有效的统一的API路由管理方式。

SpringCloud Gateway作为Spring Cloud生态系统中的网关,目标是替代Zuul,在Spring Cloud 2.0以上版本中,没有对新版本的Zuul 2.0以上最新高性能版本进行集成,仍然还是使用的Zuul 1.x非Reactor模式的老版本。而为了提升网关的性能SpringCloud Gateway是基于WebFlux框架实现的,而WebFlux框架底层则使用了高性能的Reactor模式通信框架Netty

Spring Cloud Gateway的目标提供统一的路由方式且基于Filter链的方式提供了网关基本的功能,例如:安全,监控/指标,和限流。

一句话:SpringCloud Gateway使用的Webflux中的reactor-netty响应式编程组件,底层使用了Netty通讯框架。

在这里插入图片描述

为什么使用GateWay?

一方面因为Zuul1.0已经进入了维护阶段,而且Gateway是SpringCloud团队研发的,是亲儿子产品,值得信赖。而且很多功能Zuul都没有,用起来也非常的简单便捷。

Gateway是基于异步非阻塞模型上进行开发的,性能方面不需要担心。虽然Netflix早就发布了最新的Zuul 2.x,但Spring Cloud貌似没有整合计划。而且Netflix相关组件都宣布进入维护期,不知前景如何?

多方面综合考虑Gateway是很理想的网关选择。

Spring Cloud Gateway具有哪些特性?

  • 基于Spring Framework 5, Project Reactor和Spring Boot 2.0进行构建;

  • 动态路由:能够匹配任何请求属性;

  • 可以对路由指定Predicate (断言)和Filter (过滤器);

  • 集成Hystrix的断路器功能;

  • 集成Spring Cloud服务发现功能;

  • 易于编写的 Predicate (断言)和Filter (过滤器);

  • 请求限流功能;

  • 支持路径重写。

Spring Cloud Gateway 与Zuul的区别?

在SpringCloud Finchley正式版之前,Spring Cloud推荐的网关是Netflix提供的Zuul:

  • Zuul 1.x,是一个基于阻塞I/O的APlGateway

  • Zuul 1.x基于Servlet 2.5使用阻塞架构它不支持任何长连接(如WebSocket)Zuul的设计模式和Nginx较像,每次IО操作都是从工作线程中选择一个执行,请求线程被阻塞到工作线程完成,但是差别是Nginx用C++实现,Zuul用Java实现,而JVM本身会有第—次加载较慢的情况,使得Zuul的性能相对较差。

  • Zuul 2.x理念更先进,想基于Netty非阻塞和支持长连接,但SpringCloud目前还没有整合。Zuul 2.x的性能较Zuul 1.x有较大提升。在性能方面,根据官方提供的基准测试, Spring Cloud Gateway的RPS(每秒请求数)是Zuul的1.6倍。

  • Spring Cloud Gateway建立在Spring Framework 5,Project Reactor和Spring Boot2之上,使用非阻塞API

  • Spring Cloud Gateway还支持WebSocket,并且与Spring紧密集成拥有更好的开发体验

Zuul 1.x是springcloud中所集成的zuul版本,采用的是tomcat容器,使用的是传统的servlet IO处理模型。

servlet由servlet container进行生命周期管理

  • container启动时构造servlet对象并调用servlet init()进行初始化,
  • container运行时接受请求,并为每个请求分配一个线程(一般从线程池中获取空闲线程)然后调用service()
  • container关闭时调用servlet destory()销毁servlet
    在这里插入图片描述

上述模式的缺点:

servlete—个简单的网络IO模型,当请求进入servlet container时,servlet container就会为其绑定一个线程在并发不高的场景下这种模型是适用的。但是一旦高并发(比如抽风用jemeter压),线程数量就会上涨,而线程资源代价是昂贵的(上下文切换,内存消耗大)严重影响请求的处理时间。

在一些简单业务场景下,不希望为每个request分配一个线程,只需要1个或几个线程就能应对极大并发的请求,这种业务场景下servlet模型没有优势

所以Zuul 1.x是基于servlet之上的一个阻塞式处理模型,即spring实现了处理所有request请求的一个servlet(DispatcherServlet)并由该servlet阻塞式处理处理。所以springcloud zuul无法摆脱servlet模型的弊端。

传统的Web框架比如说:struts2,springmvc等都是基于Servlet API与Servlet容器基础之上运行的。

但是,在Servlet3.1之后有了异步非阻塞的支持。而WebFlux是一个典型非阻塞异步的框架,它的核心是基于Reactor的相关API实现的。相对于传统的web框架来说,它可以运行在诸如Netty,Undertow及支持Servlet3.1的容器上。非阻塞式+函数式编程(Spring5必须让你使用java8)

SpringWebFlux是Spring5.0引入的新的响应式框架区别于SpringMVC,它不需要依赖ServletAPI,它是完全异步非阻塞的,并且基于Reactor来实现响应式流规范。

三个核心概念

Route(路由):路由是构建网关的基本模块,它由ID,目标URI,一系列的断言和过滤器组成,如果断言为true则匹配该路由

Predicate(断言):参考的是Java8的java.util.function.Predicate
开发人员可以匹配HTTP请求中的所有内容(例如请求头或请求参数),如果请求与断言相匹配则进行路由

Filter(过滤):指的是Spring框架中GatewayFilter的实例,使用过滤器,可以在请求被路虫前或者之后对请求进行修改。
在这里插入图片描述

web请求,通过一些匹配条件,定位到真正的服务节点。并在这个转发过程的前后,进行一些精细化控制。
而filter,就可以理解为一个无所不能的拦截器。有了这两个元素,再加上目标uri,就可以实现一个具体的路由了

GateWay工作流程

在这里插入图片描述

客户端向Spring Cloud Gateway发出请求。然后在Gateway Handler Mapping 中找到与请求相匹配的路由,将其发送到Gateway Web Handler。

Handler再通过指定的过滤器链来将请求发送到我们实际的服务执行业务逻辑,然后返回。

过滤器之间用虚线分开是因为过滤器可能会在发送代理请求之前(“pre”)或之后(“post”)执行业务逻辑。

Filter在“pre”类型的过滤器可以做参数校验、权限校验、流量监控、日志输出、协议转换等,

在“post”类型的过滤器中可以做响应内容、响应头的修改,日志的输出,流量监控等有着非常重要的作用。

入门配置(通过网关访问8001)

新建cloud-gateway-gateway9527模块

pom文件
gateway网关不需要引入web及actuator依赖

<artifactId>cloud-gateway-gateway9527</artifactId>
    
    <dependencies>
        <!--gateway-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-gateway</artifactId>
        </dependency>
        <!--eureka-client-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        </dependency>
        <!-- 引入自己定义的api通用包,可以使用Payment支付Entity -->
        <dependency>
            <groupId>com.mzr.springcloud</groupId>
            <artifactId>cloud-api-commons</artifactId>
            <version>${project.version}</version>
        </dependency>
    </dependencies>

yml文件

server:
  port: 9527

spring:
  application:
    name: cloud-gateway

eureka:
  instance:
    hostname: cloud-gateway-service
  client: #服务提供者provider注册进eureka服务列表内
    service-url:
      register-with-eureka: true
      fetch-registry: true
      defaultZone: http://eureka7001.com:7001/eureka

主启动类

@SpringBootApplication
@EnableEurekaClient
public class GateWayMain9527
{
    public static void main(String[] args) {
            SpringApplication.run(GateWayMain9527.class, args);
    }
}

9527网关如何做路由映射?
我们目前不想暴露8001端口,希望在8001外面套一层9527。这样别人就攻击不了8001,有网关挡着
8001看看controller的访问地址

	@GetMapping(value = "/payment/get/{id}")
    public CommonResult getPaymentById(@PathVariable("id") Long id) {
        Payment payment = paymentService.getPaymentById(id);
        return ObjectUtils.isEmpty(payment) ? new CommonResult(444, "没有对应记录,查询ID: " + id + "serverPort" + serverPort, null) : new CommonResult(200, "查询成功,serverPort" + serverPort, payment);
    }
    
    @GetMapping(value = "/payment/lb")
    public String getPaymentLB()
    {
        return serverPort;
    }

在yml文件中添加网关信息

server:
  port: 9527

spring:
  application:
    name: cloud-gateway
  cloud:
    gateway:
      routes:
        - id: payment_routh                    #路由的ID,没有固定规则但要求唯一,建议配合服务名
          uri: http://localhost:8001          #匹配后提供服务的路由地址
          predicates:
            - Path=/payment/get/**           # 断言,路径相匹配的进行路由

        - id: payment_routh2                  #路由的ID,没有固定规则但要求唯一,建议配合服务名
          uri: http://localhost:8001          #匹配后提供服务的路由地址
          predicates:
            - Path=/payment/lb/**            # 断言,路径相匹配的进行路由
            
eureka:
  instance:
    hostname: cloud-gateway-service
  client: #服务提供者provider注册进eureka服务列表内
    service-url:
      register-with-eureka: true
      fetch-registry: true
      defaultZone: http://eureka7001.com:7001/eureka

启动eureka7001、payment8001、gateway9527微服务测试

添加网关前

在这里插入图片描述

添加网关后

在这里插入图片描述

访问说明

当访问 http://localhost:9527/payment/get/1 时,会通过http://localhost:9527匹配到http://localhost:8001,之后会根据/payment/get/1断言8001controller层是否存在/payment/get/**路径,如果存在返回true,反之返回false

在这里插入图片描述

gateway路由网关有两种配置方式:

  • 在配置文件yml中配置,见上边步骤
  • 代码中注入RouteLocator的Bean

使用第二种方式:通过9527网关访问到外网的百度新闻网址

@Configuration
public class GateWayConfig
{
    @Bean
    public RouteLocator customRouteLocator(RouteLocatorBuilder routeLocatorBuilder)
    {
        RouteLocatorBuilder.Builder routes = routeLocatorBuilder.routes();

        routes.route("path_route_mzr",
                r -> r.path("/guonei")
                        .uri("http://news.baidu.com/guonei")).build();

        return routes.build();
    }
}

通过微服务名称实现动态路由

以上案例存在一个问题,yml文件中routes信息uri是写死的,如果实现动态路由呢?

默认情况下Gateway会根据注册中心注册的服务列表,以注册中心上的微服务名为路径创建动态路由进行转发,从而实现动态路由的功能

启动一个eureka7001及两个服务提供者8001、8002,并修改9527模块yml文件

需要注意的是uri的协议为lb,表示启用Glteway的负载均衡功能。
lb://serviceName是spring cloudgateway在微服务中自动为我们创建的负载均衡uri

server:
  port: 9527

spring:
  application:
    name: cloud-gateway
  cloud:
    gateway:
      discovery:
        locator:
          enabled: true #开启从注册中心动态创建路由的功能,利用微服务名进行路由
      routes:
        - id: payment_routh #payment_route    #路由的ID,没有固定规则但要求唯一,建议配合服务名
          #uri: http://localhost:8001          #匹配后提供服务的路由地址
          uri: lb://CLOUD-PROVIDER-PAYMENT #匹配后提供服务的路由地址
          predicates:
            - Path=/payment/get/**         # 断言,路径相匹配的进行路由

        - id: payment_routh2 #payment_route    #路由的ID,没有固定规则但要求唯一,建议配合服务名
          #uri: http://localhost:8001          #匹配后提供服务的路由地址
          uri: lb://CLOUD-PROVIDER-PAYMENT #匹配后提供服务的路由地址
          predicates:
            - Path=/payment/lb/**         # 断言,路径相匹配的进行路由
          
eureka:
  instance:
    hostname: cloud-gateway-service
  client: #服务提供者provider注册进eureka服务列表内
    service-url:
      register-with-eureka: true
      fetch-registry: true
      defaultZone: http://eureka7001.com:7001/eureka

Predicate的使用

Spring Cloud Gateway将路由匹配作为Spring WebFlux HandlerMapping基础架构的一部分。

Spring Cloud Gateway创建Route对象时,使用RoutePredicateFactory 创建 Predicate对象,Predicate对象可以赋值给Route

Spring Cloud Gateway包含许多内置的Route Predicate Factories。所有这些谓词都匹配HTTP请求的不同属性多种谓词工厂可以组合,并通过逻辑and

什么意思呢:类似与我们写sql中where关键字后边所跟的多种条件并使用and关键字连接,条件越多,匹配到的内容越精准

常用的Route Predicate

  • After Route Predicate
  • Before Route Predicate
  • Between Route Predicate
  • Cookie Route Predicate
  • Header Route Predicate
  • Host Route Predicate
  • Method Route Predicate
  • Path Route PredicateZ
  • Query Route Predicate

1、After Before Between 使用演示

predicates:
            - Path=/payment/lb/** 
            - After=2020-02-21T15:51:37.485+08:00[Asia/Shanghai] # 会在这个时间之后这个路由才生效
           #- Before=2020-02-05T15:10:03.685+08:00[Asia/Shanghai]
		   #- Between=2020-02-02T17:45:06.206+08:00[Asia/Shanghai],2020-03-25T18:59:06.206+08:00[Asia/Shanghai]

2、Cookie 使用演示

predicates:
	- Path=/payment/lb/**
	- Cookie=username,mzr,ch.p # 需要有这个Cookie值才生效  最后一个是正则表达式
	# 使用curl测试
	# 不带cookie(curl http://localhost:9527/payment/lb)
	# 带cookie(curl http://localhost:9527/payment/lb --cookie “username=mzr”)

在这里插入图片描述
在这里插入图片描述

3、Header 演示

predicates:
    - Header=X-Request-Id, \d+

在这里插入图片描述

更多演示在官网地址:
https://docs.spring.io/spring-cloud-gateway/docs/2.2.5.RELEASE/reference/html/#configuring-route-predicate-factories-and-gateway-filter-factories

说白了, Predicate就是为了实现一组匹配规则,让请求过来找到对应的Route进行处理。

Filter的使用

路由过滤器可用于修改进入的HTTP请求和返回的HTTP响应,路由过滤器只能指定路由进行使用。
Spring Cloud Gateway内置了多种路由过滤器,他们都由GatewayFilter的工厂类来产生

按生命周期分类:

  • pre
  • post

按种类分类:

  • GatewayFilter:
    参考:https://docs.spring.io/spring-cloud-gateway/docs/2.2.5.RELEASE/reference/html/#gatewayfilter-factories
  • GlobalFilter:
    参考:https://docs.spring.io/spring-cloud-gateway/docs/2.2.5.RELEASE/reference/html/#global-filters

普通过滤器

使用方法:与Predicate类似

spring:
  cloud:
    gateway:
      routes:
      - id: payment_routh2
        uri: lb://CLOUD-PROVIDER-PAYMENT
        filters:
        - AddRequestHeader=X-Request-Id, 1024# 过滤器工厂会在匹配的请求头加上一对请求头,名称为X-Request-Id值为1024

拿AddRequestHeader举例,其他过滤器参考上述文档即可。重点学习自定义过滤器

自定义过滤器

能干嘛?

  • 全局日志记录
  • 统一网关鉴权

两个主要接口:implements GlobalFilter , ordered

使用案例:添加全局过滤器配置类

@Component
@Slf4j
public class MyLogGateWayFilter implements GlobalFilter,Ordered
{

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain)
    {
        log.info("***********come in MyLogGateWayFilter:  "+new Date());

        String uname = exchange.getRequest().getQueryParams().getFirst("uname");
		//合法性检验
        if(uname == null)
        {
            log.info("*******用户名为null,非法用户,o(╥﹏╥)o");
            //设置response状态码,因为在请求之前过滤的,就算是返回NOT_FOUND也不会返回错误页面
            exchange.getResponse().setStatusCode(HttpStatus.NOT_ACCEPTABLE);
            //完成请求调用
            return exchange.getResponse().setComplete();
        }
        //过滤链放行
        return chain.filter(exchange);
    }

    //加载过滤器顺序,数字越小优先级越高,全局配置过滤器所以返回值应该为最小
    @Override
    public int getOrder()
    {
        return 0;
    }
}

启动测试

url中没有带uname参数报错

在这里插入图片描述

url中带uname参数访问正常

在这里插入图片描述

搞定~

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1332128.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

STM32F407-14.3.10-表73具有有断路功能的互补通道OCx和OCxN的输出控制位-1x011

如上表所示&#xff0c;MOE1&#xff0c;OSSR0&#xff0c;CCxE1&#xff0c;CCxNE1时&#xff0c;OCx与OCxN对应端口的输出状态取决于OCx_REF与极性选择&#xff08;CCxP&#xff0c;CCxNP&#xff09; 死区。 ------------------------------------------------------------…

浅析海博深造

文章目录 深造作用 留学种类 选专业 择校 申请流程 申请方式 深造作用 1、个人能力提升&#xff08;学术专业、语言、新文化或新生活方式&#xff09; 2、更好的职业发展&#xff08;起点更高、结交新朋友或扩大社交圈&#xff09; 3、北京上海落户优惠 4、海外居留福…

【VB测绘程序设计】案例4——简单的四则运算练习Select Case语句的使用(附源码)

【VB测绘程序设计】案例4——简单的四则运算练习(附源码) 文章目录 前言一、界面预览二、程序介绍总结前言 在新手学习VB程序设计中,四则运算是基础,通过设计的TexT、按钮、label等控件,定义变量,实现简单程序的编写,提高对VB程序的入门训练。 一、界面预览 二、程序介…

openGauss学习笔记-171 openGauss 数据库运维-备份与恢复-导入数据-深层复制

文章目录 openGauss学习笔记-171 openGauss 数据库运维-备份与恢复-导入数据-深层复制171.1 使用CREATE TABLE执行深层复制171.1.1 操作步骤 171.2 使用CREATE TABLE LIKE执行深层复制171.2.1 操作步骤 171.3 通过创建临时表并截断原始表来执行深层复制171.3.1 操作步骤 openGa…

「Verilog学习笔记」并串转换

专栏前言 本专栏的内容主要是记录本人学习Verilog过程中的一些知识点&#xff0c;刷题网站用的是牛客网 串并转换操作是非常灵活的操作&#xff0c;核心思想就是移位。串转并就是把1位的输入放到N位reg的最低位&#xff0c;然后N位reg左移一位&#xff0c;在把1位输入放到左移后…

用C求斐波那契数列-----(C每日一编程)

斐波那契数列: 斐波那契数列是指这样一个数列&#xff1a;1&#xff0c;1&#xff0c;2&#xff0c;3&#xff0c;5&#xff0c;8&#xff0c;13&#xff0c;21&#xff0c;34&#xff0c;55&#xff0c;89……这个数列从第3项开始 &#xff0c;每一项都等于前两项之和。 递推…

MFC 视图窗口

目录 视图窗口概述 视图窗口的使用 视图窗口创建流程 命令消息 WM_COMMAND 处理顺序 对象关系 视图窗口概述 作用&#xff1a;提供了一个用于显示数据的窗口 关于视图窗口 视图类是用来展示用户&#xff0c;文档类是用来存储和管理数据视图窗口是覆盖掉框架窗口的客户区…

【错误记录/js】保存octet-stream为文件后数据错乱

目录 说在前面场景解决方式其他 说在前面 后端&#xff1a;go、gin浏览器&#xff1a;Microsoft Edge 120.0.2210.77 (正式版本) (64 位) 场景 前端通过点击按钮来下载一些文件&#xff0c;但是文件内容是一些非文件形式存储的二进制数据。 后端代码 r : gin.Default()r.Stat…

算法学习——动态规划

动态规划 什么是动态规划动态规划的解题步骤动态规划应该如何debug 斐波那契数思路确定dp数组以及下标的含义确定递推公式dp数组如何初始化确定遍历顺序举例推导dp数组 代码 爬楼梯思路代码 使用最小花费爬楼梯思路代码 不同路径思路代码 不同路径 II思路代码 整数拆分思路代码…

【机器学习】决策树

参考课程视频&#xff1a;https://www.icourse163.org/course/NEU-1462101162?tid1471214452 1 概述 样子&#xff1a; 2 分裂 2.1 分裂原则 信息增益 信息增益比 基尼指数 3 终止 & 剪枝 3.1 终止条件 无需分裂 当前节点内样本同属一类 无法分裂 当前节点内…

vue3+element plus组件库中el-carousel组件走马灯特效,当图片变动时下面数字也随着图片动态变化

1.效果图 2.html <section style"height:30%"><div class"left-img1-title"><img src"../assets/img/title.png"alt""srcset""><div class"text">回收垃圾数量</div></div>…

Linux操作系统基础(二)系统的基础设置

结合上一节内容&#xff0c;这一节就对一些常见的linux操作设置进行讲解&#xff0c;内容有限&#xff0c;只做引导和抛砖引玉。 一、输入法的安装和设置 Linux 中安装中文输入法的方法有很多&#xff0c;常用的有以下几种&#xff1a; 使用软件包管理器安装 大多数 Linux …

NFC读卡------ci522

1、NFC及卡片 NFC是近距离无线通讯技术&#xff0c;是一种非接触式识别和互联技术&#xff0c;可以在移动设备、消费类电子产品、PC和智能控件工具间进行近距离无线通信。NFC提供了一种简单、触控式的解决方案&#xff0c;可以让消费者简单直观地交换信息、访问内容与服务。 …

Multi-Drone based Single Object Tracking with Agent Sharing Network阅读笔记

Multi-Drone based Single Object Tracking with Agent Sharing Network阅读笔记 Abstract 搭载摄像头的无人机可以从更广阔的视角在空中动态跟踪目标&#xff0c;与静态摄像头或地面移动传感器相比具有优势。然而&#xff0c;由于外观变化和严重遮挡等多种因素&#xff0c;使…

基于电商场景的高并发RocketMQ实战-Broker高并发消息写入、读写队列原理分析

&#x1f308;&#x1f308;&#x1f308;&#x1f308;&#x1f308;&#x1f308;&#x1f308;&#x1f308; 【11来了】文章导读地址&#xff1a;点击查看文章导读&#xff01; &#x1f341;&#x1f341;&#x1f341;&#x1f341;&#x1f341;&#x1f341;&#x1f3…

Java研学-Servlet 基础

一 概述 1 介绍 Servlet&#xff08;Server Applet&#xff09;是Java Servlet的简称&#xff0c;称为小服务程序或服务连接器&#xff0c;用Java编写的服务器端程序&#xff0c;具有独立于平台和协议的特性&#xff0c;主要功能在于交互式地浏览和生成数据&#xff0c;生成动…

SpringMVC:SSM(Spring+SpringMVC+MyBatis)代码整理

文章目录 SpringMVC - 07SSM 框架代码整理一、准备工作1. 分析需求、准备数据库2. 新建一个项目&#xff0c;导入依赖&#xff1a;pom.xml3. 用 IDEA 连接数据库 二、MyBatis 层1. 外部配置文件&#xff1a;db.properties2. MyBatis 核心配置文件&#xff1a;mybatis-config.xm…

超级量化第10期私募大咖——线上分享总结

《掘金之心公众号&#xff1a;gnu_isnot_unix》前Citadel现自营交易与量化管理&#xff0c;分享热点&#xff0c;主观&#xff0c;量化交易内容。活在当下&#xff0c;终身学习 - 给在职却对未来始终迷茫的人的公众号。借此想告诉不断努力&#xff0c;对生活充满热情的读者们&a…

ARM 汇编语言知识积累

博文参考&#xff1a; arm中SP&#xff0c;LR&#xff0c;PC寄存器以及其它所有寄存器以及处理器运行模式介绍 arm平台根据栈进行backtrace的方法-腾讯云开发者社区-腾讯云 (tencent.com) 特殊功能寄存器&#xff1a; SP&#xff1a; 即 R13&#xff0c;栈指针&#xff0c;…

并发踩坑:list共享变量的addAll

背景&#xff1a; 某业务报错了&#xff0c;提示&#xff1a;Caused by: org.apache.ibatis.exceptions.PersistenceException: ### Error querying database. Cause: java.util.ConcurrentModificationException 分析&#xff1a; 这是执行查询时报的 并发修改异常。大概逻辑…