项目中有场景需要对接口进行登陆验证,获取请求head中的相关信息校验当前用户是否登录,但是接口中有调用下游接口也有类似校验,这时需要将请求的head信息透传到下游接口,网上搜索相关问题会找到如下方案:
@Component
public class FeignRequestInterceptor implements RequestInterceptor {
@Override
public void apply(RequestTemplate requestTemplate) {
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
if (attributes != null) {
HttpServletRequest request = attributes.getRequest();
Enumeration<String> headerNames = request.getHeaderNames();
if (headerNames != null) {
while (headerNames.hasMoreElements()) {
String name = headerNames.nextElement();
String values = request.getHeader(name);
requestTemplate.header(name, values);
}
}
}
}
}
创建一个Feign拦截器,获取当前请求中的head信息放入requestTemplate中。但是我这么写后发现attributes 为空,也就是通过RequestContextHolder.getRequestAttributes()获取不到请求。
debug查看相关代码后,发现代码走到拦截器中时,当前线程变成"hystrix-"开头的线程名称,并不是http线程池中的线程,由于从RequestContextHolder获取request时是从ThreadLocal中获取,线程变了当然也就获取不到了。因为线程变成"hystrix-"开头的线程名称所以猜测是由于开启了熔断导致的,网上搜索相关信息后才知道,开启熔断后feign调用会根据hystrix默认的并发策略,在单独的线程池中运行。
解决方案有三种:
1、关闭hystrix
修改配置
feign.hystrix.enabled=false
直接关闭hystrix当然是可以解决的,但是显然不合适
2、配置hystrix并发策略为信号量模式
添加配置
hystrix.command.default.execution.isolation.strategy=SEMAPHORE
所谓信号量模式就是不单独为每一个FeignClient分配线程池,而是限制每一个FeignClient调用的线程数,线程池还是用的http的线程池,feign调用线程不会变换就可以获取到request。但是Hystrix官方并不建议使用这种模式,特别是下游接口响应不快的时候会长时间http线程池影响性能。
3、自定义hystrix并发策略
继承HystrixConcurrencyStrategy类,覆写wrapCallable方法在callable外获取RequestContextHolder上下文信息,然后传入到callable内设置到对应上下文环境中。由于RequestContextHolder中的request可能会被http线程释放,所以建议创建自己的ContextHolder单独放head信息。
这里会遇到另一个问题,由于HystrixConcurrencyStrategy只能有一个,所以写个简单的HystrixConcurrencyStrategy注册到HystrixPlugins中,可能会导致以后引入Spring Security之类的依赖后被它覆盖。所以我参考了Spring Security写的SecurityContextConcurrencyStrategy写了一个类似的,它内部判断了当前已经存在的HystrixConcurrencyStrategy,然后在已存在的基础上进行包装,将多个HystrixConcurrencyStrategy层层嵌套解决兼容的问题。
以下为我改写的代码:
public class RequestHeaderHystrixConcurrencyStrategy extends HystrixConcurrencyStrategy {
private final HystrixConcurrencyStrategy existingConcurrencyStrategy;
public RequestHeaderHystrixConcurrencyStrategy(HystrixConcurrencyStrategy existingConcurrencyStrategy) {
this.existingConcurrencyStrategy = existingConcurrencyStrategy;
}
@Override
public BlockingQueue<Runnable> getBlockingQueue(int maxQueueSize) {
return existingConcurrencyStrategy != null ? existingConcurrencyStrategy.getBlockingQueue(maxQueueSize) : super.getBlockingQueue(maxQueueSize);
}
@Override
public <T> HystrixRequestVariable<T> getRequestVariable(HystrixRequestVariableLifecycle<T> rv) {
return existingConcurrencyStrategy != null ? existingConcurrencyStrategy.getRequestVariable(rv) : super.getRequestVariable(rv);
}
@Override
public ThreadPoolExecutor getThreadPool(HystrixThreadPoolKey threadPoolKey, HystrixProperty<Integer> corePoolSize, HystrixProperty<Integer> maximumPoolSize,
HystrixProperty<Integer> keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue) {
return existingConcurrencyStrategy != null ?
existingConcurrencyStrategy.getThreadPool(threadPoolKey, corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue) :
super.getThreadPool(threadPoolKey, corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue);
}
@Override
public ThreadPoolExecutor getThreadPool(HystrixThreadPoolKey threadPoolKey, HystrixThreadPoolProperties threadPoolProperties) {
return existingConcurrencyStrategy != null ?
existingConcurrencyStrategy.getThreadPool(threadPoolKey, threadPoolProperties) :
super.getThreadPool(threadPoolKey, threadPoolProperties);
}
@Override
public <T> Callable<T> wrapCallable(Callable<T> callable) {
Map<String, String> headers = AuthenticationContextHolder.getRequestHeaders();
SysUser userInfo = AuthenticationContextHolder.getUserInfo();
return existingConcurrencyStrategy != null ?
existingConcurrencyStrategy.wrapCallable(new WrappedCallable<T>(callable, headers, userInfo)) :
super.wrapCallable(new WrappedCallable<T>(callable, headers, userInfo));
}
static class WrappedCallable<T> implements Callable<T> {
private final Callable<T> callable;
private final Map<String, String> headers;
private final SysUser userInfo;
public WrappedCallable(Callable<T> callable, Map<String, String> headers, SysUser userInfo) {
this.callable = callable;
this.headers = headers;
this.userInfo = userInfo;
}
@Override
public T call() throws Exception {
try {
AuthenticationContextHolder.setRequestHeaders(headers);
AuthenticationContextHolder.setUserInfo(userInfo);
return callable.call();
} finally {
AuthenticationContextHolder.remove();
}
}
}
}
@Configuration(proxyBeanMethods = false)
@ConditionalOnProperty(name = "feign.hystrix.enabled", havingValue = "true")
@Slf4j
public class HystrixConcurrencyStrategyAutoConfiguration {
@Autowired(required = false)
private HystrixConcurrencyStrategy existingConcurrencyStrategy;
@PostConstruct
public void init() {
HystrixEventNotifier eventNotifier = HystrixPlugins.getInstance().getEventNotifier();
HystrixMetricsPublisher metricsPublisher = HystrixPlugins.getInstance().getMetricsPublisher();
HystrixPropertiesStrategy propertiesStrategy = HystrixPlugins.getInstance().getPropertiesStrategy();
HystrixCommandExecutionHook commandExecutionHook = HystrixPlugins.getInstance().getCommandExecutionHook();
HystrixConcurrencyStrategy concurrencyStrategy = detectRegisteredConcurrencyStrategy();
HystrixPlugins.reset();
// 注册Hystrix并发策略以外的插件
HystrixPlugins.getInstance().registerConcurrencyStrategy(new RequestHeaderHystrixConcurrencyStrategy(concurrencyStrategy));
HystrixPlugins.getInstance().registerEventNotifier(eventNotifier);
HystrixPlugins.getInstance().registerMetricsPublisher(metricsPublisher);
HystrixPlugins.getInstance().registerPropertiesStrategy(propertiesStrategy);
HystrixPlugins.getInstance().registerCommandExecutionHook(commandExecutionHook);
}
private HystrixConcurrencyStrategy detectRegisteredConcurrencyStrategy() {
HystrixConcurrencyStrategy registeredStrategy = HystrixPlugins.getInstance().getConcurrencyStrategy();
if (existingConcurrencyStrategy == null) {
return registeredStrategy;
}
if (registeredStrategy instanceof HystrixConcurrencyStrategyDefault) {
return existingConcurrencyStrategy;
}
if (!existingConcurrencyStrategy.equals(registeredStrategy)) {
log.warn("找到多个 HystrixConcurrencyStrategy, 使用已存在的HystrixConcurrencyStrategy");
}
return existingConcurrencyStrategy;
}
}
@Component
public class FeignRequestInterceptor implements RequestInterceptor {
@Override
public void apply(RequestTemplate requestTemplate) {
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
if (attributes != null) {
HttpServletRequest request = attributes.getRequest();
Enumeration<String> headerNames = request.getHeaderNames();
if (headerNames != null) {
while (headerNames.hasMoreElements()) {
String name = headerNames.nextElement();
String values = request.getHeader(name);
requestTemplate.header(name, values);
}
}
} else {
// RequestContextHolder中获取不到request时,可能是当前线程在Hystrix线程池中,则需要从AuthenticationontextHolder中获取header信息
Map<String, String> headers = AuthenticationContextHolder.getRequestHeaders();
if (MapUtils.isNotEmpty(headers)) {
headers.forEach(requestTemplate::header);
}
}
}
}
其中HystrixConcurrencyStrategyAutoConfiguration类中加了条件注解,在关闭hystrix的时候就不需要装配自定义的并发策略了
参考:https://www.codenong.com/cs109642312/