Feign同步调用
在分布式开发中,会用到Feign进行远程调用其他服务的接口获取数据,首先看看这个调用原理,如下图:
通过Feign进行远程调用时,是Java模拟发送一个请求到另一个服务,但是这个请求不像浏览器要样会自己携带上当前域名下的cookie信息,这个Java模拟的请求是没有任何cookie信息的,所以在调用另一个服务时被拦截器拦截时认证不通过,因此调用失败。
为了解决这个问题,我们可以使用Feign远程调用的拦截器,在Feign构造好请求后,先交于拦截器进行处理,其流程大致如下图:
@Configuration
public class FeignConfig {
@Bean
public RequestInterceptor requestInterceptor() {
return new RequestInterceptor() {
/**
* 调用远程方法之前先调用此方法
*/
@Override
public void apply(RequestTemplate requestTemplate) {
//Spring提供的工具,获取当前请求的属性,
ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
//获取当前请求对象
HttpServletRequest request = requestAttributes.getRequest();
//同步请求头信息
requestTemplate.header("Cookie", request.getHeader("Cookie"));
}
};
}
}
Feign异步调用
但是在Feign异步调用时使用上面的拦截器会失效,在拦截器中通过断点调试发现,它直接丢失了上下文对象,也就是拦截器中的HttpServletRequest为空,而在Feign拦截器中是通过RequestContextHolder去获取上下文信息的,而RequestContextHolder是通过ThreadLocal去获取的,也就是说在同步调用时,从前往后执行都是在同一个线程中,数据在同样一个线程是可以共享的,但是我们在异步调用时,可能是通过创建了一个新的线程去执行的,这个线程和主线程不是同一个线程,所以是获取不到主线程的上下文信息的。我们在执行业务逻辑时又不得不考虑多线程去执行来提高效率
所以为了解决这个问题,我们可以在主方法中先获取请求的上下文信息,然后在执行子线程将主线程的上下文信息放在子线程的RequestContextHolder中,这样在子线程执行feign拦截器时就可以获取到上下文信息了
public void testDemo() throws ExecutionException, InterruptedException {
// 主线程请求的上下文信息
RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
CompletableFuture<Void> task1 = CompletableFuture.runAsync(() -> {
//设置上下文信息
RequestContextHolder.setRequestAttributes(requestAttributes);
//TODO 执行远程调用
}, executor);
CompletableFuture<Void> task2 = CompletableFuture.runAsync(() -> {
//设置上下文信息
RequestContextHolder.setRequestAttributes(requestAttributes);
//TODO 执行远程调用
}, executor);
CompletableFuture.allOf(task1, task2).get();
}
// TODO feign的拦截器代码不用改