为什么我可以在拦截其中BaseContext.setCurrentId(empId);然后在controller中通过BaseContext.getCurrentId(empId);拿到值

系列博客目录



1.为什么我可以在拦截其中BaseContext.setCurrentId(empId);然后在controller中通过BaseContext.getCurrentId(empId);拿到值。

你能够在拦截器中调用 BaseContext.setCurrentId(empId),并在控制器中通过 BaseContext.getCurrentId() 拿到值,是因为 ThreadLocal 的作用。ThreadLocal 可以让你在同一个线程内跨方法传递数据,而这些数据是线程独立的。

主要原因是 ThreadLocal 如何工作

1. ThreadLocal 是基于线程的

ThreadLocal 会为每个线程维护一份独立的变量副本。当你在拦截器中调用 BaseContext.setCurrentId(empId) 时,数据会存储在当前请求的线程的 ThreadLocal 存储中。同一请求线程中后续所有的方法调用都可以访问到这份存储。

  • 在拦截器中调用 BaseContext.setCurrentId(empId),将 empId 设置到当前线程的 ThreadLocal 存储中。
  • 在控制器中调用 BaseContext.getCurrentId(),可以从当前线程的 ThreadLocal 中获取先前存储的数据(即 empId)。

2. 请求线程的独立性

当 Web 应用处理一个 HTTP 请求时,通常每个请求都会在一个独立的线程中执行。因此,所有在同一线程内执行的操作(例如拦截器和控制器中的方法)都可以共享同一个 ThreadLocal 变量的数据。

  • 在拦截器中设置 BaseContext.setCurrentId(empId) 时,empId 会被保存在当前请求所使用的线程中的 ThreadLocal 变量里。
  • 当请求到达控制器时,同一线程仍然在执行,所以控制器可以通过 BaseContext.getCurrentId() 获取到相同的 empId

工作过程:

  1. 拦截器阶段

    • 在请求的拦截器中,你调用了 BaseContext.setCurrentId(empId),此时 empId 被保存在当前请求线程的 ThreadLocal 存储中。
    public class MyInterceptor implements HandlerInterceptor {
        @Override
        public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
            Long empId = getEmpIdFromRequest(request);
            BaseContext.setCurrentId(empId);  // 将 empId 设置到当前线程的 ThreadLocal 中
            return true;
        }
    }
    
  2. 控制器阶段

    • 在控制器中,通过 BaseContext.getCurrentId() 获取当前线程的 ThreadLocal 中的 empId,因为该线程中存储了拦截器中设置的数据。
    @RestController
    public class MyController {
        @GetMapping("/getEmpId")
        public Long getEmpId() {
            return BaseContext.getCurrentId();  // 从当前线程的 ThreadLocal 中获取 empId
        }
    }
    

关键点:

  • 线程隔离ThreadLocal 保证每个线程有独立的存储,因此,在同一线程中跨方法调用时,能够共享 ThreadLocal 存储的数据。
  • 拦截器和控制器共享同一线程:虽然拦截器和控制器是不同的组件,但它们处理同一个 HTTP 请求时,通常是由同一个线程来执行的,因此它们都能访问到同一个 ThreadLocal 存储的数据。

举个例子:

假设你在请求的拦截器中设置了 empId,然后在控制器中获取它。

拦截器代码:

public class MyInterceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        // 从请求中获取 empId
        Long empId = 123L;
        // 设置到当前线程的 ThreadLocal 中
        BaseContext.setCurrentId(empId);
        return true;  // 放行请求,继续执行后续处理
    }
}

控制器代码:

@RestController
public class MyController {
    @GetMapping("/getEmpId")
    public Long getEmpId() {
        // 获取当前线程的 ThreadLocal 中存储的 empId
        return BaseContext.getCurrentId();
    }
}

过程:

  1. 请求到达拦截器:拦截器会通过 BaseContext.setCurrentId(empId)empId 存储在当前线程的 ThreadLocal 中。
  2. 请求到达控制器:控制器中的 BaseContext.getCurrentId() 会从当前线程的 ThreadLocal 中获取到存储的 empId

总结:

  • 你在拦截器中通过 BaseContext.setCurrentId(empId) 存储了数据到当前线程的 ThreadLocal 中。
  • 在控制器中,通过 BaseContext.getCurrentId() 获取同一个线程中的 ThreadLocal 数据,从而拿到 empId
  • ThreadLocal 确保了每个线程有独立的存储空间,因此在同一线程中的不同方法之间,可以共享数据。

2.为什么两次BaseContext不是产生两个独立的对象

在 Java 中,BaseContext.setCurrentId(empId)BaseContext.getCurrentId() 调用并不会产生两个独立的对象,而是使用了同一个 ThreadLocal 对象。这是因为 ThreadLocal 的设计本身就是为了在同一个线程内共享数据。我们来逐步解析这个问题。

为什么不会产生两个独立的对象?

  1. ThreadLocal 是单一对象
    BaseContext 中定义的 ThreadLocal 变量 threadLocal 是类级别的静态变量,它只有一个实例,所有调用 setCurrentIdgetCurrentId 方法的代码都会访问同一个 ThreadLocal 对象。

    public class BaseContext {
        public static ThreadLocal<Long> threadLocal = new ThreadLocal<>();
        
        public static void setCurrentId(Long id) {
            threadLocal.set(id);  // 设置当前线程的 threadLocal 数据
        }
    
        public static Long getCurrentId() {
            return threadLocal.get();  // 获取当前线程的 threadLocal 数据
        }
    
        public static void removeCurrentId() {
            threadLocal.remove();  // 清除当前线程的 threadLocal 数据
        }
    }
    
    • 这个 threadLocal 是一个静态成员变量,意味着它是 属于类的,而不是实例化的对象。它在类加载时创建,并且在整个应用生命周期内只有一个实例。
    • 所以无论调用多少次 setCurrentIdgetCurrentId 方法,都在操作同一个 ThreadLocal 对象。
  2. ThreadLocal 的作用域是线程级别的
    ThreadLocal 是按线程隔离的,每个线程都有自己独立的存储空间,因此它允许在同一线程中的不同方法之间共享数据。但 它不会为每个方法或每次调用创建新的 ThreadLocal 对象

    • 在你的代码中,调用 BaseContext.setCurrentId(empId)BaseContext.getCurrentId() 实际上都在访问 同一个 ThreadLocal,只是操作的是当前线程内部的独立存储(由 ThreadLocal 管理)。
    • 也就是说,虽然调用了 setget,它们是在 同一个线程内操作 ThreadLocal 中的数据,不会在每次调用时创建新的 ThreadLocal 对象。
  3. ThreadLocal 的设计是线程隔离
    ThreadLocal 是为每个线程提供独立存储的。它会为每个线程维护一个独立的副本,但在 同一个线程内,所有对该线程的 ThreadLocal 变量的访问都是共享的。

    • 当你在一个线程中调用 BaseContext.setCurrentId(empId) 时,empId 会被保存在该线程的 ThreadLocal 存储中。
    • 当你在同一个线程中调用 BaseContext.getCurrentId() 时,你拿到的就是该线程的 ThreadLocal 存储中的值。

重要的概念:

  1. 线程隔离
    每个线程有一个独立的 ThreadLocal 存储区域。这意味着,同一个 ThreadLocal 对象不会在不同线程之间共享数据,而是在每个线程内保持独立性。

  2. 单一的 ThreadLocal 对象
    在类中定义的 ThreadLocal 是单一的,它只会有一个实例。在同一个线程中,它允许不同方法之间共享数据,因为这些方法访问的是同一个 ThreadLocal 存储区域。

示例:

假设你有以下代码:

public class MyClass {
    private static ThreadLocal<String> threadLocal = new ThreadLocal<>();
    
    public static void setCurrentValue(String value) {
        threadLocal.set(value);
    }
    
    public static String getCurrentValue() {
        return threadLocal.get();
    }
}

在同一个线程中,调用 setCurrentValuegetCurrentValue 会操作同一个 ThreadLocal 存储。

示例调用:

public class Test {
    public static void main(String[] args) {
        MyClass.setCurrentValue("Hello, World!");
        String value = MyClass.getCurrentValue();
        System.out.println(value);  // 输出: Hello, World!
    }
}

运行过程:

  1. 当调用 MyClass.setCurrentValue("Hello, World!") 时,"Hello, World!" 被保存在当前线程的 ThreadLocal 存储中。
  2. 当调用 MyClass.getCurrentValue() 时,它会从当前线程的 ThreadLocal 存储中获取到 Hello, World!

为什么不会生成多个 ThreadLocal 实例?

  • 因为 threadLocal 变量是 静态的,它只会在类加载时创建一个实例,不会根据方法调用、线程调用等多次生成新实例。
  • 不同方法之间并不会重新创建 ThreadLocal 对象,它们操作的是同一个 ThreadLocal 变量。

总结:

  • BaseContext.setCurrentIdBaseContext.getCurrentId 操作的是同一个 ThreadLocal 变量,且 ThreadLocal 在类中只有一个实例。
  • ThreadLocal 是基于线程的,它在同一个线程内允许方法之间共享数据,但不会在每次调用时创建新的对象。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值