feign集成hystrix获取不到RequestContextHolder.currentRequestAttributes()案例解决

feign集成hystrix获取不到RequestContextHolder.currentRequestAttributes

  • 1、问题描述
  • 2、问题分析
  • 3、通过HystrixConcurrencyStrategy自定义策略,解决线程间的数据传递
  • 4、使用hystrix-plugins.properties生效自定义plugins
  • 5、重写RequestInterceptor并注入bean


1、问题描述

SpringBoot项目中使用到了feign作为Cloud平台内部微服务之间的接口调用,内部组件之间调用过程中,需要在RequestTemplate中添加请求头header鉴权等信息,而feign在开启了httpclient和hystrix支持后,发现RequestContextHolder.currentRequestAttributes()抛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.异常。
yml引入feign、hystrix配置如下:

feign:
  httpclient:
    enabled: true
  hystrix:
    enabled: true
  compression: #开启GZIP
    request:
      enabled: true
    response:
      enabled: true

hystrix:
  threadpool:
    default:
      coreSize: 20  # 设置线程池大小为20
  metrics:
    enabled: true
  command:
    default:
      execution:
        isolation:
          thread:
            timeoutInMilliseconds: 60000
        timeout:
          enabled: false

2、问题分析

hystrix使用多线程管理请求连接池,从主线程到发送基于hystrix的feign请求线程已不在同一个线程内,而RequestContextHolder是基于ThreadLocal实现的,这就使得线程之间数据断链,需要通过线程之间的数据传递使得ThreadLocal中存储的currentRequestAttributes接上。hystrix强大在于是支持此扩展操作的。

3、通过HystrixConcurrencyStrategy自定义策略,解决线程间的数据传递

废话不多说,直接上代码:

package com.xx.xx.app.config;

import java.util.concurrent.Callable;

import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;

import com.netflix.hystrix.strategy.concurrency.HystrixConcurrencyStrategy;

/**
 * @author zhouyuhhu
 * @date 2019/12/26
 */
public class RequestContextHystrixConcurrencyStrategy extends HystrixConcurrencyStrategy {
    @Override
    public <T> Callable<T> wrapCallable(Callable<T> callable) {
        return new RequestAttributeAwareCallable<>(callable, RequestContextHolder.getRequestAttributes());
    }

    static class RequestAttributeAwareCallable<T> implements Callable<T> {

        private final Callable<T> delegate;
        private final RequestAttributes requestAttributes;

        public RequestAttributeAwareCallable(Callable<T> callable, RequestAttributes requestAttributes) {
            this.delegate = callable;
            this.requestAttributes = requestAttributes;
        }

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

自定义策略方案实现后,现在的问题是如何让自己的策略注册上去生效,网上给了个方案如下:

@PostConstruct
public void init() {
    HystrixPlugins.getInstance().registerConcurrencyStrategy(new RequestContextHystrixConcurrencyStrategy());
}

但是,报错:

Caused by: java.lang.IllegalStateException: Another strategy was already registered.

如果使用HystrixPlugins.reset();太粗暴,把所有的HystrixPlugins都reset了个遍,其实这里仅仅是重定义了HystrixConcurrencyStrategy。

4、使用hystrix-plugins.properties生效自定义plugins

在工程的classpath下引入hystrix-plugins.properties配置文件,hystrix会默认读取hystrix-plugins.properties配置文件并生效自定义的plugins:

hystrix.plugin.HystrixConcurrencyStrategy.implementation=com.xx.xx.xx.config.RequestContextHystrixConcurrencyStrategy

5、重写RequestInterceptor并注入bean

最后,就可以在RequestTemplate中通过RequestContextHolder.currentRequestAttributes()添加header信息了,代码如下:

package com.xx.xx.xx.xx;

import javax.servlet.http.HttpServletRequest;

import org.springframework.http.MediaType;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import feign.RequestInterceptor;
import feign.RequestTemplate;
import lombok.extern.slf4j.Slf4j;

/**
 * @author zhouyuhhu
 * @date 2019/9/19 17:17
 */
@Component
@Slf4j
public class FeignAccessTokenRequestInterceptor implements RequestInterceptor {
    @Override
    public void apply(RequestTemplate requestTemplate) {
        // 请求MediaType
        requestTemplate.header("Content-Type", MediaType.APPLICATION_JSON_UTF8.toString());
        requestTemplate.header("Accept", MediaType.APPLICATION_JSON_UTF8.toString());

        try {
            HttpServletRequest request =
                ((ServletRequestAttributes)RequestContextHolder.currentRequestAttributes()).getRequest();
            if (null != request) {
                // 
                requestTemplate.header("sysRestApiPermission", request.getHeader("sysRestApiPermission"));
                // 渠道来源
                requestTemplate.header("REQUEST-SOURCE", request.getHeader("REQUEST-SOURCE"));
                // X-Auth-Token
                requestTemplate.header("X-Auth-Token", request.getHeader("X-Auth-Token"));
                // Authorization
                requestTemplate.header("Authorization", request.getHeader("Authorization"));
            }
        } catch (Exception e) {
            log.warn("getRequest fail!", e);
        }
    }
}

你可能感兴趣的:(SpringBoot)