详解springcloud gateway工作原理、断言、filter、uri、id、全局跨域、globalfilter等以及关键源码实现

1.gateway概念
  1. 网关就是当前微服务项目的"统一入口"
  2. 程序中的网关就是当前微服务项目对外界开放的统一入口
  3. 所有外界的请求都需要先经过网关才能访问到我们的程序
  4. 提供了统一入口之后,方便对所有请求进行统一的检查和管理
2. 网关的主要功能
  • 将所有请求统一经过网关
  • 网关可以对这些请求进行检查
  • 网关方便记录所有请求的日志
  • 网关可以统一将所有请求路由到正确的模块\服务上

“路由"的近义词就是"分配”
在这里插入图片描述

3. 工作原理

在这里插入图片描述
在这里插入图片描述
Spring Gateway的工作原理基于路由、断言(Predicates)和过滤器(Filters)三大核心概念:

  • ‌路由(Route):定义了请求的转发规则,包括目标URL和匹配条件。
  • 断言(Predicates):用于匹配HTTP请求的各种条件,如路径、头信息、参数等。只有匹配成功的请求才会被路由处理。
  • 过滤器(Filters):在请求处理前后执行特定的逻辑,例如权限校验、日志记录
4. 配置和使用示例
spring:cloud:gateway:routes:- id: myrouteuri: http://example.compredicates:- Path=/api/**filters:- AddRequestHeader=X-Request-ID, \${requestId}

这个配置定义了一个路由,所有路径以/api/开头的请求都会被转发到http://example.com,并且在请求头中添加一个X-Request-ID字段‌

springcloud gateway中配置uri有3种方式:

  • ws(websocket)方式: uri: ws://localhost:9000
  • http方式: uri: http://localhost:8130/
  • lb(注册中心中服务名字)方式: uri: lb://brilliance-consumer

springcloud gatetway命名规范

能被gateway的lb方式识别到的命名规则为:

   "[a-zA-Z]([a-zA-Z]|\\d|\\+|\\.|-)*:.*"

如果名字中有非*“a-zA-Z:.”*规则字符或者使用“_”,则会报错

5.网关gateway routes的组成

1. id:必须唯一

2. predicates(断言)
在这里插入图片描述
关键类源码分析:

package org.springframework.cloud.gateway.handler.predicate;import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.function.Predicate;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.core.style.ToStringCreator;
import org.springframework.http.server.PathContainer;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.util.pattern.PathPattern;
import org.springframework.web.util.pattern.PathPattern.PathMatchInfo;
import org.springframework.web.util.pattern.PathPatternParser;
import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.GATEWAY_PREDICATE_MATCHED_PATH_ATTR;
import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.GATEWAY_PREDICATE_MATCHED_PATH_ROUTE_ID_ATTR;
import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.GATEWAY_PREDICATE_PATH_CONTAINER_ATTR;
import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.GATEWAY_PREDICATE_ROUTE_ATTR;
import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.putUriTemplateVariables;
import static org.springframework.http.server.PathContainer.parsePath;/*** @author Spencer Gibb* @author Dhawal Kapil*/
public class PathRoutePredicateFactory extends AbstractRoutePredicateFactory<PathRoutePredicateFactory.Config> {private static final Log log = LogFactory.getLog(PathRoutePredicateFactory.class);private static final String MATCH_TRAILING_SLASH = "matchTrailingSlash";private PathPatternParser pathPatternParser = new PathPatternParser();public PathRoutePredicateFactory() {super(Config.class);}private static void traceMatch(String prefix, Object desired, Object actual, boolean match) {if (log.isTraceEnabled()) {String message = String.format("%s \"%s\" %s against value \"%s\"", prefix, desired,match ? "matches" : "does not match", actual);log.trace(message);}}public void setPathPatternParser(PathPatternParser pathPatternParser) {this.pathPatternParser = pathPatternParser;}@Overridepublic List<String> shortcutFieldOrder() {return Arrays.asList("patterns", MATCH_TRAILING_SLASH);}@Overridepublic ShortcutType shortcutType() {return ShortcutType.GATHER_LIST_TAIL_FLAG;}@Overridepublic Predicate<ServerWebExchange> apply(Config config) {final ArrayList<PathPattern> pathPatterns = new ArrayList<>();synchronized (this.pathPatternParser) {pathPatternParser.setMatchOptionalTrailingSeparator(config.isMatchTrailingSlash());config.getPatterns().forEach(pattern -> {PathPattern pathPattern = this.pathPatternParser.parse(pattern);pathPatterns.add(pathPattern);});}return new GatewayPredicate() {@Overridepublic boolean test(ServerWebExchange exchange) {PathContainer path = (PathContainer) exchange.getAttributes().computeIfAbsent(GATEWAY_PREDICATE_PATH_CONTAINER_ATTR,s -> parsePath(exchange.getRequest().getURI().getRawPath()));PathPattern match = null;for (int i = 0; i < pathPatterns.size(); i++) {PathPattern pathPattern = pathPatterns.get(i);if (pathPattern.matches(path)) {match = pathPattern;break;}}if (match != null) {traceMatch("Pattern", match.getPatternString(), path, true);PathMatchInfo pathMatchInfo = match.matchAndExtract(path);putUriTemplateVariables(exchange, pathMatchInfo.getUriVariables());exchange.getAttributes().put(GATEWAY_PREDICATE_MATCHED_PATH_ATTR, match.getPatternString());String routeId = (String) exchange.getAttributes().get(GATEWAY_PREDICATE_ROUTE_ATTR);if (routeId != null) {// populated in RoutePredicateHandlerMappingexchange.getAttributes().put(GATEWAY_PREDICATE_MATCHED_PATH_ROUTE_ID_ATTR, routeId);}return true;}else {traceMatch("Pattern", config.getPatterns(), path, false);return false;}}@Overridepublic Object getConfig() {return config;}@Overridepublic String toString() {return String.format("Paths: %s, match trailing slash: %b", config.getPatterns(),config.isMatchTrailingSlash());}};}@Validatedpublic static class Config {private List<String> patterns = new ArrayList<>();private boolean matchTrailingSlash = true;public List<String> getPatterns() {return patterns;}public Config setPatterns(List<String> patterns) {this.patterns = patterns;return this;}/*** @deprecated use {@link #isMatchTrailingSlash()}*/@Deprecatedpublic boolean isMatchOptionalTrailingSeparator() {return isMatchTrailingSlash();}/*** @deprecated use {@link #setMatchTrailingSlash(boolean)}*/@Deprecatedpublic Config setMatchOptionalTrailingSeparator(boolean matchOptionalTrailingSeparator) {setMatchTrailingSlash(matchOptionalTrailingSeparator);return this;}public boolean isMatchTrailingSlash() {return matchTrailingSlash;}public Config setMatchTrailingSlash(boolean matchTrailingSlash) {this.matchTrailingSlash = matchTrailingSlash;return this;}@Overridepublic String toString() {return new ToStringCreator(this).append("patterns", patterns).append(MATCH_TRAILING_SLASH, matchTrailingSlash).toString();}}}
package org.springframework.cloud.gateway.handler.predicate;import java.util.function.Consumer;
import java.util.function.Predicate;
import org.springframework.cloud.gateway.handler.AsyncPredicate;
import org.springframework.cloud.gateway.support.Configurable;
import org.springframework.cloud.gateway.support.NameUtils;
import org.springframework.cloud.gateway.support.ShortcutConfigurable;
import org.springframework.web.server.ServerWebExchange;
import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.toAsyncPredicate;/*** @author Spencer Gibb*/
@FunctionalInterface
public interface RoutePredicateFactory<C> extends ShortcutConfigurable, Configurable<C> {/*** Pattern key.*/String PATTERN_KEY = "pattern";// useful for javadsldefault Predicate<ServerWebExchange> apply(Consumer<C> consumer) {C config = newConfig();consumer.accept(config);beforeApply(config);return apply(config);}default AsyncPredicate<ServerWebExchange> applyAsync(Consumer<C> consumer) {C config = newConfig();consumer.accept(config);beforeApply(config);return applyAsync(config);}default Class<C> getConfigClass() {throw new UnsupportedOperationException("getConfigClass() not implemented");}@Overridedefault C newConfig() {throw new UnsupportedOperationException("newConfig() not implemented");}default void beforeApply(C config) {}Predicate<ServerWebExchange> apply(C config);default AsyncPredicate<ServerWebExchange> applyAsync(C config) {return toAsyncPredicate(apply(config));}default String name() {return NameUtils.normalizeRoutePredicateName(getClass());}}

自定义Vip路由断言工厂实现

package com.wemedia.gateway.config;import jakarta.validation.constraints.NotEmpty;
import org.springframework.cloud.gateway.handler.predicate.AbstractRoutePredicateFactory;
import org.springframework.cloud.gateway.handler.predicate.CookieRoutePredicateFactory;
import org.springframework.cloud.gateway.handler.predicate.GatewayPredicate;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.server.ServerWebExchange;import java.util.Arrays;
import java.util.List;
import java.util.function.Predicate;/*** 自定义Vip路由断言工厂*/
@Component
public class VipRoutePredicateFactory extends AbstractRoutePredicateFactory<VipRoutePredicateFactory.Config> {public VipRoutePredicateFactory(){super(Config.class);}@Overridepublic List<String> shortcutFieldOrder() {return Arrays.asList("param","value");}@Overridepublic Predicate<ServerWebExchange> apply(Config config) {return new GatewayPredicate() {@Overridepublic boolean test(ServerWebExchange serverWebExchange) {//localhost/search?q=hhh&user=jackmaServerHttpRequest request = serverWebExchange.getRequest();String first = request.getQueryParams().getFirst(config.param);return StringUtils.hasText(first)&&first.equals(config.value);}};}@Validatedpublic static class Config {@NotEmptyprivate String value;@NotEmptyprivate String param;public String getParam() {return param;}public void setParam(String param) {this.param = param;}public String getValue() {return value;}public void setValue(String value) {this.value = value;}}
}在这里插入代码片

配置Vip断言
在这里插入图片描述
3. Filter(过滤器)
在这里插入图片描述
3.1 rewritePath(路径重写)
在这里插入图片描述
在这里插入图片描述

  • 添加RewritePath过滤器,重写原先路径/readDb,在访问路径前面追加/api/order/readDb,不然网关无法直接访问/readDb;
  • 添加AddReponseHeader过滤器,给响应头增加参数X-Response-ABC,值为123

3.2 默认过滤器filter:
在这里插入图片描述
增加默认过滤器default-filters, 参数Add-ReponseHeader=X-Reponse-Abc ,值为123 给所有服务的相应头中

3.3 全局过滤器GlobalFilter

package com.wemedia.gateway.filter;import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;/*** 实现响应时间全局过滤器*/
@Component
@Slf4j
public class RTGlobalFilter implements GlobalFilter, Ordered {@Overridepublic Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {ServerHttpRequest request = exchange.getRequest();ServerHttpResponse response = exchange.getResponse();String uri = request.getURI().toString();long start=System.currentTimeMillis();log.info("请求【{}】开始时间:{}",uri,start );//================以上是前置逻辑==============Mono<Void> filter = chain.filter(exchange).doFinally((result) -> {//================后置逻辑long end = System.currentTimeMillis();log.info("请求【{}】结束时间:{},耗时:{}ms", uri, end, end - start);});return filter;}@Overridepublic int getOrder() {return 0;}
}

过滤器filter 列表:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
3.4 自定义过滤器工厂

关键源码分析

package org.springframework.cloud.gateway.filter.factory;
import reactor.core.publisher.Mono;
import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.support.ServerWebExchangeUtils;
import org.springframework.http.HttpHeaders;
import org.springframework.web.server.ServerWebExchange;import static org.springframework.cloud.gateway.support.GatewayToStringStyler.filterToStringCreator;/*** @author Spencer Gibb*/
public class AddResponseHeaderGatewayFilterFactory extends AbstractNameValueGatewayFilterFactory {@Overridepublic GatewayFilter apply(NameValueConfig config) {return new GatewayFilter() {@Overridepublic Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {return chain.filter(exchange).then(Mono.fromRunnable(() -> addHeader(exchange, config)));}@Overridepublic String toString() {return filterToStringCreator(AddResponseHeaderGatewayFilterFactory.this).append(config.getName(), config.getValue()).toString();}};}void addHeader(ServerWebExchange exchange, NameValueConfig config) {final String value = ServerWebExchangeUtils.expand(exchange, config.getValue());HttpHeaders headers = exchange.getResponse().getHeaders();// if response has been commited, no more response headers will bee added.if (!exchange.getResponse().isCommitted()) {headers.add(config.getName(), value);}}}

1.实现一次性token自定义过滤器工厂

package com.wemedia.gateway.filter;import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.factory.AbstractNameValueGatewayFilterFactory;
import org.springframework.http.HttpHeaders;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
import java.util.UUID;/*** 实现一次性令牌的自定义过滤器工场*/
@Component
@Slf4j
public class OnceTokenGatewayFilterFactory extends AbstractNameValueGatewayFilterFactory {@Overridepublic GatewayFilter apply(NameValueConfig config) {return new GatewayFilter() {@Overridepublic Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {//每次响应之前,添加一个一次性令牌,支持uuid,jwt等格式return chain.filter(exchange).then(Mono.fromRunnable(()->{ServerHttpResponse response = exchange.getResponse();HttpHeaders headers = response.getHeaders();String value = config.getValue();if("uuid".equalsIgnoreCase(value)){value = UUID.randomUUID().toString();}if("jwt".equalsIgnoreCase(value)){value="";}headers.add(config.getName(),value);}));}};}
}

2.配置一次性token过滤器
在这里插入图片描述
3.访问api响应结果如下:
在这里插入图片描述
3.5 实现全局跨域
在这里插入图片描述

  • 解决单机跨域方法:直接在每个controller上增加注解@CrossOrigin

在这里插入图片描述

  • 在分布式系统上解决跨域问题,在gateway上统一处理跨域问题
    配置全局跨域:
    在这里插入图片描述
    运行结果如下,增加了跨域处理:
    在这里插入图片描述

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

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

相关文章

C#中的弱引用使用

弱引用&#xff08;Weak Reference&#xff09;是一种特殊的引用类型&#xff0c;它允许你引用一个对象&#xff0c;但不会阻止该对象被垃圾回收器&#xff08;GC&#xff09;回收。弱引用通常用于需要缓存或跟踪对象&#xff0c;但又不希望因保留引用而导致内存泄漏的场景。弱…

spring响应式编程系列:异步生产数据

目录 示例 大致流程 create new MonoCreate subscribe new LambdaMonoSubscriber monoCreate.subscribe accept success onNext 时序图 类图 数据发布者 MonoCreate 数据订阅者 LambdaMonoSubscriber 订阅的消息体 DefaultMonoSink 本篇文章我们来研究如何将…

MCP Python SDK构建的**SQLite浏览器**的完整操作指南

以下是使用MCP Python SDK构建的SQLite浏览器的完整操作指南&#xff1a; 一、环境准备 安装依赖 # 安装MCP SDK及SQLite支持 pip install mcp sqlite3创建测试数据库 sqlite3 test.db <<EOF CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT, email TEXT); IN…

【Python爬虫基础篇】--3.cookie和session

目录 1.cookie 1.1.定义 1.2.参数 1.3.分类 2.session 3.使用cookie登录微博 4.使用session登录 1.cookie 由于http是一个无状态的协议&#xff0c;请求与请求之间无法相互传递或者记录一些信息&#xff0c;cookie和session正是为了解决这个问题而产生。 例子&#xff1…

风车邮箱系统详细使用指南:Windows与Ubuntu双平台解析

风车邮箱系统V1.2使用手册 风车邮箱系统详细使用指南&#xff1a;Windows与Ubuntu双平台解析 前言 在日常网络活动中&#xff0c;我们经常需要一个临时邮箱来注册各类网站或接收验证码&#xff0c;但不想使用自己的真实邮箱。「风车无线邮箱系统」作为一款优秀的临时邮箱工具…

同样的接口用postman/apifox能跑通,用jmeter跑就报错500

之前没用过jmeter,第一次用调试压测脚本遇到了问题 一样的接口用postman能跑通&#xff0c;用jmeter跑就报错500&#xff0c;百度很多文章都说是该接口需要加一个‘内容编码’改成utf-8,我加了还是不行 后来我就想到apifox好像有隐藏的header&#xff0c;然后开始比较apifox的…

1656打印路径-Floyd回溯/图论-链表/数据结构

蓝桥账户中心 1.税收&#xff1a; “城市的税收”&#xff1a;所以是中介点的税收&#xff0c;经过该点后加上 2.路径&#xff1a; 用数组存储前驱节点从而串成链表 pre[ i ][ j ]代表的是从 i 到 j 的最短路径上 j 的前驱节点是什么 那么便可以pre[ i ][ j ]k 把k加入pa…

Eigen矩阵操作类 (Map, Block, 视图类)

1. Map 类&#xff1a;内存映射&#xff08;零拷贝操作&#xff09; 核心功能 将现有的 C/C 数组或缓冲区映射为 Eigen 矩阵/向量&#xff0c;不复制数据&#xff0c;直接操作原内存。 模板参数 cpp Map<Matrix<Scalar, Rows, Cols, Options, MaxRows, MaxCols>&…

多系统安装经验,移动硬盘,ubuntu grub修改/etc/fstab 移动硬盘需要改成nfts格式才能放steam游戏

总结&#xff1a;我硬盘会自动挂载&#xff0c;直接格式化nfts&#xff0c;steam就能装里面了 机械硬盘装系统真的不行&#xff0c;超级慢游戏还跑不了 --------------------------------------------------------------------底下都不用看 笔记本一个系统&#xff0c;移动硬盘…

JFLAP SOFTWARE 编译原理用(自动机绘图)

csdn全是蛆虫&#xff0c;2mb的软件&#xff0c;都在那里搞收费&#xff0c;我就看不惯&#xff0c;我就放出来&#xff0c;那咋了&#xff01;&#xff01;&#xff01; https://pan.baidu.com/s/1IuEfHScynjCCUF5ScF26KA 通过网盘分享的文件&#xff1a;JFLAP7.1.jar 链接: h…

[Windows] Disk Sorter文件分类管理软件 v16.7.18

[Windows] Disk Sorter文件分类管理 链接&#xff1a;https://pan.xunlei.com/s/VOOl0sDntAdHvlMkc7N0ZOD-A1?pwd966n# Disk Sorter是一个功能强大的文件分类管理软件&#xff0c;允许对本地磁盘、网络共享、NAS设备和企业存储系统中的文件进行分类&#xff0c;并且支持生成…

STM32提高篇: 蓝牙通讯

STM32提高篇: 蓝牙通讯 一.蓝牙通讯介绍1.蓝牙技术类型 二.蓝牙协议栈1.蓝牙芯片架构2.BLE低功耗蓝牙协议栈框架 三.ESP32-C3中的蓝牙功能1.广播2.扫描3.通讯 四.发送和接收 一.蓝牙通讯介绍 蓝牙&#xff0c;是一种利用低功率无线电&#xff0c;支持设备短距离通信的无线电技…

6.1.多级缓存架构

目录 一、多级缓存基础与核心概念 缓存的定义与价值 • 缓存的应用场景&#xff08;高并发、低延迟、减轻数据库压力&#xff09; • 多级缓存 vs 单级缓存的优劣对比 多级缓存核心组件 • 本地缓存&#xff08;Caffeine、Guava Cache&#xff09; • 分布式缓存&#xff08;…

MySQL的MVCC【学习笔记】

MVCC 事务的隔离级别分为四种&#xff0c;其中Read Committed和Repeatable Read隔离级别&#xff0c;部分实现就是通过MVCC&#xff08;Multi-Version Concurrency Control&#xff0c;多版本并发控制&#xff09; 版本链 版本链是通过undo日志实现的&#xff0c; 事务每次修改…

基于OpenMV+STM32+OLED与YOLOv11+PaddleOCR的嵌入式车牌识别系统开发笔记

基于OpenMV、STM32与OLED的嵌入式车牌识别系统开发笔记 基于OpenMV、STM32与OLED的嵌入式车牌识别系统开发笔记系统架构全景 一、实物演示二、OpenMV端设计要点1. 硬件配置优化2. 智能帧率控制算法3. 数据传输协议设计 三、PyTorch后端核心实现&#xff1a;YOLOv11与PaddleOCR的…

C#中常见的设计模式

文章目录 引言设计模式的分类创建型模式 (Creational Patterns)1. 单例模式 (Singleton)2. 工厂方法模式 (Factory Method)3. 抽象工厂模式 (Abstract Factory)4. 建造者模式 (Builder) 结构型模式 (Structural Patterns)5. 适配器模式 (Adapter)6. 装饰器模式 (Decorator)7. 外…

Nacos简介—3.Nacos的配置简介

大纲 1.Nacos生产集群Web端口与数据库配置 2.Nacos生产集群的Distro协议核心参数 3.Nacos打通CMDB实现跨机房的就近访问 4.Nacos基于SPI动态扩展机制来获取CMDB的数据 5.基于Nacos SPI机制开发CMDB动态扩展 6.Nacos基于CMDB来实现多机房就近访问 7.Nacos生产集群Prometh…

Jest 快照测试

以下是关于 Jest 快照测试的系统化知识总结,从基础使用到底层原理全面覆盖: 一、快照测试核心原理 1. 工作机制三阶段 #mermaid-svg-GC46t2NBvGv7RF0M {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-GC46t2NBvGv…

第十六届蓝桥杯大赛软件赛省赛 C/C++ 大学B组 [京津冀]

由于官方没有公布题目的数据, 所以代码仅供参考 1. 密密摆放 题目链接&#xff1a;P12337 [蓝桥杯 2025 省 AB/Python B 第二场] 密密摆放 - 洛谷 题目描述 小蓝有一个大箱子&#xff0c;内部的长宽高分别是 200、250、240&#xff08;单位&#xff1a;毫米&#xff09;&…

Spring 学习笔记之 @Transactional 异常不回滚汇总

使用springboot时&#xff0c;只要引入spring-jdbc/jpa相关的依赖后&#xff0c;在想要启用事务的方法上加上Transactional注解就能开启事务&#xff0c;碰到异常就能自动回滚。大大的提高了编码的便捷性性&#xff0c;同时也不侵入代码&#xff0c;保持了代码的简洁性。 默认情…