疫情退散!
由于疫情原因,导致朋友不能有问题口口相传,只能通过电话沟通了。
这个问题说起来大概是这个样子的:
系统中在请求的拦截器通过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传递给另起线程。完成目的。