Spring 源码分析 事务管理的实现原理(上)

0 阅读16分钟

前言

上篇文章介绍了 Spring AOP 的实现原理,掌握了 AOP,再理解 Spring 事务会简单很多,值得注意的是,提到事务,我们通常说的是关系型数据库的事务,Spring 只是一个 Java 开源框架,它不存在事务的说法,Spring 是通过一系列代码逻辑来管理数据库事务,实现业务场景中事务的嵌套处理。本篇文章先介绍 Spring 事务管理涉及的相关类

本篇文章使用的 SpringBoot 版本是 3.4.1 ,对应 Spring 版本 6.2.1

SpringBoot & Spring 架构图示概览

这里我以 SpringBoot 源码入口为起点,画了一个相关的流程图,包含了 SpringBoot、Spring 事务、Spring AOP、Spring 事件、BeanFactoryPostProcessor、BeanPostProcessor 等所有 Spring 知识,以及相关模块之间的交互联系,后续也会持续更新此图(因为我自己还没有学完),我试了下作者侧这边更新后,分享的协作链接也会实时变更,希望对大家有帮助

SpringBoot & Spring 架构图 持续更新 对于即将需要面试的同学应该会比较有帮助!

基本用法

注解方式

直接在方法上标注 @Transactional 即可,这样 test1() 方法就会以存在事务的方式运行,抛出相关异常会回滚事务

@Transactional
public void test1(){}

@Transactional 内部有很多属性用来控制事务的行为

public @interface Transactional {

    //指定事务管理器
    @AliasFor("value")
    String transactionManager() default "";
    
    //传播行为
    Propagation propagation() default Propagation.REQUIRED;
    
    //指定哪些异常要回滚
    Class<? extends Throwable>[] rollbackFor() default {};
    
    //指定超时时间
    int timeout() default TransactionDefinition.TIMEOUT_DEFAULT;
    //...
}

包括选择事务管理器、指定传播行为、要回滚的异常、超时时间等等。

编程方式 TransactionTemplate

TransactionTemplatespring-tx 模块给我们提供的一个操作事务的类,针对有返回值和无返回值提供了两种 API,在 SpringBoot 中提供了自动配置,直接使用即可

@Configuration(proxyBeanMethods = false)
@ConditionalOnSingleCandidate(PlatformTransactionManager.class)
public static class TransactionTemplateConfiguration {

    @Bean
    @ConditionalOnMissingBean(TransactionOperations.class)
    public TransactionTemplate transactionTemplate(PlatformTransactionManager transactionManager) {
       return new TransactionTemplate(transactionManager);
    }
}
  • 无返回值
@Autowired
private TransactionTemplate transactionTemplate;

public void test2(){
    transactionTemplate.execute(new TransactionCallbackWithoutResult() {
        @Override
        protected void doInTransactionWithoutResult(TransactionStatus status) {
            try {
                //业务行为
            } catch (Exception e){
                status.setRollbackOnly();
                throw new RuntimeException("创建订单失败", e);
            }
        }
    });
}
  • 有返回值

当业务操作需要结果时可以使用

public void test3(){
    Object obj = transactionTemplate.execute(new TransactionCallback<Object>() {
        @Override
        public Object doInTransaction(TransactionStatus status) {
            try {
                //业务操作
                return null;
            } catch (Exception e){
                // 标记事务为回滚
                status.setRollbackOnly();
                throw new RuntimeException("创建用户失败,事务已回滚", e);
            }
        }
    });
}

编程式事务可以和声明式事务结合使用,TransactionTemplate 的默认传播行为是 REQUIRED,这意味着没有外层声明式事务的影响下,每一次 transactionTemplate.execute() 都是一个新事物,使用编程式事务让我们可以 更灵活的控制事务粒度

事务的传播行为

Spring 事务管理提供了以下几种传播行为,每一种传播行为会对当前事务的嵌套有不同的处理逻辑。

传播行为 Propagation翻译如果当前有事务如果当前无事务异常回滚影响典型应用场景
REQUIRED必需(默认)加入当前事务创建新事务全部回滚大多数业务方法
SUPPORTS支持加入当前事务非事务运行看情况查询方法,可事务可非事务
MANDATORY强制加入当前事务抛出异常全部回滚必须被事务方法调用
REQUIRES_NEW新建事务挂起当前事务,创建新事务创建新事务各自独立回滚独立业务,如日志记录
NOT_SUPPORTED不支持挂起当前事务,非事务运行非事务运行不影响事务非核心业务,避免事务锁
NEVER从不抛出异常非事务运行不适用强制非事务环境
NESTED嵌套创建嵌套子事务(保存点)创建新事务子事务回滚不影响主事务部分失败不影响整体的场景

Spring 注解事务的核心就是对不同的传播行为或者说对事务嵌套进行处理,因为关系型数据库 MySQL 是不支持事务嵌套的,假如我们在 MySQL 客户端开启事务之后,再次执行 Begin,那么它会先提交当前事务然后重新开启新事务

select * from Cash_repay_apply
BEGIN;
update  Cash_repay_apply set repay_status = 'TEST' where id = 1
BEGIN; -- 会先提交事务,再开启新事务
update  Cash_repay_apply set repay_status = 'TEST' where id = 2
COMMIT;

上述代码无法嵌套 BEGIN,执行第二个 BEGIN 时,会先提交上一次 BEGIN 的事务。

事务管理器

事务管理器 TransactionManagerSpring 操作数据库事务的核心顶层接口,它的子接口 PlatformTransactionManager 定义了三个核心方法

public interface PlatformTransactionManager extends TransactionManager {
    /**
     * 获取事务
     * */
    TransactionStatus getTransaction(@Nullable TransactionDefinition definition) throws TransactionException;
    
    /**
     * 提交事务
     * */
    void commit(TransactionStatus status) throws TransactionException;
    
    /**
     * 回滚事务
     * */
    void rollback(TransactionStatus status) throws TransactionException;
}

无论应用程序玩的有多花哨,最终无非是对数据库事务进行操作,那么数据库事务其实就三个点,开启事务、提交、回滚。所以事务管理器提供了三个方法来对应

它把事务定义封装到 TransactionDefinition 中,根据它获取一个数据库事务对象,事务结果信息封装到 TransactionStatus 对象中,然后提供了提交事务和回滚事务两个方法由具体子类实现。在 SpringBoot 项目中通常我们会引入 spring-boot-starter-jdbc

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>

引入之后会启用 JdbcTransactionManagerConfiguration,这时候具体的实现就是 JdbcTransactionManager 。不过有意思的是具体的实现在抽象事物管理器 AbstractPlatformTransactionManager 中,因为这一套开启事务、提交、回滚的操作其实都是通用的,无非就是用 jdbc 驱动的 Connection 对象来和 MySQL 通信,执行客户端命令罢了

回想我们之前学习的 jdbc 知识,事务的操作一定离不开 jdbc 规范,我们跟踪源码深入会发现,事务的获取、提交、回滚的确都是通过 Connection 对象来操作的

public interface Connection  extends Wrapper, AutoCloseable {
    void commit() throws SQLException;
    void rollback() throws SQLException;
}

SpringBoot 默认数据库连接池是 HikariCP 。它的 Connection 实现类是 HikariProxyConnection,不过实际上它几乎啥也没干,包装了一层,最后还是调用 jdbc 驱动的 Connection 实现类 ConnectionImpl

JdbcTransactionManagerDataSourceTransactionManager 的子类,一个事务管理器对应一个数据源,Spring 对事务的管理,本质上是管理数据库连接 Connection,事务最终是用 Connection 来开启和提交的·

TransactionDefinition

从这个类的名字可以看出,它是事务的定义抽象,结合事务管理器 PlatformTransactionManager.getTransaction(TransactionDefinition definition) 的入参我们可以知道 TransactionDefinition 定义了事务的相关属性,根据它的定义通过事务管理器获取一个数据库事务。

在设计上源码中抽象出了几个继承关系如下图 。使用声明式事务时候最终实现类是 RuleBasedTransactionAttribute 公共属性的定义基本上还是在 DefaultTransactionDefinition

image.png

TransactionStatus

TransactionStatus 描述了事务开启后运行过程中状态。默认实现类是 DefaultTransactionStatus,结合事务管理器的 commit(TransactionStatus status)rollback(TransactionStatus status) 的方法入参可以看出,TransactionStatus 封装了最终的事务结果(提交/回滚),提交到数据库中。

public class DefaultTransactionStatus extends AbstractTransactionStatus {

    private final String transactionName;      // 事务名称

    private final Object transaction;          // 底层事务对象

    private final boolean newTransaction;       // 是否是新事务(通常可以理解为是否是当前事务方法自己创建的事务)

    private final boolean newSynchronization;   // 是否新的事务同步(同上)

    private final boolean nested;               // 是否是嵌套事务

    private final boolean readOnly;             // 是否只读

    private final Object suspendedResources;     // 当前事务方法挂起的事务资源
}

了解了 PlatformTransactionManager、TransactionDefinition、TransactionStatus, 结合 PlatformTransactionManager 的三个方法

public interface PlatformTransactionManager extends TransactionManager {
   /**
    * 获取事务
    * */
   TransactionStatus getTransaction(@Nullable TransactionDefinition definition) throws TransactionException;
   
   /**
    * 提交事务
    * */
   void commit(TransactionStatus status) throws TransactionException;
   
   /**
    * 回滚事务
    * */
   void rollback(TransactionStatus status) throws TransactionException;
}

前面我们说 Spring 对事务的管理,本质上是对 Connection 进行管理,就是调用这三个方法来管理 Connection 对象。

思考除了 TransactionTemplate 我们是否可以使用 PlatformTransactionManager 来实现编程式事务?

TransactionStatus 和 TransactionDefinition 的区别

下面我们用表格的方式对比一下这两个类·

维度TransactionDefinitionTransactionStatus
角色事务的定义(配置时)事务的状态(运行时)
生命周期事务 开始前 定义,全局共享事务 开始后 创建,事务结束销毁
可变性事务获取后 不可变(配置固定)可变(状态会随业务代码变化)
存储内容事务的配置属性当前事务的运行状态
创建时机在获取事务前由调用者提供事务开始时由事务管理器创建

Spring 事务管理图示

这里我们举几个常见的事务场景

普通事务

@Transactional
public void test(){
    //...db 操作
}

例如这段简单的代码,事务的开启流程图如下

image.png

其实就是代理方法用 AOP 实现了一个环绕通知的效果,在目标方法执行前开启事务,在目标方法执行后提交或者回滚事务,我们暂时可以不关注开启事务和回滚事务内部还有哪些细节,后面会详细介绍。

嵌套事务

以下面这段嵌套事务的代码为例

@Transactional
public void test(){
    //db操作
    test2Service.testNewTx();
}

@Service
public class Test2Service{
    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void testNewTx(){
       //db 操作...
    }
}

这段示例代码是一个已存在的事务中,嵌套了一个需要新开事务的业务方法,图示如下

image.png

这里代码示例中,会先开启 test() 方法的事务,然后执行到 testNewTx() 时,发现当前方法传播行为是新开事务,于是会再开启一个事务,等内层方法 testNewTx() 结束后,提交/回滚它的事务,然后再继续进行外层 test() 方法的事务,从这个案例不难想象,无论多少层事务嵌套,都是一样的处理逻辑,无非是嵌套层数的多少罢了,后面会详细介绍源码,实现相对比较简单

总结

Spring 事务管理是通过代理目标对象,执行代理方法,在原方法执行前开启事务、原方法执行后提交/回滚事务,当遇到嵌套事务时逻辑亦然

TransactionInterceptor

上一篇 AOP 实现原理,我们知道执行代理对象方法会调用一系列拦截器实现拦截。上一段我们说了 Spring 管理事务实际上就是对目标对象代理,实现了一个环绕通知的效果,而 TransactionInterceptor 就是实现这个环绕通知包装的拦截器。

public class TransactionInterceptor extends TransactionAspectSupport implements MethodInterceptor, Serializable {
    //......
    
    @Override
    public Object invoke(MethodInvocation invocation) throws Throwable {
        Class<?> targetClass = (invocation.getThis() != null ? AopUtils.getTargetClass(invocation.getThis()) : null);

        //以事务方式调用
        return invokeWithinTransaction(invocation.getMethod(), targetClass, invocation::proceed);
    }
}

观察源码可以发现 invokeWithinTransaction() 是父类 TransactionAspectSupport 实现的。具体实现逻辑后面会介绍

TransactionSynchronizationManager

TransactionSynchronizationManager 是事务管理过程中一个非常核心的类,它的作用是保存事务管理过程中事务的相关信息,绑定到当前线程,包括 事务资源、同步器、隔离级别、事务激活状态

public abstract class TransactionSynchronizationManager {

    //保存当前线程的事务资源,key 通常是数据源,value 通常是 Connection 包装对象
    private static final ThreadLocal<Map<Object, Object>> resources = new NamedThreadLocal<>("Transactional resources");
    
    //保存当前事务的回调方法
    private static final ThreadLocal<Set<TransactionSynchronization>> synchronizations = new NamedThreadLocal<>("Transaction synchronizations");

    //保存当前线程事务的名字
    private static final ThreadLocal<String> currentTransactionName = new NamedThreadLocal<>("Current transaction name");
    
    //当前线程事务是不是只读
    private static final ThreadLocal<Boolean> currentTransactionReadOnly = new NamedThreadLocal<>("Current transaction read-only status");

    //当前线程事务隔离级别,通常开发中应该很少手动设置隔离级别
    private static final ThreadLocal<Integer> currentTransactionIsolationLevel = new NamedThreadLocal<>("Current transaction isolation level");

    //当前线程事务是不是激活的
    private static final ThreadLocal<Boolean> actualTransactionActive = new NamedThreadLocal<>("Actual transaction active");
}

我们查看这个类的成员变量, 提供了六个 ThreadLocal 存储事务相关信息。其核心方法如下

//绑定资源(把修改后的 Connection 对象绑定到当前线程)
public static void bindResource(Object key, Object value){}
 
//解绑资源(把修改后的 Connection 对象从当前线程解绑)
public static Object unbindResource(Object key){}

//注册同步器(注册事务阶段回调代码,下一段会介绍 TransactionSynchronization)
public static void registerSynchronization(TransactionSynchronization synchronization){}
 
//获取同步器
public static List<TransactionSynchronization> getSynchronizations(){}
 

思考一个问题,为什么作者不用一个对象来封装这六个 ThreadLocal ?它们都是当前线程绑定的事务相关的信息,完全可以抽象出一个对象?

TransactionSynchronization

介绍

查看注释,TransactionSynchronization 是事务同步回调接口,在 Spring 管理事务的过程中,事务管理进行到不同的阶段会回调不同的方法

public interface TransactionSynchronization extends Ordered, Flushable {
    /**
     * 挂起事务时回调
     */
    default void suspend() {}

    /**
     * 恢复事务时回调
     */
    default void resume() {}

    /**
     * Flush 的时候回调,例如 Hibernate/JPA 的 flush 操作.
     */
    @Override
    default void flush() {}

    /**
     * 创建新保存点后调用
     */
    default void savepoint(Object savepoint) {}

    /**
     * 回滚到上一个保存点前调用
     */
    default void savepointRollback(Object savepoint) {}

    /**
     * 事务提交前调用
     */
    default void beforeCommit(boolean readOnly) {}

    /**
     * 事务完成(回滚/提交)前调用
     */
    default void beforeCompletion() {}

    /**
     * 事务提交后调用
     */
    default void afterCommit() {}

    /**
     * 事务完成(提交、回滚)后调用
     */
    default void afterCompletion(int status) {}

}

上一段我们介绍了 TransactionSynchronizationManager 中有一个 registerSynchronization() 方法可以注册事务同步回调接口,它是绑定到当前线程的,不需要交给 Spring 管理,例如

TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() {
    @Override
    public void beforeCommit(boolean readOnly) {
        System.out.println("TransactionSynchronization beforeCommit");
    }
    //...
});

事务保存点 savepoint

值得一提的是 savepoint ,叫做保存点,是关系型数据库中支持部分回滚的一个命令

BEGIN;
update 语句1
update 语句2
SAVEPOINT test1;
update 语句3
update 语句4
ROLLBACK TO SAVEPOINT test1;
-- ......

使用 ROLLBACK TO SAVEPOINT 可以指定事务回滚到哪一个节点位置。在 Spring 项目中可以使用如下的方式来设置保存点

@Transactional
public void test1(){
    CashRepayApply apply = cashRepayApplyMapper.getForUpdate(1L);
    apply.setRepayStatus("test");;
    cashRepayApplyMapper.updateById(apply);
    //创建保存点
    Object sp1 = TransactionAspectSupport.currentTransactionStatus().createSavepoint();

    apply.setRepayStatus("test2");
    cashRepayApplyMapper.updateById(apply);
    try {
        if(true){
            throw new RuntimeException("<UNK>");
        }
    } catch (Exception e){
        //回滚到保存点
        TransactionAspectSupport.currentTransactionStatus().rollbackToSavepoint(sp1);
    }
}

TransactionExecutionListener

介绍

这个类是事务执行监听器,它是 Spring 6.1 版本新增的功能,它的功能和 TransactionSynchronization 有些类似,也是在事务的不同阶段执行一些回调方法

public interface TransactionExecutionListener {

    /**
     * 事务开始前执行
     */
    default void beforeBegin(TransactionExecution transaction) {}

    /**
     * 事务开始后执行
     */
    default void afterBegin(TransactionExecution transaction, @Nullable Throwable beginFailure) {}

    /**
     * 事务提交前执行
     */
    default void beforeCommit(TransactionExecution transaction) {}

    /**
     * 事务提交后执行
     */
    default void afterCommit(TransactionExecution transaction, @Nullable Throwable commitFailure) {}

    /**
     * 事务回滚前执行
     */
    default void beforeRollback(TransactionExecution transaction) {}

    /**
     * 事务回滚后执行
     */
    default void afterRollback(TransactionExecution transaction, @Nullable Throwable rollbackFailure) {}
}

值得注意的是 TransactionExecutionListener 是事务管理器 AbstractPlatformTransactionManager 中的一个成员变量,

public abstract class AbstractPlatformTransactionManager implements PlatformTransactionManager {
 
    private Collection<TransactionExecutionListener> transactionExecutionListeners = new ArrayList<>();
    //......

前面介绍过,事务的三个重要操作,开启、提交、回滚都是事务管理器来实现的,在这些操作的过程中,不同的阶段会遍历调用 TransactionExecutionListener 的不同方法。

自动配置来源

TransactionSynchronization 最重要的一个区别是 TransactionSynchronization 是绑定到某一个事务(线程)上的回调,而 TransactionExecutionListener 是针对所有事务的回调。结合 TransactionExecutionListener 是抽象事务管理器的成员变量来思考,所以很明显 TransactionExecutionListener 是交给 Spring 管理才会被赋值到抽象事务管理器中生效的

SpringBoot 中, TransactionManagerCustomizationAutoConfiguration 自动配置类中

@ConditionalOnClass(PlatformTransactionManager.class)
@AutoConfiguration(before = TransactionAutoConfiguration.class)
@EnableConfigurationProperties(TransactionProperties.class)
public class TransactionManagerCustomizationAutoConfiguration {

    /*
     * 从容器中获取 TransactionManagerCustomizer 的 Bean 列表,得到 TransactionManagerCustomizers
     */
    @Bean
    @ConditionalOnMissingBean
    TransactionManagerCustomizers platformTransactionManagerCustomizers(
          ObjectProvider<TransactionManagerCustomizer<?>> customizers) {
       return TransactionManagerCustomizers.of(customizers.orderedStream().toList());
    }

    /*
     * 从容器中获取 TransactionExecutionListener 的 Bean 列表,得到 ExecutionListenersTransactionManagerCustomizer
     */
    @Bean
    ExecutionListenersTransactionManagerCustomizer transactionExecutionListeners(
          ObjectProvider<TransactionExecutionListener> listeners) {
       return new ExecutionListenersTransactionManagerCustomizer(listeners.orderedStream().toList());
    }

}

前面的文章介绍过 SpringBoot 源码中,XxxCustomizer 都是一些用户自定义扩展的实现抽象,TransactionManagerCustomizer 很明显是自定义事务管理器配置的,然后我们再看 JdbcTransactionManagerConfiguration

@Configuration(proxyBeanMethods = false)
@ConditionalOnSingleCandidate(DataSource.class)
static class JdbcTransactionManagerConfiguration {

    /*
     * 从容器中获取 TransactionManagerCustomizers 列表应用到 DataSourceTransactionManager 中
     */
    @Bean
    @ConditionalOnMissingBean(TransactionManager.class)
    DataSourceTransactionManager transactionManager(Environment environment, DataSource dataSource,
          ObjectProvider<TransactionManagerCustomizers> transactionManagerCustomizers) {
       DataSourceTransactionManager transactionManager = createTransactionManager(environment, dataSource);
       transactionManagerCustomizers.ifAvailable((customizers) -> customizers.customize(transactionManager));
       return transactionManager;
    }
    
    /*
     * 具体的事务管理器
     */
    private DataSourceTransactionManager createTransactionManager(Environment environment, DataSource dataSource) {
       return environment.getProperty("spring.dao.exceptiontranslation.enabled", Boolean.class, Boolean.TRUE)
             ? new JdbcTransactionManager(dataSource) : new DataSourceTransactionManager(dataSource);
    }

}

我们看在构造事务管理器交给 Spring 的时候会执行 TransactionManagerCustomizer#customize 方法,我们再观察 ExecutionListenersTransactionManagerCustomizer 的实现方法

class ExecutionListenersTransactionManagerCustomizer
       implements TransactionManagerCustomizer<ConfigurableTransactionManager> {

    private final List<TransactionExecutionListener> listeners;
    ExecutionListenersTransactionManagerCustomizer(List<TransactionExecutionListener> listeners) {
       this.listeners = listeners;
    }

    @Override
    public void customize(ConfigurableTransactionManager transactionManager) {
       //向事务管理器中添加 TransactionExecutionListener
       this.listeners.forEach(transactionManager::addListener);
    }

}

这下就很清晰了,只要我们自定义一个 TransactionExecutionListener 交给 Spring 管理即可被事务管理器应用

@Bean
public TransactionExecutionListener transactionExecutionListener() {
    return new TransactionExecutionListener() {
        @Override
        public void beforeBegin(TransactionExecution transaction) {
            System.out.println("beforeBegin");
        }
        //......
    };
}

事务监听器和同步器对比

两者最重要的区别是 TransactionExecutionListener 作用的是所有事务,而 TransactionSynchronization 作用的是具体某个绑定到当前线程的事务,下面我们用一张表格对比两者区别

对比维度TransactionExecutionListenerTransactionSynchronization
作用范围全局级别:作用于事务管理器下的所有事务线程级别:作用于当前线程绑定的事务
注册方式Spring 启动时通过事务管理器或 Bean 配置运行时动态注册(通过TransactionSynchronizationManager.registerSynchronization()
事务感知可以获取TransactionExecution对象,了解事务全局信息只能感知当前事务,无法获取事务详细信息
调用次数每个事务生命周期都会触发只有显式注册了的事务才触发
依赖关系独立于特定线程,不依赖线程绑定依赖当前线程的事务绑定状态
适用场景- 全局监控和统计
- 全局限流
- 审计日志
- 性能分析
- 业务回调(发消息、事件)
- 清理线程本地缓存
- 特定业务的补偿操作
- 事务完成后的资源释放
生命周期方法- afterBegin
- beforeCommit
- afterCommit
- beforeRollback
- afterRollback
- afterCompletion
- beforeSuspend
- afterSuspend
- beforeResume
- afterResume
- suspend
- resume
- flush
- beforeCommit
- beforeCompletion
- afterCommit
- afterCompletion
执行顺序相同阶段比同步器后执行相同阶段比监听器先执行
异常处理监听器中的异常会影响事务(可配置)同步中的异常通常不会影响事务
资源管理由 Spring 容器管理生命周期由当前事务管理,事务结束后自动清理
事务挂起可以感知事务的挂起和恢复事件可以实现挂起和恢复的回调

从两者的方法作用可以看出,TransactionExecutionListener 的方法涉及事务提交/回滚前的操作,使用它的 beforeCommit()、afterCommit() 等方法如果出现异常,会导致事务的回滚,所以需要注意在这些方法的使用中,如果不想让监听器的逻辑代码影响事务,一定要捕捉异常

结语

由于篇幅问题,本篇文章先介绍 Spring 事务管理的相关重要的类和接口,下一篇文章我们具体介绍声明式事务实现的源代码,以及对比 TransactionTemplate 实现编程式事务,使用事务管理器实现另一种编程式事务管理。

如果这篇文章对你有帮助,记得点赞加关注!你的支持就是我继续创作的动力!