聊聊Spring事务同步器TransactionSynchronization

news2024/10/7 0:04:54

在一些业务场景中可能我们需要去对某一个spring事务的生命周期进行监控,比如在这个事务提交,回滚,被挂起的时候,我们想要去执行一些自定义的操作,这怎么去做呢?其实spring作为一个高扩展性的框架,早就提供好了这一个扩展点,这个扩展点就是事务同步器TransactionSynchronization

使用方式 

public interface TransactionSynchronization extends Flushable {
    
   int STATUS_COMMITTED = 0;

   int STATUS_ROLLED_BACK = 1;
   
   int STATUS_UNKNOWN = 2;

   default void suspend() {
   }

   default void resume() {
   }

   @Override
   default void flush() {
   }
   
   default void beforeCommit(boolean readOnly) {
   }

   default void beforeCompletion() {
   }

   default void afterCommit() {
   }

   default void afterCompletion(int status) {
   }

}

可以看到,TransactionSynchronization是一个接口,它里面定义了一系列与事务各生命周期阶段相关的方法。比如,我们可以这样使用: 

@Transactional(rollbackFor = Exception.class)
public void saveUser(User user) {
    TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() {
        @Override
        public void afterCommit() {
            System.out.println("saveUser事务已提交...");
        }
    });
    userDao.saveUser(user);
}

在spring事务刚开始的时候,我们通过TransactionSynchronizationManager事务同步管理器去注册一个事务同步器,当spring事务提交完之后,就会去回调当前这个spring事务所注册的所有事务同步器(一个spring事务可以注册多个事务同步器)的afterCommit方法。需要注意的是注册事务同步器必须得在一个spring事务中才能注册,否则会抛出Transaction synchronization is not active这个错误

为何事务同步器必须要在spring事务中才能注册?

 具体原因我们可以通过源码中找到:

public static void registerSynchronization(TransactionSynchronization synchronization)
      throws IllegalStateException {

   Assert.notNull(synchronization, "TransactionSynchronization must not be null");
   Set<TransactionSynchronization> synchs = synchronizations.get();
   if (synchs == null) {
      throw new IllegalStateException("Transaction synchronization is not active");
   }
   synchs.add(synchronization);
}

 可以看到如果synchronizations.get()返回的是null,那么就会抛出这个错误,而什么时候这里才不会返回null呢?我们可以去看spring事务创建的时候,代码如下:

 

 每一次创建一个新的spring事务的时候都会去调用startTransaction方法,而在startTransaction方法中会调用prepareSynchronization方法,这个方法中做的事情主要就是去把新创建的spring事务的一些信息放到线程上下文中(在这个spring事务执行期间我们都可以通过事务同步管理器去拿到这些信息)。最后最关键的就是调用了initSynchronization方法,在这个方法中我们就看到了此时会初始化一个空集合放到synchronizations中,所以当执行spring事务中的业务代码的时候,此时由于synchronizations已经不为空了,所以我们就可以成功地把事务同步器注册到事务同步器管理器中了

事务同步器在多个事务之间如何切换? 

事务同步器只对注册它的那个spring事务生效,如果这个spring事务中存在嵌套的spring事务,那么事务同步器就不会对嵌套的那个spring事务生效了。这可能有点难理解,我们可以直接上代码去方便理解: 

上面的代码就是saveUser方法会去调用saveUser2方法,其中saveUser2方法的事务传播级别是REQUIRES_NEW,也就是saveUser和saveUser2这两个方法会处于两个不同的事务中,重点是saveUser方法还往事务同步管理器中注册了一个事务同步器去监听事务提交阶段。那么当调用saveUser方法的时候,由于会创建两个事务,此时会不会回调两次事务同步器? 

可以发现事务同步器的afterCommit方法只回调了一次。那么要想回调两次怎么办?答案:在saveUser2方法中也去注册一个事务同步器: 

 执行结果如下:

可以看到只要我们在saveUser2方法中也去注册一个事务同步器,那么当saveUser2的事务提交的时候,就能执行到afterCommit方法了。那么为什么会这样呢?下面我们深入源码去探究:

org.springframework.transaction.support.AbstractPlatformTransactionManager#handleExistingTransaction

private TransactionStatus handleExistingTransaction(
    TransactionDefinition definition, Object transaction, boolean debugEnabled)
throws TransactionException {

        // .............

        // 条件成立:该事务方法所声明的事务传播级别等于PROPAGATION_REQUIRES_NEW,该级别表示需要开启一个新的事务
        if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_REQUIRES_NEW) {
            if (debugEnabled) {
                logger.debug("Suspending current transaction, creating new transaction with name [" +
                        definition.getName() + "]");
            }

            // 首先需要把原来的事务挂起,这里的挂起其实就是把ThreadLocal中与当前线程上下文绑定的资源对象进行解绑(remove),
            // 而返回的SuspendedResourcesHolder对象中缓存了原来事务的资源对象
            SuspendedResourcesHolder suspendedResources = suspend(transaction);
            try {
                // 创建一个新的事务
                return startTransaction(definition, transaction, debugEnabled, suspendedResources);
            } catch (RuntimeException | Error beginEx) {
                resumeAfterBeginException(transaction, suspendedResources, beginEx);
                throw beginEx;
            }
        }

    // ...............
}

当当前线程上下文中存在事务的时候,就会执行handleExistingTransaction方法,在handleExistingTransaction方法中会去判断处理不同的事务传播级别,我们这里以PROPAGATION_REQUIRES_NEW为例子,此时会调用suspend方法,并返回一个挂起的资源对象,我们进去suspend方法看看: 

protected final SuspendedResourcesHolder suspend(@Nullable Object transaction) throws TransactionException {
// 条件成立:说明当前线程上下文中已经存在事务
if (TransactionSynchronizationManager.isSynchronizationActive()) {
    // 回调线程上下文中所有的事务同步器,并执行其suspend方法,然后再把所有的事务同步器从线程上下文中移除
    List<TransactionSynchronization> suspendedSynchronizations = doSuspendSynchronization();
    try {
        // 被挂起的资源对象
        Object suspendedResources = null;
        if (transaction != null) {
            suspendedResources = doSuspend(transaction);
        }

        // 因为需要把当前的事务进行挂起,所以下面要做的就是把线程上下文中的当前事务信息需要被缓存起来,等到事务恢复的时候再从缓存中获取并恢复,并且后面会把新事务的信息放到线程上下文中
        // 获取被挂起的事务名称
        String name = TransactionSynchronizationManager.getCurrentTransactionName();
        // 把线程上下文中的事务名称置为null
        TransactionSynchronizationManager.setCurrentTransactionName(null);

        // 获取被挂起的事务的读写模式
        boolean readOnly = TransactionSynchronizationManager.isCurrentTransactionReadOnly();
        // 把线程上下文中的事务读写模式改为非只读
        TransactionSynchronizationManager.setCurrentTransactionReadOnly(false);

        // 获取被挂起的事务的事务隔离级别
        Integer isolationLevel = TransactionSynchronizationManager.getCurrentTransactionIsolationLevel();
        // 把线程上下文中的事务中的事务隔离级别改为null
        TransactionSynchronizationManager.setCurrentTransactionIsolationLevel(null);

        // 获取被挂起事务的激活状态
        boolean wasActive = TransactionSynchronizationManager.isActualTransactionActive();
        // 把线程上下文中的事务激活状态置为false
        TransactionSynchronizationManager.setActualTransactionActive(false);

        // 返回被挂起的资源
        return new SuspendedResourcesHolder(
            suspendedResources, suspendedSynchronizations, name, readOnly, isolationLevel, wasActive);
    } catch (RuntimeException | Error ex) {
        // doSuspend failed - original transaction is still active...
        doResumeSynchronization(suspendedSynchronizations);
        throw ex;
    }
}
// 条件成立:说明此时线程上下文中还不存在事务
else if (transaction != null) {
    // 对事务资源进行挂起,具体操作由子类实现,并且返回的是原来事务的连接对象
    Object suspendedResources = doSuspend(transaction);
    // 把原来的资源缓存在SuspendedResourcesHolder这个挂起资源对象中
    return new SuspendedResourcesHolder(suspendedResources);
} else {
    // Neither transaction nor synchronization active.
    return null;
}
}
private List<TransactionSynchronization> doSuspendSynchronization() {
   // 获取当前线程上下文中的所有事务同步器
   List<TransactionSynchronization> suspendedSynchronizations =
         TransactionSynchronizationManager.getSynchronizations();
   // 遍历所有的事务同步器,调用suspend方法
   for (TransactionSynchronization synchronization : suspendedSynchronizations) {
      synchronization.suspend();
   }

   // 然后把所有的事务同步器都从线程上下文中移除
   TransactionSynchronizationManager.clearSynchronization();
   return suspendedSynchronizations;
}

 可以看到此时会把线程上下文中的事务信息以及事务同步器都取出来,然后创建一个SuppendedResourcesHolder对象,把取出来的事务信息以及事务同步器放到这个对象中,最后把这个对象返回出去

/**
 * 开启事务
 *
 * @param definition         事务属性定义对象
 * @param transaction        事务对象
 * @param debugEnabled       debugEnabled
 * @param suspendedResources 被挂起的事务的资源对象,如果当前线程中不存在事务,则该参数对象为null
 */
private TransactionStatus startTransaction(TransactionDefinition definition, Object transaction,
                                 boolean debugEnabled, @Nullable SuspendedResourcesHolder suspendedResources) {

   // 只要事务同步模式不等于SYNCHRONIZATION_NEVER,那么事务同步在事务开启后都会生效
   boolean newSynchronization = (getTransactionSynchronization() != SYNCHRONIZATION_NEVER);
   // 创建一个事务状态对象
   // 这里newTransaction属性很重要,当此时是创建一个新的事务的时候,newTransaction就等于true
   DefaultTransactionStatus status = newTransactionStatus(
         definition, transaction, true, newSynchronization, debugEnabled, suspendedResources);
   // 具体由子类实现
   doBegin(transaction, definition);
   // 事务同步器绑定到当前线程上下文
   prepareSynchronization(status, definition);
   return status;
}

然后由于事务传播级别是PROPAGATION_REQUIRES_NEW,所以我们需要新开一个spring事务,也就是调用startTransaction方法创建一个新的TransactionStatus,同样的需要执行prepareSynchronization方法,上面也说过了,在prepareSynchronization方法中会去把新创建的事务的信息放到线程上下文中 

/**
 * 为给定参数创建TransactionStatus实例
 */
protected DefaultTransactionStatus newTransactionStatus(
      TransactionDefinition definition, @Nullable Object transaction, boolean newTransaction,
      boolean newSynchronization, boolean debug, @Nullable Object suspendedResources) {

   boolean actualNewSynchronization = newSynchronization &&
         !TransactionSynchronizationManager.isSynchronizationActive();
   return new DefaultTransactionStatus(
         transaction, newTransaction, actualNewSynchronization,
         definition.isReadOnly(), debug, suspendedResources);
}

在创建TransactionStatus的构造方法中,此时就会把SuppendedResourcesHolder对象作为参数被存放在DefaultTransactionStatus对象中。这样一来此时就效果就是当前线程上下文中保存的事务就是新创建的事务了,而原来的事务都会缓存到新事务的TransactionStatus对象中了。然后我们关注到事务提交的环节 

private void processCommit(DefaultTransactionStatus status) throws TransactionException {
   try {
     // ...........事务提交代码省略
   } finally {
      // 这行代码里面处理了恢复被挂起事务的操作,也就是说恢复被挂起的事务是在新事务提交完之后去执行的
      cleanupAfterCompletion(status);
   }
}

在提交完事务之后,会执行cleanupAfterCompletion方法 

/**
 * 当事务完成后会调用,进行一些清理以及恢复操作
 *
 * @param status 事务状态对象
 * @see #doCleanupAfterCompletion
 */
private void cleanupAfterCompletion(DefaultTransactionStatus status) {
   // 标记事务状态为已完成
   status.setCompleted();
   if (status.isNewSynchronization()) {
      // 清空当前线程上下文与该事务相关的所有信息
      TransactionSynchronizationManager.clear();
   }

   if (status.isNewTransaction()) {
      // 钩子方法,子类实现
      doCleanupAfterCompletion(status.getTransaction());
   }

   // 条件成立:说明该事务中有被挂起的事务
   if (status.getSuspendedResources() != null) {
      if (status.isDebug()) {
         logger.debug("Resuming suspended transaction after completion of inner transaction");
      }
      Object transaction = (status.hasTransaction() ? status.getTransaction() : null);
      // 恢复被挂起的事务(把被挂起的事务重新绑定到当前线程上下文中)
      resume(transaction, (SuspendedResourcesHolder) status.getSuspendedResources());
   }
}

 此时就会去当前事务的TransactionStatus中查看一下SuspendedResourcesHolder是否存在,如果存在就说明又被挂起的事务,我们需要把这个事务进行恢复(因为事务提交了,之前的事务就需要被恢复)。具体进行事务恢复的逻辑在resume方法中

/**
 * 恢复给定的事务
 *
 * @param transaction     当前线程上下文的事务对象
 * @param resourcesHolder 要恢复的事务
 * @see #doResume
 * @see #suspend
 */
protected final void resume(@Nullable Object transaction, @Nullable SuspendedResourcesHolder resourcesHolder)
      throws TransactionException {

   if (resourcesHolder != null) {
      // 获取被挂起的资源
      Object suspendedResources = resourcesHolder.suspendedResources;
      // 条件成立:被挂起的资源不为null
      if (suspendedResources != null) {
         // 执行具体的事务恢复逻辑,交由子类实现
         doResume(transaction, suspendedResources);
      }

      // 把被挂起的事务的信息放到当前线程上下文中
      List<TransactionSynchronization> suspendedSynchronizations = resourcesHolder.suspendedSynchronizations;
      if (suspendedSynchronizations != null) {
         TransactionSynchronizationManager.setActualTransactionActive(resourcesHolder.wasActive);
         TransactionSynchronizationManager.setCurrentTransactionIsolationLevel(resourcesHolder.isolationLevel);
         TransactionSynchronizationManager.setCurrentTransactionReadOnly(resourcesHolder.readOnly);
         TransactionSynchronizationManager.setCurrentTransactionName(resourcesHolder.name);
         doResumeSynchronization(suspendedSynchronizations);
      }
   }
}

 可以看到就是把之前被挂起的事务的信息以及事务同步器从SuspendedResourcesHolder对象中拿出来,然后重新放到线程上下文中,然后当被挂起的事务提交或回滚的时候,就可以从线程上下文中获取到它的事务同步器然后进行相应的方法回调了

事务同步器的生效条件

事务同步器并不是说总是能生效的,它是基于一个配置属性来判断是否能够生效,这个配置属性在事务管理器中可以进行设置:

private int transactionSynchronization = SYNCHRONIZATION_ALWAYS;
public final void setTransactionSynchronization(int transactionSynchronization) {
   this.transactionSynchronization = transactionSynchronization;
}

 这个属性有三个值可以进行选择:

/**
 * 该事务同步模式表示事务同步始终生效
 *
 * @see org.springframework.transaction.TransactionDefinition#PROPAGATION_SUPPORTS
 * @see org.springframework.transaction.TransactionDefinition#PROPAGATION_NOT_SUPPORTED
 * @see org.springframework.transaction.TransactionDefinition#PROPAGATION_NEVER
 */
public static final int SYNCHRONIZATION_ALWAYS = 0;

/**
 * 该事务同步模式表示事务同步仅在非“空”事务的时候生效
 *
 * @see org.springframework.transaction.TransactionDefinition#PROPAGATION_REQUIRED
 * @see org.springframework.transaction.TransactionDefinition#PROPAGATION_MANDATORY
 * @see org.springframework.transaction.TransactionDefinition#PROPAGATION_REQUIRES_NEW
 */
public static final int SYNCHRONIZATION_ON_ACTUAL_TRANSACTION = 1;

/**
 * 该事务同步模式表示事务同步从不生效
 */
public static final int SYNCHRONIZATION_NEVER = 2;

这三个可选值的作用如下:

  • SYNCHRONIZATION_ALWAYS

如果设置了该属性值,则不管什么事务传播级别,事务同步器的方法都能够进行相应的回调,这也是事务管理器的默认值

  • SYNCHRONIZATION_ON_ACTUAL_TRANSACTION

如果设置了该属性值,则只有非“空”事务中的事务同步器才能被回调,什么意思呢?对于比如PROPAGATION_SUPPORTS,PROPAGATION_NOT_SUPPORTED,PROPAGATION_NEVER这三种事务传播级别来说,它们都表示不处于事务中,这样的话就相当于业务代码就不是在事务中执行了,所以也叫做是“空”事务,设置了SYNCHRONIZATION_ON_ACTUAL_TRANSACTION的话,对于“空”事务来说,尽管设置了事务同步器,也不会被回调。因为代码都不在事务中执行了,也就没有所谓的commit和rollback这些事务生命周期阶段了。应用场景可以是如果我们想要只针对监控真实的事务而不是这些“空”事务,那么我们就可以设置该属性值

  • SYNCHRONIZATION_NEVER

如果设置了该属性,不管是真实事务还是“空”事务,都不会回调事务同步器

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

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

相关文章

中秋特辑:Java事件监听实现一个猜灯谜小游戏

众所周知&#xff0c;JavaSwing是Java中关于窗口开发的一个工具包&#xff0c;可以开发一些窗口程序&#xff0c;然后由于工具包的一些限制&#xff0c;导致Java在窗口开发商并没有太多优势&#xff08;当然也有一些第三方的工具包也很好用&#xff09;&#xff0c;不过&#x…

使用Python CV2融合人脸到新图片--优化版

优化说明 上一版本人脸跟奥特曼图片合并后边界感很严重&#xff0c;于是查找资料发现CV2还有一个泊松函数很适合融合图像。具体代码如下&#xff1a; import numpy as np import cv2usrFilePath "newpic22.jpg" atmFilePath "atm2.jpg" src cv2.imrea…

java基础-集合-ArrayList(JDK1.8)源码学习

文章目录 类图新增addensureCapacityInternalensureExplicitCapacitygrowhugeCapacity 删除removefastRemove 遍历Iterator 类图 新增 add public boolean add(E e) {// 根据注释可知 Increments modCount!!&#xff0c;modCount下面详解ensureCapacityInternal(size 1); //…

特斯拉Dojo超算:AI训练平台的自动驾驶与通用人工智能之关键

特斯拉公开Dojo超算架构细节&#xff0c;AI训练算力平台成为其自动驾驶与通用人工智能布局的关键一环 在近日举行的Hot Chips 34会议上&#xff0c;特斯拉披露了其自主研发的AI超算Dojo的详细信息。Dojo是一个可定制的超级计算机&#xff0c;从芯片到系统全部由特斯拉自主设计…

如何优化网站SEO(百度SEO优化的6个方案及密度)

一&#xff1a;蘑菇号https://www.mooogu.cn/ SEO优化是提高网站在搜索引擎中排名的关键技术。对于新网站而言&#xff0c;如何快速提高百度排名是每个站长需要关注的问题。下面我们将介绍新网站百度SEO具体方法。 二&#xff1a; 首先&#xff0c;通过网站架构优化来提高页…

解决5053无法安装驱动的故障

用5053连接车机&#xff0c;发现驱动上面有一个问号&#xff0c;看来驱动出问题了&#xff0c;试着用原来的办法无法强行安装&#xff0c;出现如下报错: 主要原因是老旧的设备驱动程序没有及时更新&#xff0c;遭到了新系统的嫌弃&#xff0c;导致数字签名验证失败&#xff0c;…

golang for循环append的数据重复

原因&#xff0c;因为使用了& 需要增加一行&#xff0c;问题解决

华为云云耀云服务器L实例评测| 搭建属于自己的第一个中秋快乐网页

华为云服务器 1 如何快速获得一个华为云服务器1.1 注册华为云账号1.2 选择华为云服务器实例 (云耀L系列)1.3 选择服务器区域1.4 选择实例规格1.5 付款界面确认实例参数&#xff0c;支付即可 2 运行自己的服务器2.1 找到自己的服务器控制面板2.2 了解服务器面板2.3 登录我们的服…

【网络编程】TCP Socket编程

TCP Socket编程 1. ServerSocket2. Socket3. TCP的长短连接4. Socket 通信模型5. 代码示例&#xff1a;TCP 回显服务器 流套接字&#xff1a; 使用传输层TCP协议 TCP: 即Transmission Control Protocol&#xff08;传输控制协议&#xff09;&#xff0c;传输层协议。 TCP的特点…

Pycharm配置环境以及Teminal不能使用问题解决

Pycharm配置环境 配置好环境后点击Terminal Teminal不能使用问题解决 我的报错信息&#xff1a; Import-Module : 无法加载文件 D:\Anaconda\shell\condabin\Conda.psm1&#xff0c;因为在此系统上禁止运行脚本。 解决方案&#xff1a; 第一步.&#xff1a;在 Windows 下用…

K8S名称空间和资源配额

Kubernetes 支持多个虚拟集群&#xff0c;底层依赖于同一个物理集群。 这些虚拟集群被称为名称空间。名称空间namespace是k8s集群级别的资源&#xff0c;可以给不同的用户、租户、环境或项目创建对应的名称空间&#xff0c;例如&#xff0c;可以为test、dev、prod环境分别创建各…

服务器搭建(TCP套接字)-基础版(服务端)

一、socket 1.1、vim man查看socket :!man socket1.2、 依赖的头文件 #include <sys/types.h> #include <sys/socket.h>1.3、原型 int socket(int domain, int type, int protocol);domain说明AF_INETIPV4协议AF_INET6IPV6协议AF_LOCALUnix域协议 type说明S…

JavaScript中的垃圾回收机制

聚沙成塔每天进步一点点 ⭐ 专栏简介⭐ JavaScript的垃圾回收机制⭐ 内存管理⭐ 引用计数⭐ 标记-清除算法⭐ 内存泄漏⭐ 性能优化⭐ 使用delete操作符⭐ 注意循环中的变量引用⭐ 使用工具进行内存分析⭐ 使用合适的数据结构⭐ 写在最后 ⭐ 专栏简介 前端入门之旅&#xff1a;探…

企业架构LNMP学习笔记54

企业架构NoSQL数据库之MongoDB。 学习目标和内容&#xff1a; 1&#xff09;能够简单描述mongoDB的使用特点&#xff1a; 2&#xff09;能够安装配置启动MongoDB&#xff1b; 3&#xff09;能够使用命令行客户端简单操作MongoDB&#xff1b; 4&#xff09;能够实现基本的数…

vsftp3.0 匿名用户,本地用户,虚拟用户

整体配置介绍&#xff1a; 进入vsftpd配置文件 vim /etc/vsftpd/vsftpd.conf //输入i开始编辑&#xff0c;修改后按esc退出编辑&#xff0c;输入:wq后回车保存并退出anonymous_enableYES #接受匿名用户&#xff0c;默认无密码请求 lo…

01_Elasticsearch入门介绍

01_Elasticsearch入门介绍 Elasticsearch 是什么1、什么是搜索&#xff1f;2、如果用数据库做搜索会怎么样&#xff1f;3、什么是全文检索和Lucene&#xff1f;4、什么是Elasticsearch&#xff1f;5、Elasticsearch的功能6、Elasticsearch的适用场景7、Elasticsearch的特点 什么…

Anaconda成功安装之后没有在菜单列和桌面显示图标

1、进入命令提示符 2、输入cmd 3、进入到Anaconda安装路径 比如我装在F盘 4、然后输入 python .\Lib\_nsis.py mkmenus 回车 这时候菜单列就可以看到了

第 4 章 串(串的堆分配存储实现)

1. 背景说明 实现基本与定长分配一致&#xff0c;不过将定长分配改为动态分配&#xff0c;解除了长度限制&#xff0c;实现更加灵活。 2. 示例代码 1) status.h /* DataStructure 预定义常量和类型头文件 */#ifndef STATUS_H #define STATUS_H#define CHECK_NULL(pointer) if …

[JAVAee]spring-Bean对象的执行流程与生命周期

执行流程 spring中Bean对象的执行流程大致分为四步: 启动Spring容器实例化Bean对象Bean对象注册到Spring容器中将Bean对象装配到所需的类中 ①启动Spring容器,在main方法中获取spring上下文对象并配备spring. import demo.*;import org.springframework.context.Applicati…

nacos动态配置刷新机制原理

nacos动态配置刷新机制原理 项目里面许多业务场景以及灵活配置的需求经常要用到动态配置。一般就是apollo和nacos两种选型。 nacos动态刷新导致的bug nacos一般为了实现动态配置一般会加入RefreshScope注解进行实现&#xff0c;例如下面的代码加入了RefreshScope想要实现跨域…