关于RequestContextHolder遇到的小问题

2,225 阅读2分钟

疫情退散!

由于疫情原因,导致朋友不能有问题口口相传,只能通过电话沟通了。

这个问题说起来大概是这个样子的:

        系统中在请求的拦截器通过RequestContextHolder获取Request中的Header透传下去以及zuul的机器选择时通过Header区分,相关代码如下:

public class CoreFeignRequestInterceptor implements RequestInterceptor {   
 public CoreFeignRequestInterceptor() {    }  
 public void apply(RequestTemplate template) {       
     ServletRequestAttributes attributes = (ServletRequestAttributes)RequestContextHolder.getRequestAttributes();  
     template.header("**", new String[]{(String)Optional.ofNullable(attributes).map((a) -> { return a.getRequest(); }).map((r) -> { return r.getHeader("**");        }).orElse("")});    
  }}

public class CoreHttpRequestInterceptor implements ClientHttpRequestInterceptor {   
   public CoreHttpRequestInterceptor() {    }    
   public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) throws IOException {        
       HttpRequestWrapper requestWrapper = new HttpRequestWrapper(request);        
       ServletRequestAttributes attributes = (ServletRequestAttributes)RequestContextHolder.getRequestAttributes();       
       requestWrapper.getHeaders().add("**", (String)Optional.ofNullable(attributes).map((a) -> {return a.getRequest();}).map((r) -> {return r.getHeader("**");}).orElse(""));       
       return execution.execute(requestWrapper, body);    }}

public class CanaryMetadataRule extends ZoneAvoidanceRule {    
    private static final Logger log = LoggerFactory.getLogger(CanaryMetadataRule.class);    
    public CanaryMetadataRule() {    }    
    public Server choose(Object key) {        
        List<Server> serverList = this.getPredicate().getEligibleServers(this.getLoadBalancer().getAllServers(), key);   
        if (CollectionUtils.isEmpty(serverList)) {            
            return null;       
        } else {         
            ServletRequestAttributes attributes = (ServletRequestAttributes)RequestContextHolder.getRequestAttributes();     
            String version = (String)Optional.ofNullable(attributes).map((a) -> { return a.getRequest(); }).map((r) -> { return r.getHeader("**");}).orElse("");  
            log.info("version header-->>" + version);            
            List<Server> noMetaServerList = new ArrayList();            
            Iterator var6 = serverList.iterator();            
            while(var6.hasNext()) {             
                Server server = (Server)var6.next();             
                Map<String, String> metadata = ((DiscoveryEnabledServer)server).getInstanceInfo().getMetadata();      
                String metaVersion = (String)metadata.get("**");             
                if (!StringUtils.isEmpty(metaVersion)) {               
                    if (metaVersion.equals(version)) {                    
                        log.info("choose canary-->>" + server.getMetaInfo().getAppName());  
                        return server;                   
                     }} else {                   
                        log.info("choose common-->>" + server.getMetaInfo().getAppName()); 
                        noMetaServerList.add(server);           
                     }
                 }          
                if (!noMetaServerList.isEmpty()) {            
                    return this.originChoose(noMetaServerList, key);     
                } else {               
                    return null;         
                }        }    }   
       private Server originChoose(List<Server> noMetaServerList, Object key) {       
             com.google.common.base.Optional<Server> server = this.getPredicate().chooseRoundRobinAfterFiltering(noMetaServerList, key);      
             return server.isPresent() ? (Server)server.get() : null;    
}}

OK  那么我来说下问题,当在同一个Request Scope里这样写没得问题,但是,当另起一个线程时,问题就出现了,ServletRequestAttributes attributes = (ServletRequestAttributes)RequestContextHolder.getRequestAttributes();这里拿header是拿不到的。

网上现有的一种解决方案,效果不佳,但是思路是适合我们的。

先上结果

@Bean("taskExecutor")
public AsyncTaskExecutor taskExecutor() {   
 ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();  
 executor.setCorePoolSize(2); 
 executor.setMaxPoolSize(100);  
 executor.setThreadNamePrefix("async-");  
 executor.setWaitForTasksToCompleteOnShutdown(true);  
 //关键在这一步,给线程池线程加个装饰器,把当前线程的header透传给线程池内线程
 executor.setTaskDecorator(new ContextDecorator());    
 executor.initialize();    
 return executor;
}

class ContextDecorator implements TaskDecorator {   
   @Override    
   public Runnable decorate(Runnable runnable) {     
       //在非request scope中调用此方法是会抛异常的,另外网上大部分的做法就是把这个当前线程的attributes对象直接给到Runnable
       //这种做法不适合我们,因为我们的逻辑代码为非阻塞,即通过@Async调用逻辑方法后直接返回response结束请求,
       //所以当请求结束后,即便GC不会清除这个对象,但是对象中保存header的map被清空了
       ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.currentRequestAttributes();      
       return () -> {           
           try {               
               //所以我们此处需要看对象有没有,没有就给new一个,然后通过反射塞值
               ServletRequestAttributes currentRequestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); 
               if (null == currentRequestAttributes){                    
                   org.apache.coyote.Request coyoteRequet = new org.apache.coyote.Request(); 
                   Request request = new Request();  
                   request.setCoyoteRequest(coyoteRequet); 
                   currentRequestAttributes = new ServletRequestAttributes(new RequestFacade(request));    
                } 
                RequestFacade httpServletRequest = (RequestFacade) currentRequestAttributes.getRequest(); 
              try {                   
                 Field request1 = httpServletRequest.getClass().getDeclaredField("request");
                 request1.setAccessible(true);
                 Object o = request1.get(httpServletRequest);
                 Field coyoteRequest = o.getClass().getDeclaredField("coyoteRequest");
                 coyoteRequest.setAccessible(true); 
                 Object o1 = coyoteRequest.get(o); 
                 Field headers = o1.getClass().getDeclaredField("headers");
                 headers.setAccessible(true);
                 MimeHeaders o2 = (MimeHeaders)headers.get(o1); 
                 o2.addValue("**").setString(Optional.ofNullable(attributes).map(ServletRequestAttributes::getRequest).map(r -> r.getHeader("**")).orElse(""));
                 RequestContextHolder.setRequestAttributes(currentRequestAttributes);  
               } catch (Exception e) { 
                   e.printStackTrace();
               }                
             runnable.run();    
        } finally {             
            RequestContextHolder.resetRequestAttributes();          
        }     
       };    
}}



异步方法@Async指定线程池taskExecutor。

至此,就可以将当前线程的request header传递给另起线程。完成目的。