并发编程原理与实战(三十六)深入解析 ThreadLocal 在 Spring 框架中的核心应用与实战技巧

本文继续分析ThreadLocal在springmvc、spring等框架下的应用实战。

用户信息存储

在以springmvc 为主要框架的Web应用中,ThreadLocal最常见的应用是在拦截器中存储用户身份信息,然后在Controller、Service和DAO层中直接获取使用。这样可以避免在方法参数中层层传递用户信息,保持代码的简洁性。

拦截器示例代码:

@Slf4j
@Component
public class LoginInterceptor implements HandlerInterceptor {

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        try{
            //从请求中获取token.
            String token = getToken(request);
            //根据token获取用户信息
          	UserInfo userInfo = getUserInfo(token);
            if(null != userInfo) {
                //储用户身份信息
                SecurityContextHolder.setUserInfo(userInfo);
                return true;
            }else {
                return false;
            }
        }catch(Exception ex) {
            log.error("拦截器异常",ex);
        }
        return false;
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {

    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        //请求处理完成后调用清理方法
        SecurityContextHolder.clear();
    }

SecurityContextHolder示例代码:

public class SecurityContextHolder {
    private static final ThreadLocal<UserInfo> USER_INFO_THREAD_LOCAL = new ThreadLocal<>();

    public static void setUserInfo(UserInfo userInfo) {
        USER_INFO_THREAD_LOCAL.set(userInfo);
    }

    public static UserInfo getUserInfo() {
        return USER_INFO_THREAD_LOCAL.get();
    }

    public static void clear() {
        USER_INFO_THREAD_LOCAL.remove();
    }

}

Controller层中直接获取使用:

@PostMapping("/update")
public AjaxResult update(@RequestBody params request,HttpServletRequest servletRequest) {
   UserInfo loguser = SecurityContextHolder.getUserInfo();
   ...
}

数据库连接管理

Spring框架中使用ThreadLocal来管理数据库连接,确保在同一线程的整个事务过程中使用同一个Connection对象。这在声明式事务管理中尤为重要,通过TransactionSynchronizationManager来维护线程本地的数据库连接。以spring 5.3.6版本为例,我们分析下ThreadLocal在其中的应用。

NamedThreadLocal

public abstract class TransactionSynchronizationManager {
    private static final Log logger = LogFactory.getLog(TransactionSynchronizationManager.class);
    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");
    ......
}

从源码可以看出,spring 内部使用NamedThreadLocal来管理数据库连接,而NamedThreadLocal的核心作用与 ThreadLocal 一致,即‌实现线程本地变量隔离‌(每个线程拥有独立的局部变量),但额外增加了‌变量名称(name)‌ 的支持,主要用于‌调试和日志输出‌,方便在多线程环境中追踪变量归属。

(1)resources‌ - 存储当前线程的事务资源

  • 保存数据库连接等事务资源,以 Map<Object, Object> 结构存储
  • 键通常为数据源对象,值为对应的连接持有者(如 ConnectionHolder)
  • 支持一个事务中操作多个数据源的情况

(2)synchronizations‌ - 管理事务同步回调

  • 存储 TransactionSynchronization 对象集合,用于注册事务生命周期回调
  • 允许在事务提交、回滚等关键节点执行自定义逻辑

(3)currentTransactionName‌ - 记录当前事务名称

  • 存储描述性的事务名称,便于监控和调试

(4)currentTransactionReadOnly‌ - 标识事务只读状态

  • 布尔值,标记当前事务是否为只读模式

(5)currentTransactionIsolationLevel‌ - 保存事务隔离级别

  • 存储当前事务的隔离级别设置

(6)actualTransactionActive‌ - 指示实际事务激活状态

  • 布尔值,表示当前是否有活跃的事务

Spring事务管理调用链

下图是调用一个保存接口,从Controller到数据库的调用过程,分析其中Spring事务管理调用链,剖析NamedThreadLocal来管理数据库连接的过程。
在这里插入图片描述

(1)Controller层方法‌

作为事务调用的入口点,通常接收HTTP请求并调用Service层方法。Controller本身不直接参与事务管理,但它是事务传播的起点。

(2)Service层方法‌

业务逻辑的核心实现层,通常使用@Transactional注解标记需要进行事务管理的方法。Spring会在运行时为这些Service类创建代理对象。对Service类创建代理对象的核心目的是在不修改原有业务逻辑的前提下,通过代理实现功能增强、解耦与扩展。事务管理便是其功能增强之一,将事务管理等切面逻辑集中在代理中,实现代码复用,减少重复开发。

(3)CglibAopProxy‌

从上图可以看出,Spring AOP采用的是CGLIB代理模式。当目标类没有实现接口时,Spring会使用CGLIB生成子类来创建代理对象,拦截所有方法调用并织入事务管理等横切逻辑。上图中,OrderServicelmpl E n h a n c e r B y S p r i n g C G L I B EnhancerBySpringCGLIB EnhancerBySpringCGLIB1ac47b72就是我们的service类的代理对象。

这里可能会有一个疑问,我们的service类明明是实现了接口的,那为什么还用CGLIB代理而不是JDK动态代理‌?

@Service
public class OrderServiceImpl extends ServiceImpl<OrderMapper, Order> implements IOrderService {
......
}

这是因为Spring Boot 环境下默认开启 proxy-target-class=true,强制使用类代理(CGLIB)‌,即使目标类实现了接口。这一配置确保了对 ServiceImpl 具体方法的增强逻辑能被正确应用,同时避免接口代理的局限性。

这点可以从Spring Boot自动配置的jar中的json元数据中的配置可以验证。比如

spring-boot-autoconfigure-2.4.5.jar中的additional-spring-configuration-metadata.json有如下配置:

{
  "name": "spring.aop.auto",
  "type": "java.lang.Boolean",
  "description": "Add @EnableAspectJAutoProxy.",
  "defaultValue": true
},
{
  "name": "spring.aop.proxy-target-class",
  "type": "java.lang.Boolean",
  "description": "Whether subclass-based (CGLIB) proxies are to be created (true), as opposed to standard Java interface-based proxies (false).",
  "defaultValue": true
},

spring.aop.auto=true (表示启用AOP)
spring.aop.proxy-target-class=true (表示强制CGLIB)

(4)ReflectiveMethodInvocation‌

该类封装了被代理的目标方法、参数等信息,通过proceed()方法沿着拦截器链依次执行各个拦截器。

(5)TransactionInterceptor‌

Spring事务管理的核心拦截器,实现了MethodInterceptor接口。它负责在目标方法执行前后进行事务处理,包括开启事务、提交事务或回滚事务。

(6)TransactionAspectSupport‌

事务切面支持基类,为事务拦截器提供基础设施支持。它包含了事务处理的主要逻辑,如获取事务属性、创建事务、处理回滚等。在这个类里,我们终于看到了NamedThreadLocal的使用。

/**
 * Holder to support the {@code currentTransactionStatus()} method,
 * and to support communication between different cooperating advices
 * (e.g. before and after advice) if the aspect involves more than a
 * single method (as will be the case for around advice).
 */
private static final ThreadLocal<TransactionInfo> transactionInfoHolder =
        new NamedThreadLocal<>("Current aspect-driven transaction");

用于支持currentTransactionStatus()方法的持有者,以及当切面涉及多个方法时(对于around advice而言就是这种情况),在不同协同增强建议(如before和after advice)之间提供通信支持。

从源码可以看出,transactionInfoHolder对象为当前线程保存了一个 TransactionInfo 对象,该对象包含了管理事务所需的关键信息,例如事务状态 (TransactionStatus)、事务属性等
,这确保了在同一线程内执行的多个数据库操作可以共享同一个事务上下文。

/**
 * Opaque object used to hold transaction information. Subclasses
 * must pass it back to methods on this class, but not see its internals.
 */
protected static final class TransactionInfo {

    @Nullable
    private final PlatformTransactionManager transactionManager;

    @Nullable
    private final TransactionAttribute transactionAttribute;

    private final String joinpointIdentification;

    @Nullable
    private TransactionStatus transactionStatus;

    @Nullable
    private TransactionInfo oldTransactionInfo;

    public TransactionInfo(@Nullable PlatformTransactionManager transactionManager,
            @Nullable TransactionAttribute transactionAttribute, String joinpointIdentification) {

        this.transactionManager = transactionManager;
        this.transactionAttribute = transactionAttribute;
        this.joinpointIdentification = joinpointIdentification;
    }

    public PlatformTransactionManager getTransactionManager() {
        Assert.state(this.transactionManager != null, "No PlatformTransactionManager set");
        return this.transactionManager;
    }

    @Nullable
    public TransactionAttribute getTransactionAttribute() {
        return this.transactionAttribute;
    }

    /**
     * Return a String representation of this joinpoint (usually a Method call)
     * for use in logging.
     */
    public String getJoinpointIdentification() {
        return this.joinpointIdentification;
    }

    public void newTransactionStatus(@Nullable TransactionStatus status) {
        this.transactionStatus = status;
    }

    @Nullable
    public TransactionStatus getTransactionStatus() {
        return this.transactionStatus;
    }

    /**
     * Return whether a transaction was created by this aspect,
     * or whether we just have a placeholder to keep ThreadLocal stack integrity.
     */
    public boolean hasTransaction() {
        return (this.transactionStatus != null);
    }

    private void bindToThread() {
        // Expose current TransactionStatus, preserving any existing TransactionStatus
        // for restoration after this transaction is complete.
        this.oldTransactionInfo = transactionInfoHolder.get();
        transactionInfoHolder.set(this);
    }

    private void restoreThreadLocalStatus() {
        // Use stack to restore old transaction TransactionInfo.
        // Will be null if none was set.
        transactionInfoHolder.set(this.oldTransactionInfo);
    }

    @Override
    public String toString() {
        return (this.transactionAttribute != null ? this.transactionAttribute.toString() : "No transaction");
    }
}

我们跟中下进入TransactionAspectSupport的invokeWithinTransaction()方法后事务的创建过程。

protected Object invokeWithinTransaction(Method method, @Nullable Class<?> targetClass,final InvocationCallback invocation) throws Throwable {
    ...
    final TransactionAttribute txAttr = (tas != null ? tas.getTransactionAttribute(method, targetClass) : null);        
    ...
    PlatformTransactionManager ptm = asPlatformTransactionManager(tm);
    final String joinpointIdentification = methodIdentification(method, targetClass, txAttr);   
    ...
    // Standard transaction demarcation with getTransaction and commit/rollback calls.    
    TransactionInfo txInfo = createTransactionIfNecessary(ptm, txAttr, joinpointIdentification);
    ...
}      

如果有需要则创建事务

protected TransactionInfo createTransactionIfNecessary(@Nullable PlatformTransactionManager tm,
			@Nullable TransactionAttribute txAttr, final String joinpointIdentification) {

		// If no name specified, apply method identification as transaction name.
		if (txAttr != null && txAttr.getName() == null) {
			txAttr = new DelegatingTransactionAttribute(txAttr) {
				@Override
				public String getName() {
					return joinpointIdentification;
				}
			};
		}

		TransactionStatus status = null;
		if (txAttr != null) {
			if (tm != null) {
                // 获取事务的状态
				status = tm.getTransaction(txAttr);
			}
			else {
				if (logger.isDebugEnabled()) {
					logger.debug("Skipping transactional joinpoint [" + joinpointIdentification +
							"] because no transaction manager has been configured");
				}
			}
		}
    	// 准备事务
		return prepareTransactionInfo(tm, txAttr, joinpointIdentification, status);
	}

从源码中可以看出,准备事务信息之前通过TransactionStatus接口检查事务的当前状态。TransactionStatus 接口是 Spring 事务管理的核心接口之一,主要用于在事务执行过程中获取状态信息和进行程序化控制。包括判断当前事务是否为新创建的事务、是否已被标记为只能回滚、否已完成、是否包含保存点,还支持通过编程方式将事务标记为回滚状态、保存点管理等功能。

(7)AbstractPlatformTransactionManager‌

获取事务的状态需要通过抽象类AbstractPlatformTransactionManager‌的getTransaction()方法完成。该抽象类是Spring事务管理器的抽象基类,实现了PlatformTransactionManager接口。它定义了事务管理的基本流程,包括事务的创建、提交、回滚和挂起恢复操作。

/**
 * This implementation handles propagation behavior. Delegates to
 * {@code doGetTransaction}, {@code isExistingTransaction}
 * and {@code doBegin}.
 * @see #doGetTransaction
 * @see #isExistingTransaction
 * @see #doBegin
 */
@Override
public final TransactionStatus getTransaction(@Nullable TransactionDefinition definition)
        throws TransactionException {

    // 当没有提供事务定义时,系统会使用默认的事务配置。这意味着如果方法上未明确使用@Transactional注解或未在配置中指定事务属性,框架将采用默认的事务管理行为。
    TransactionDefinition def = (definition != null ? definition : TransactionDefinition.withDefaults());

    Object transaction = doGetTransaction();
    boolean debugEnabled = logger.isDebugEnabled();

    if (isExistingTransaction(transaction)) {
        // 如果检测到存在事务 -> 则检查传播行为以确定应如何执行。
        return handleExistingTransaction(def, transaction, debugEnabled);
    }

    // 如果是新建事务,则检查新事务的定义设置
    if (def.getTimeout() < TransactionDefinition.TIMEOUT_DEFAULT) {
        throw new InvalidTimeoutException("Invalid transaction timeout", def.getTimeout());
    }

    // 如果未找到现有事务 -> 则检查传播行为以确定后续操作。
    if (def.getPropagationBehavior() == TransactionDefinition.PROPAGATION_MANDATORY) {
        throw new IllegalTransactionStateException(
                "No existing transaction found for transaction marked with propagation 'mandatory'");
    }
    else if (def.getPropagationBehavior() == TransactionDefinition.PROPAGATION_REQUIRED ||
            def.getPropagationBehavior() == TransactionDefinition.PROPAGATION_REQUIRES_NEW ||
            def.getPropagationBehavior() == TransactionDefinition.PROPAGATION_NESTED) {
        SuspendedResourcesHolder suspendedResources = suspend(null);
        if (debugEnabled) {
            logger.debug("Creating new transaction with name [" + def.getName() + "]: " + def);
        }
        try {
            //开启事务
            return startTransaction(def, transaction, debugEnabled, suspendedResources);
        }
        catch (RuntimeException | Error ex) {
            resume(null, suspendedResources);
            throw ex;
        }
    }
    else {
        // 创建“空”事务:无实际事务,但可能存在同步。“空”事务通常是指只读的事务,方法被标记为@Transactional(readOnly = true)
        if (def.getIsolationLevel() != TransactionDefinition.ISOLATION_DEFAULT && logger.isWarnEnabled()) {
            logger.warn("Custom isolation level specified but no actual transaction initiated; " +
                    "isolation level will effectively be ignored: " + def);
        }
        boolean newSynchronization = (getTransactionSynchronization() == SYNCHRONIZATION_ALWAYS);
        return prepareTransactionStatus(def, null, true, newSynchronization, debugEnabled, null);
    }
}

开启事务

/**
 * Start a new transaction.
 */
private TransactionStatus startTransaction(TransactionDefinition definition, Object transaction,
        boolean debugEnabled, @Nullable SuspendedResourcesHolder suspendedResources) {

    boolean newSynchronization = (getTransactionSynchronization() != SYNCHRONIZATION_NEVER);
    DefaultTransactionStatus status = newTransactionStatus(
            definition, transaction, true, newSynchronization, debugEnabled, suspendedResources);
    doBegin(transaction, definition);
    prepareSynchronization(status, definition);
    return status;
}

(8)DataSourceTransactionManager‌

抽象类AbstractPlatformTransactionManager‌定义了事务管理的基本流程,包括事务的创建、提交、回滚和挂起恢复操作,具体的实现则需要由子类去实现。DataSourceTransactionManager‌就是其实现类之一,针对JDBC数据源的具体事务管理器实现。它管理数据库连接的事务状态,负责将事务的开启、提交和回滚操作委托给底层的JDBC连接。

protected void doBegin(Object transaction, TransactionDefinition definition) {
    DataSourceTransactionObject txObject = (DataSourceTransactionObject)transaction;
    Connection con = null;

    try {
        if (!txObject.hasConnectionHolder() || txObject.getConnectionHolder().isSynchronizedWithTransaction()) {
            Connection newCon = this.obtainDataSource().getConnection();
            if (this.logger.isDebugEnabled()) {
                this.logger.debug("Acquired Connection [" + newCon + "] for JDBC transaction");
            }

            txObject.setConnectionHolder(new ConnectionHolder(newCon), true);
        }

        txObject.getConnectionHolder().setSynchronizedWithTransaction(true);
        con = txObject.getConnectionHolder().getConnection();
        Integer previousIsolationLevel = DataSourceUtils.prepareConnectionForTransaction(con, definition);
        txObject.setPreviousIsolationLevel(previousIsolationLevel);
        txObject.setReadOnly(definition.isReadOnly());
        if (con.getAutoCommit()) {
            txObject.setMustRestoreAutoCommit(true);
            if (this.logger.isDebugEnabled()) {
                this.logger.debug("Switching JDBC Connection [" + con + "] to manual commit");
            }

            con.setAutoCommit(false);
        }

        this.prepareTransactionalConnection(con, definition);
        txObject.getConnectionHolder().setTransactionActive(true);
        int timeout = this.determineTimeout(definition);
        if (timeout != -1) {
            txObject.getConnectionHolder().setTimeoutInSeconds(timeout);
        }

        if (txObject.isNewConnectionHolder()) {
            TransactionSynchronizationManager.bindResource(this.obtainDataSource(), txObject.getConnectionHolder());
        }

    } catch (Throwable ex) {
        if (txObject.isNewConnectionHolder()) {
            DataSourceUtils.releaseConnection(con, this.obtainDataSource());
            txObject.setConnectionHolder((ConnectionHolder)null, false);
        }

        throw new CannotCreateTransactionException("Could not open JDBC Connection for transaction", ex);
    }
}

(9)TransactionAspectSupport,将事务信息绑定当当前线程

获取了事务状态之后回到TransactionAspectSupport,下一步就是将事务信息绑定当当前线程。

/**
 * Prepare a TransactionInfo for the given attribute and status object.
 * @param txAttr the TransactionAttribute (may be {@code null})
 * @param joinpointIdentification the fully qualified method name
 * (used for monitoring and logging purposes)
 * @param status the TransactionStatus for the current transaction
 * @return the prepared TransactionInfo object
 */
protected TransactionInfo prepareTransactionInfo(@Nullable PlatformTransactionManager tm,
        @Nullable TransactionAttribute txAttr, String joinpointIdentification,
        @Nullable TransactionStatus status) {

    TransactionInfo txInfo = new TransactionInfo(tm, txAttr, joinpointIdentification);
    if (txAttr != null) {
        // We need a transaction for this method...
        if (logger.isTraceEnabled()) {
            logger.trace("Getting transaction for [" + txInfo.getJoinpointIdentification() + "]");
        }
        // The transaction manager will flag an error if an incompatible tx already exists.
        txInfo.newTransactionStatus(status);
    }
    else {
        // The TransactionInfo.hasTransaction() method will return false. We created it only
        // to preserve the integrity of the ThreadLocal stack maintained in this class.
        if (logger.isTraceEnabled()) {
            logger.trace("No need to create transaction for [" + joinpointIdentification +
                    "]: This method is not transactional.");
        }
    }

    // We always bind the TransactionInfo to the thread, even if we didn't create
    // a new transaction here. This guarantees that the TransactionInfo stack
    // will be managed correctly even if no transaction was created by this aspect.
    txInfo.bindToThread();
    return txInfo;
}

准备好事务信息后调用内部类TransactionInfo中的bindToThread()方法将当前事务绑定到当前线程。

private void bindToThread() {
    // 暴露当前 TransactionStatus,保留任何现有的 TransactionStatus
    // 以便在此事务完成后进行恢复
    this.oldTransactionInfo = transactionInfoHolder.get();
    transactionInfoHolder.set(this);
}

获取事务状态则通过currentTransactionInfo()方法实现。

@Nullable
protected static TransactionInfo currentTransactionInfo() throws NoTransactionException {
    return transactionInfoHolder.get();
}

(10)TransactionInterceptor,清理事务信息

回到Spring事务管理的核心拦截器,invokeWithinTransaction()方法的finally块中,会调用cleanupTransactionInfo()来确保事务结束后相关资源被正确清理。

/**
 * Reset the TransactionInfo ThreadLocal.
 * <p>Call this in all cases: exception or normal return!
 * @param txInfo information about the current transaction (may be {@code null})
 */
protected void cleanupTransactionInfo(@Nullable TransactionInfo txInfo) {
    if (txInfo != null) {
        txInfo.restoreThreadLocalStatus();
    }
}

restoreThreadLocalStatus()方法是TransactionAspectSupport‌中的方法

private void restoreThreadLocalStatus() {
    // Use stack to restore old transaction TransactionInfo.
    // Will be null if none was set.
    transactionInfoHolder.set(this.oldTransactionInfo);
}

从源码可以看出,使用栈来恢复旧的 TransactionInfo 事务信息, 如果之前没有设置过,则为 null。

到此,我们已经理清事务存储到NamedThreadLocal的过程,每个线程独立存储自己的事务信息,这也是spring在多线程环境下保证事务安全的重要手段之一。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

帧栈

您的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值