Feign拦截器中获取RequestContextHolder.getRequestAttributes()为空问题排查

问题描述:

项目中有场景需要对接口进行登陆验证,获取请求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/

你可能感兴趣的:(java)