SpringCloud Feign转发请求头(防止session失效)的解决方案

作者:学圆惑边 时间:2022-08-29 12:25:59 

微服务开发中经常有这样的需求,公司自定义了通用的请求头,需要在微服务的调用链中转发,比如在请求头中加入了token,或者某个自定义的信息uniqueId,总之就是自定义的一个键值对的东东,A服务调用B服务,B服务调用C服务,这样通用的东西如何让他在一个调用链中不断地传递下去呢?以A服务为例:

方案1

最傻的办法,在程序中获取,调用B的时候再转发,怎么获取在Controller中国通过注解获取,或者通过request对象获取,这个不难,在请求B服务的时候,通过注解将值放进去即可;简代码如下:

获取:
@RequestMapping(value = "/api/test", method = RequestMethod.GET)
public String testFun(@RequestParam String name, @RequestHeader("uniqueId") String uniqueId) {
 if(uniqueId == null ){
    return "Must defined the uniqueId , it can not be null";
 }
 log.info(uniqueId, "begin testFun... ");
return uniqueId;
}

然后A使用Feign调用B服务的时候,传过去:


@FeignClient(value = "DEMO-SERVICE")
public interface CallClient {

/**
* 访问DEMO-SERVICE服务的/api/test接口,通过注解将logId传递给下游服务
*/
@RequestMapping(value = "/api/test", method = RequestMethod.GET)
 String callApiTest(@RequestParam(value = "name") String name, @RequestHeader(value = "uniqueId") String uniqueId);

}

方案弊端:毫无疑问,这方案不好,因为对代码有侵入,需要开发人员没次手动的获取和添加,因此舍弃

方案2

服务通过请求 * ,在请求从A发送到B之后,在 * 内将自己需要的东东加到请求头:

import com.intellif.log.LoggerUtilI;
import feign.RequestInterceptor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import java.util.Enumeration;

/**
* 自定义的请求头处理类,处理服务发送时的请求头;
* 将服务接收到的请求头中的uniqueId和token字段取出来,并设置到新的请求头里面去转发给下游服务
* 比如A服务收到一个请求,请求头里面包含uniqueId和token字段,A处理时会使用Feign客户端调用B服务
* 那么uniqueId和token这两个字段就会添加到请求头中一并发给B服务;
*
* @author mozping
* @version 1.0
* @date 2018/6/27 14:13
* @see FeignHeadConfiguration
* @since JDK1.8
*/
@Configuration
public class FeignHeadConfiguration {
 private final LoggerUtilI logger = LoggerUtilI.getLogger(this.getClass().getName());

@Bean
 public RequestInterceptor requestInterceptor() {
   return requestTemplate -> {
     ServletRequestAttributes attrs = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
     if (attrs != null) {
       HttpServletRequest request = attrs.getRequest();
       Enumeration<String> headerNames = request.getHeaderNames();
       if (headerNames != null) {
         while (headerNames.hasMoreElements()) {
           String name = headerNames.nextElement();
           String value = request.getHeader(name);
           /**
            * 遍历请求头里面的属性字段,将logId和token添加到新的请求头中转发到下游服务
            * */
           if ("uniqueId".equalsIgnoreCase(name) || "token".equalsIgnoreCase(name)) {
             logger.debug("添加自定义请求头key:" + name + ",value:" + value);
             requestTemplate.header(name, value);
           } else {
             logger.debug("FeignHeadConfiguration", "非自定义请求头key:" + name + ",value:" + value + "不需要添加!");
           }
         }
       } else {
         logger.warn("FeignHeadConfiguration", "获取请求头失败!");
       }
     }
   };
 }

}

网上很多关于这种方法的博文或者资料,大同小异,但是有一个问题,在开启熔断器之后,这里的attrs就是null,因为熔断器默认的隔离策略是thread,也就是线程隔离,实际上接收到的对象和这个在发送给B不是一个线程,怎么办?有一个办法,修改隔离策略hystrix.command.default.execution.isolation.strategy=SEMAPHORE,改为信号量的隔离模式,但是不推荐,因为thread是默认的,而且要命的是信号量模式,熔断器不生效,比如设置了熔断时间hystrix.command.default.execution.isolation.semaphore.timeoutInMilliseconds=5000,五秒,如果B服务里面sleep了10秒,非得等到B执行完毕再返回,因此这个方案也不可取;但是有什么办法可以在默认的Thread模式下让 * 拿到上游服务的请求头?自定义策略:代码如下:


import com.netflix.hystrix.HystrixThreadPoolKey;
import com.netflix.hystrix.HystrixThreadPoolProperties;
import com.netflix.hystrix.strategy.HystrixPlugins;
import com.netflix.hystrix.strategy.concurrency.HystrixConcurrencyStrategy;
import com.netflix.hystrix.strategy.concurrency.HystrixRequestVariable;
import com.netflix.hystrix.strategy.concurrency.HystrixRequestVariableLifecycle;
import com.netflix.hystrix.strategy.eventnotifier.HystrixEventNotifier;
import com.netflix.hystrix.strategy.executionhook.HystrixCommandExecutionHook;
import com.netflix.hystrix.strategy.metrics.HystrixMetricsPublisher;
import com.netflix.hystrix.strategy.properties.HystrixPropertiesStrategy;
import com.netflix.hystrix.strategy.properties.HystrixProperty;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;

import java.util.concurrent.BlockingQueue;
import java.util.concurrent.Callable;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

/**
* 自定义Feign的隔离策略;
* 在转发Feign的请求头的时候,如果开启了Hystrix,Hystrix的默认隔离策略是Thread(线程隔离策略),因此转发 * 内是无法获取到请求的请求头信息的,可以修改默认隔离策略为信号量模式:hystrix.command.default.execution.isolation.strategy=SEMAPHORE,这样的话转发线程和请求线程实际上是一个线程,这并不是最好的解决方法,信号量模式也不是官方最为推荐的隔离策略;另一个解决方法就是自定义Hystrix的隔离策略,思路是将现有的并发策略作为新并发策略的成员变量,在新并发策略中,返回现有并发策略的线程池、Queue;将策略加到Spring容器即可;
*
* @author mozping
* @version 1.0
* @date 2018/7/5 9:08
* @see FeignHystrixConcurrencyStrategyIntellif
* @since JDK1.8
*/
@Component
public class FeignHystrixConcurrencyStrategyIntellif extends HystrixConcurrencyStrategy {

private static final Logger log = LoggerFactory.getLogger(FeignHystrixConcurrencyStrategyIntellif.class);
 private HystrixConcurrencyStrategy delegate;

public FeignHystrixConcurrencyStrategyIntellif() {
   try {
     this.delegate = HystrixPlugins.getInstance().getConcurrencyStrategy();
     if (this.delegate instanceof FeignHystrixConcurrencyStrategyIntellif) {
       // Welcome to singleton hell...
       return;
     }
     HystrixCommandExecutionHook commandExecutionHook =
         HystrixPlugins.getInstance().getCommandExecutionHook();
     HystrixEventNotifier eventNotifier = HystrixPlugins.getInstance().getEventNotifier();
     HystrixMetricsPublisher metricsPublisher = HystrixPlugins.getInstance().getMetricsPublisher();
     HystrixPropertiesStrategy propertiesStrategy =
         HystrixPlugins.getInstance().getPropertiesStrategy();
     this.logCurrentStateOfHystrixPlugins(eventNotifier, metricsPublisher, propertiesStrategy);
     HystrixPlugins.reset();
     HystrixPlugins.getInstance().registerConcurrencyStrategy(this);
     HystrixPlugins.getInstance().registerCommandExecutionHook(commandExecutionHook);
     HystrixPlugins.getInstance().registerEventNotifier(eventNotifier);
     HystrixPlugins.getInstance().registerMetricsPublisher(metricsPublisher);
     HystrixPlugins.getInstance().registerPropertiesStrategy(propertiesStrategy);
   } catch (Exception e) {
     log.error("Failed to register Sleuth Hystrix Concurrency Strategy", e);
   }
 }

private void logCurrentStateOfHystrixPlugins(HystrixEventNotifier eventNotifier,
                        HystrixMetricsPublisher metricsPublisher, HystrixPropertiesStrategy propertiesStrategy) {
   if (log.isDebugEnabled()) {
     log.debug("Current Hystrix plugins configuration is [" + "concurrencyStrategy ["
         + this.delegate + "]," + "eventNotifier [" + eventNotifier + "]," + "metricPublisher ["
         + metricsPublisher + "]," + "propertiesStrategy [" + propertiesStrategy + "]," + "]");
     log.debug("Registering Sleuth Hystrix Concurrency Strategy.");
   }
 }

@Override
 public <T> Callable<T> wrapCallable(Callable<T> callable) {
   RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
   return new WrappedCallable<>(callable, requestAttributes);
 }

@Override
 public ThreadPoolExecutor getThreadPool(HystrixThreadPoolKey threadPoolKey,
                     HystrixProperty<Integer> corePoolSize, HystrixProperty<Integer> maximumPoolSize,
                     HystrixProperty<Integer> keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue) {
   return this.delegate.getThreadPool(threadPoolKey, corePoolSize, maximumPoolSize, keepAliveTime,
       unit, workQueue);
 }

@Override
 public ThreadPoolExecutor getThreadPool(HystrixThreadPoolKey threadPoolKey,
                     HystrixThreadPoolProperties threadPoolProperties) {
   return this.delegate.getThreadPool(threadPoolKey, threadPoolProperties);
 }

@Override
 public BlockingQueue<Runnable> getBlockingQueue(int maxQueueSize) {
   return this.delegate.getBlockingQueue(maxQueueSize);
 }

@Override
 public <T> HystrixRequestVariable<T> getRequestVariable(HystrixRequestVariableLifecycle<T> rv) {
   return this.delegate.getRequestVariable(rv);
 }

static class WrappedCallable<T> implements Callable<T> {
   private final Callable<T> target;
   private final RequestAttributes requestAttributes;

public WrappedCallable(Callable<T> target, RequestAttributes requestAttributes) {
     this.target = target;
     this.requestAttributes = requestAttributes;
   }

@Override
   public T call() throws Exception {
     try {
       RequestContextHolder.setRequestAttributes(requestAttributes);
       return target.call();
     } finally {
       RequestContextHolder.resetRequestAttributes();
     }
   }
 }
}

然后使用默认的熔断器隔离策略,也可以在 * 内获取到上游服务的请求头信息了;
这里参考的博客,感谢这位大牛:https://blog.csdn.net/Crystalqy/article/details/79083857

来源:https://blog.csdn.net/my_momo_csdn/article/details/80922737

标签:SpringCloud,Feign,转发请求头
0
投稿

猜你喜欢

  • Java使用entrySet方法获取Map集合中的元素

    2022-08-06 17:41:05
  • 浅谈Java异常的Exception e中的egetMessage()和toString()方法的区别

    2022-05-27 00:37:31
  • java中transient关键字用法分析

    2022-01-22 04:27:05
  • SpringMVC拦截器创建配置及执行顺序

    2023-06-06 20:41:16
  • C#实现创建标签PDF文件的示例代码

    2023-09-15 07:04:58
  • Android开发Jetpack组件LiveData使用讲解

    2023-03-21 09:27:49
  • SpringCloud微服务架构升级汇总

    2022-05-04 06:09:19
  • Android自定义ViewGroup实现带箭头的圆角矩形菜单

    2022-11-26 14:40:25
  • Java OkHttp框架源码深入解析

    2023-11-29 03:22:50
  • android实现okHttp的get和post请求的简单封装与使用

    2023-10-06 04:20:25
  • JAVA十大排序算法之桶排序详解

    2022-11-08 01:07:47
  • 浅谈利用Spring的AbstractRoutingDataSource解决多数据源的问题

    2021-09-07 07:20:59
  • 使用mutex实现应用程序单实例运行代码分享

    2023-09-18 22:45:11
  • Android自定义View实现仿网易音乐唱片播放效果

    2022-02-27 07:18:40
  • SpringBoot实现阿里云短信发送的示例代码

    2023-05-15 21:08:54
  • java根据不同的参数调用不同的实现类操作

    2021-11-08 17:05:16
  • 搭建简单的Spring-Data JPA项目

    2023-04-05 01:06:30
  • Android编程开发之性能优化技巧总结

    2022-08-26 13:23:00
  • Springboot 项目读取Resources目录下的文件(推荐)

    2023-07-27 09:40:47
  • Java java.lang.ExceptionInInitializerError 错误如何解决

    2021-06-11 10:46:24
  • asp之家 软件编程 m.aspxhome.com