通常情况下,我们会将登录用户的相关信息,存放到threadLocal当中,以便于我们在代码中获取用户信息,但是threadLocal的数据只存在于当前请求线程中,对于分布式微服务场景,如何将threaLocal中的数据,进行跨服务传递,需要我们思考解决。
核心需要解决的两个问题是:
1.如何将当前服务的threadLocal数据传递给下游服务?
2.下游服务如何拿到上游服务的threadLocal信息并塞到当前线程的threadLocal当中?
对于第一个问题,我们可以借助http Header,将threadLocal信息封装到header当中,传递给被调用的服务,微服务场景下,利用feign的拦截器,可以简化这种封装逻辑。自定义CustomFeignInterceptor 实现 RequestInterceptor,重写apply方法,将当前线程的threadLocal信息,封装到feign请求模板的header当中,需要注意的是,这种传递threadLocal的逻辑,是需要定义为全局逻辑,还是指定某个feignClient逻辑,如果定义为全局拦截逻辑,需要配合@Configuration,将CustomFeignInterceptor Bean注入到父容器中,参考代码如下:
@Slf4j public class CustomFeignInterceptor implements RequestInterceptor { @Override public void apply(RequestTemplate requestTemplate) { buildHead(requestTemplate); String headers = JSON.toJSONString(HttpContext.getHeaders()); if (!"{}".equals(headers)) { requestTemplate.header(FocoConstants.HTTP_CONTEXT, headers); } } private void buildHead(RequestTemplate requestTemplate) { //传递loginUser参数 PageParam pageParam = ThreadPagingUtil.get(); String pageValue = null; if (pageParam != null) { pageValue = JSON.toJSONString(pageParam); } String loginContext = LoginContextHolder.get(true); if(!"{}".equals(loginContext)){ requestTemplate .header(LoginContextConstant.LOGIN_CONTEXT, loginContext); } FocoContextManager.setHeader((header,focoContext)->{ if(!"{}".equals(focoContext)){ requestTemplate.header(header,focoContext); } }); requestTemplate .header(FocoConstants.ORIGINAL, FocoConstants.FEIGN_ORIGINAL) .header(FocoConstants.PAGE, pageValue); } }
@Slf4j @Configuration public class FeignAutoConfiguration { /** * 自定义Feign请求拦截器 */ @Bean public CustomFeignInterceptor loginContextFeignInterceptor() { return new CustomFeignInterceptor(); } /** * 自定义Feign 错误解码器 */ @Bean @ConditionalOnProperty(prefix = "feign.hystrix", name = FocoConstants.ENABLED,havingValue = "false",matchIfMissing = true) public FeignErrorDecoder feignErrorDecoder() { return new FeignErrorDecoder(); } @Bean FeignExceptionHandler feignExceptionHandler(){ return new FeignExceptionHandler(); } }
对于第二个问题,可以自定义spring拦截器,获取请求头中的threadLocal信息,塞入当前线程的threadLocal中,即可完成跨服务的threadLocal传递,参考代码如下:
@Slf4j public class LoginContextInterceptor extends HandlerInterceptorAdapter { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) { if (request != null) { String loginContextStr = request.getHeader(LoginContextConstant.LOGIN_CONTEXT); if (StrUtil.isNotBlank(loginContextStr)) { LoginContextHolder.set(loginContextStr); } FocoContextManager.setLocal((focoContextHeader)-> request.getHeader(focoContextHeader)); String headerMap = request.getHeader(FocoConstants.HTTP_CONTEXT); if(StrUtil.isNotBlank(headerMap)){ HttpContext.setHeaders(JSON.parseObject(headerMap, Map.class)); } } return true; } @Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) { LoginContextHolder.remove(); HttpContext.cleanHeaders(); FocoContextManager.remove(); } }
需要注意的是,在请求结束后,需要remove线程中threadLocal数据,因为请求线程被tomcat回收后,不一定会立即销毁,如果不在请求结束后主动remove 线程中的threadLocal信息,可能会影响后续请求,拿到脏数据。
总结,利用请求头传递threadLocal在实际生产中非常实用,除此之外,还有一些特殊场景,也可能需要考虑threadLocal的传递,例如MQ消息,发送接收时,可以将threadLocal信息放在消息头中,进行传递。类似这种解决方案基本是整个公司项目通用,建议将上述处理逻辑封装成starter,通过spirng的SPI机制,注入到目标服务。
标签:解决方案,threadLocal,传递,header,ThreadLocal,线程,requestTemplate,public From: https://www.cnblogs.com/itcq/p/17173158.html