RequestContextHolder多线程获取不到request对象,调用feign接口时,在Feign中的RequestInterceptor也获取不到HttpServletRequest问题解决方案。
1.RequestContextHolder多线程获取不到request对象
异常信息,报错如下:
2024-07-09 22:06:32.320 [pool-5-thread-2] ERROR com.mergeplus.handler.ObjectHandler:227 - class: interface org.jeecg.common.system.api.client.SysFeignClient, methodName=mergeRegion, error: {}
java.lang.IllegalStateException: No thread-bound request found: Are you referring to request attributes outside of an actual web request, or processing a request outside of the originally receiving thread? If you are actually operating within a web request and still receive this message, your code is probably running outside of DispatcherServlet: In this case, use RequestContextListener or RequestContextFilter to expose the current request.
at org.springframework.web.context.request.RequestContextHolder.currentRequestAttributes(RequestContextHolder.java:131)
at org.springframework.web.context.support.WebApplicationContextUtils.currentRequestAttributes(WebApplicationContextUtils.java:313)
at org.springframework.web.context.support.WebApplicationContextUtils$RequestObjectFactory.getObject(WebApplicationContextUtils.java:329)
at org.springframework.web.context.support.WebApplicationContextUtils$RequestObjectFactory.getObject(WebApplicationContextUtils.java:324)
at org.springframework.beans.factory.support.AutowireUtils$ObjectFactoryDelegatingInvocationHandler.invoke(AutowireUtils.java:283)
at jdk.proxy2/jdk.proxy2.$Proxy179.getHeaderNames(Unknown Source)
at com.mergeplus.handler.ObjectHandler.lambda$doHandler$0(ObjectHandler.java:155)
at java.base/java.util.concurrent.CompletableFuture$AsyncSupply.run$$$capture(CompletableFuture.java:1768)
at java.base/java.util.concurrent.CompletableFuture$AsyncSupply.run(CompletableFuture.java)
at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1136)
at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:635)
at java.base/java.lang.Thread.run(Thread.java:833)
异常错误截图:
2.解决方案
// 设置子线程共享(重点)
final RequestAttributes attributes = RequestContextHolder.getRequestAttributes();
merges.stream().map(e -> CompletableFuture.supplyAsync(() -> {
JSONObject returnMap = null;
try {
if (e.getClientBean() != null) {
// 设置当前线程的 RequestAttributes(重点)
RequestContextHolder.setRequestAttributes(attributes);
Object returnValue = e.getMethod().invoke(e.getClientBean(), jsonObject.get(e.getSourceKey()));
if (returnValue == null) {
return e;
}
returnMap = JSON.parseObject(JSON.toJSONString(returnValue));
if (returnMap == null || returnMap.isEmpty()) {
return e;
}
result.put(e.getTargetKey(), returnMap.get(jsonObject.get(e.getSourceKey())));
} else {
if (e.getUrl() == null || e.getUrl().trim().length() == 0) {
return e;
}
//设置Http的Header
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
//设置访问参数
HashMap<String, Object> params = new HashMap<>();
if (e.getKey() == null || e.getKey().trim().length() == 0) {
params.put(e.getSourceKey(), jsonObject.get(e.getSourceKey()));
}
//设置访问的Entity
HttpEntity entity = new HttpEntity<>(params, headers);
// todo
ResponseEntity<Object> exchange = restTemplate.exchange(e.getUrl(), HttpMethod.GET, entity, Object.class);
if (exchange == null) {
return e;
}
Object body = exchange.getBody();
if (body == null) {
return e;
}
returnMap = JSON.parseObject(JSON.toJSONString(body));
if (returnMap == null || returnMap.isEmpty()) {
return e;
}
result.put(e.getTargetKey(), returnMap.get(jsonObject.get(e.getSourceKey())));
// if (body instanceof Map) {
// returnMap = (Map) body;
// if (returnMap == null || returnMap.isEmpty()) {
// return e;
// }
// result.put(e.getTargetKey(), returnMap.get(jsonObject.get(e.getSourceKey())));
// }
}
} catch (Exception ex) {
log.error("class: {}, methodName={}, error: {}", e.getClientBeanClazz(), e.getMethod().getName(), ex);
} finally {
RequestContextHolder.resetRequestAttributes(); // 记得在最后重置请求属性(重点)
}
return e;
}, executor)).toList().stream().map(CompletableFuture::join).collect(Collectors.toList());
FeignInterceptorConfig代码
package org.jeecg.config;
import java.io.IOException;
import java.util.*;
import feign.RequestTemplate;
import jakarta.servlet.http.HttpServletRequest;
import org.apache.commons.lang3.StringUtils;
import org.jeecg.common.config.mqtoken.UserTokenContext;
import org.jeecg.common.constant.CommonConstant;
import org.jeecg.common.util.DateUtils;
import org.jeecg.common.util.PathMatcherUtil;
import org.jeecg.common.util.SpringContextUtils;
import org.jeecg.common.util.TokenUtils;
import org.jeecg.config.sign.interceptor.SignAuthConfiguration;
import org.jeecg.config.sign.util.HttpUtils;
import org.jeecg.config.sign.util.SignUtil;
import org.springframework.beans.factory.ObjectFactory;
import org.springframework.boot.autoconfigure.AutoConfigureBefore;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.http.HttpMessageConverters;
import org.springframework.cloud.openfeign.FeignAutoConfiguration;
import org.springframework.cloud.openfeign.support.SpringDecoder;
import org.springframework.cloud.openfeign.support.SpringEncoder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.context.annotation.Scope;
import org.springframework.http.MediaType;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.serializer.SerializerFeature;
import com.alibaba.fastjson.support.config.FastJsonConfig;
import com.alibaba.fastjson.support.spring.FastJsonHttpMessageConverter;
import feign.Feign;
import feign.Logger;
import feign.RequestInterceptor;
import feign.codec.Decoder;
import feign.codec.Encoder;
import feign.form.spring.SpringFormEncoder;
import lombok.extern.slf4j.Slf4j;
/**
* @Description: FeignConfig
* @author: JeecgBoot
*/
@ConditionalOnClass(Feign.class)
@AutoConfigureBefore(FeignAutoConfiguration.class)
@Slf4j
@Configuration
public class FeignInterceptorConfig {
@Bean
public RequestInterceptor requestInterceptor() {
return requestTemplate -> {
ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
if (requestAttributes == null) {
return;
}
//1.获取请求对象
HttpServletRequest request = requestAttributes.getRequest();
Enumeration<String> headerNames = request.getHeaderNames();
if (headerNames == null || !headerNames.hasMoreElements()) {
return;
}
//2.获取请求对象中的所有的头信息(网关传递过来的)
while (headerNames.hasMoreElements()) {
String name = headerNames.nextElement();//头的名称
// 跳过content-length,不然可能会报too many bites written问题
if ("content-length".equalsIgnoreCase(name)) {
continue;
}
String value = request.getHeader(name);//头名称对应的值
// System.out.println("name:" + name + "::::::::value:" + value);
//3.将头信息传递给feign (restTemplate)
requestTemplate.header(name,value);
// if (CommonConstant.X_ACCESS_TOKEN.equals(name) && StringUtils.isBlank(value)) {
// String token = UserTokenContext.getToken();
// requestTemplate.header(CommonConstant.X_ACCESS_TOKEN, token);
// }
}
//================================================================================================================
//针对特殊接口,进行加签验证 ——根据URL地址过滤请求 【字典表参数签名验证】
// todo
if (PathMatcherUtil.matches(Arrays.asList(PathMatcherUtil.SIGN_URL_LIST),requestTemplate.path())) {
// if (PathMatcherUtil.matches(Arrays.asList(SignAuthConfiguration.SIGN_URL_LIST),requestTemplate.path())) {
try {
log.info("============================ [begin] fegin api url ============================");
log.info(requestTemplate.path());
log.info(requestTemplate.method());
String queryLine = requestTemplate.queryLine();
String questionMark="?";
if(queryLine!=null && queryLine.startsWith(questionMark)){
queryLine = queryLine.substring(1);
}
log.info(queryLine);
if(requestTemplate.body()!=null){
log.info(new String(requestTemplate.body()));
}
SortedMap<String, String> allParams = HttpUtils.getAllParams(requestTemplate.path(),queryLine,requestTemplate.body(),requestTemplate.method());
String sign = SignUtil.getParamsSign(allParams);
log.info(" Feign request params sign: {}",sign);
log.info("============================ [end] fegin api url ============================");
requestTemplate.header(CommonConstant.X_SIGN, sign);
requestTemplate.header(CommonConstant.X_TIMESTAMP, String.valueOf(System.currentTimeMillis()));
} catch (IOException e) {
e.printStackTrace();
}
}
//================================================================================================================
};
}
}
3.相关大数据学习demo地址:
https://github.com/carteryh/big-data