You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Spring Boot中如何将通用请求数据存入上下文实现全局访问?

在Spring Boot中实现请求级上下文统一管理时区、Locale等数据

核心思路

利用ThreadLocal存储请求级别的上下文数据,结合Spring的**拦截器(HandlerInterceptor)**在请求入口处初始化上下文,请求结束后清理,确保下游代码可以直接静态访问这些数据,避免重复查询。

实现步骤

1. 定义自定义上下文类MyContext

用ThreadLocal封装上下文数据,提供静态方法获取/设置/清理数据,同时处理默认值避免空指针:

public class MyContext {
    private static final ThreadLocal<ContextData> THREAD_LOCAL = new ThreadLocal<>();

    public static class ContextData {
        private TimeZone timeZone;
        private Locale locale;
        private String language;

        // Getter & Setter
        public TimeZone getTimeZone() { return timeZone; }
        public void setTimeZone(TimeZone timeZone) { this.timeZone = timeZone; }
        public Locale getLocale() { return locale; }
        public void setLocale(Locale locale) { this.locale = locale; }
        public String getLanguage() { return language; }
        public void setLanguage(String language) { this.language = language; }
    }

    public static void setContext(ContextData contextData) {
        THREAD_LOCAL.set(contextData);
    }

    public static ContextData getContext() {
        ContextData data = THREAD_LOCAL.get();
        if (data == null) {
            data = new ContextData();
            // 设置系统默认值作为兜底
            data.setTimeZone(TimeZone.getDefault());
            data.setLocale(Locale.getDefault());
            data.setLanguage(Locale.getDefault().getLanguage());
        }
        return data;
    }

    // 快捷获取方法
    public static TimeZone getTimeZone() {
        return getContext().getTimeZone();
    }

    public static Locale getLocale() {
        return getContext().getLocale();
    }

    public static String getLanguage() {
        return getContext().getLanguage();
    }

    // 必须调用,防止内存泄漏
    public static void clear() {
        THREAD_LOCAL.remove();
    }
}

2. 编写拦截器初始化上下文

在请求到达控制器前,查询数据库获取用户/请求对应的配置,注入到MyContext中,请求结束后清理:

@Component
public class ContextInterceptor implements HandlerInterceptor {

    @Autowired
    private UserSettingService userSettingService; // 自定义的查询配置的服务

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        // 从请求中获取用户标识(示例:从请求头取userId)
        String userId = request.getHeader("X-User-Id");
        if (userId != null) {
            // 查询数据库获取用户配置
            UserSetting setting = userSettingService.getSettingByUserId(userId);
            MyContext.ContextData contextData = new MyContext.ContextData();
            contextData.setTimeZone(TimeZone.getTimeZone(setting.getTimeZone()));
            contextData.setLocale(new Locale(setting.getLanguage()));
            contextData.setLanguage(setting.getLanguage());
            MyContext.setContext(contextData);
        }
        return true;
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        // 请求结束强制清理ThreadLocal,避免线程池复用导致上下文污染
        MyContext.clear();
    }
}

3. 注册拦截器到Spring MVC

让拦截器生效,指定拦截/排除路径:

@Configuration
public class WebMvcConfig implements WebMvcConfigurer {

    @Autowired
    private ContextInterceptor contextInterceptor;

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(contextInterceptor)
                .addPathPatterns("/**") // 拦截所有请求
                .excludePathPatterns("/static/**", "/error"); // 排除静态资源和错误页
    }
}

4. 下游代码直接使用

在控制器、服务类中直接调用静态方法获取数据:

@RestController
@RequestMapping("/api")
public class DemoController {

    @GetMapping("/info")
    public ResponseEntity<String> getContextInfo() {
        return ResponseEntity.ok(String.format(
                "时区:%s,Locale:%s,语言:%s",
                MyContext.getTimeZone().getID(),
                MyContext.getLocale(),
                MyContext.getLanguage()
        ));
    }
}

@Service
public class DemoService {
    public void processBusiness() {
        Locale locale = MyContext.getLocale();
        // 根据Locale处理国际化业务逻辑
    }
}

注意事项

  • ThreadLocal清理:必须在请求结束时调用MyContext.clear(),否则线程池复用会导致上下文数据串用,引发bug。
  • 异步请求处理:如果使用@Async异步方法,ThreadLocal上下文不会自动传递,需要通过TaskDecorator封装上下文,或者在异步方法内重新初始化。
  • 缓存优化:频繁查询数据库的话,可以给用户配置加Redis缓存,减少DB压力。
  • 兜底逻辑:MyContext中要设置默认值,避免未获取到用户配置时出现空指针。

内容的提问来源于stack exchange,提问作者Harsha Limaye

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.27 21:33:16