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
相关产品推荐
相关产品推荐

