解决fegin+hystrix中RequestContextHolder.getRequestAttributes();返回null值的解决方法

本文介绍如何在Spring Cloud微服务架构中,利用Feign实现服务间调用,并通过Hystrix实现熔断和超时处理,确保系统的稳定性和高可用性。文章详细讲解了自定义Feign拦截器、熔断策略以及日志配置的方法。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

转载大佬的文章: http://www.itmuch.com/spring-cloud-sum/hystrix-threadlocal/

自定义feign拦截器
 

package com.wm.commonfeign.config;

import feign.RequestInterceptor;
import feign.RequestTemplate;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Configuration;
import org.springframework.util.ObjectUtils;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

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

/**
 * @class_name: CustomFeignConfig
 * @description: 服务间调用携带authorization请求头,拦截器统一处理
 * @author: wm_yu
 * @create: 2019/07/26
 **/
@Configuration
@Slf4j
public class CustomFeignConfig implements RequestInterceptor {
    private static final String TOKEN_HEADER = "authorization";

    @Override
    public void apply(RequestTemplate requestTemplate) {
        HttpServletRequest request = getHttpServletRequest();
        //获取请求参数 TODO 获取请求参数失败  RequestInterceptor是在请求前给request添加参数,实际上参数并没有同步过来,待解决,建议使用feign官方的输出日志
        String jsonString = this.getRequestJsonString(request);
        //输出调用feign日志
        log.info("feign调用请求方法日志打印,methodName={},Parameter={}",request.getMethod(),jsonString);
        if (ObjectUtils.isEmpty(request)) {
            requestTemplate.header(TOKEN_HEADER, getHeaders(request).get(TOKEN_HEADER));
        }
    }


    /**
     * 获取请求的request
     * @return
     */
    private HttpServletRequest getHttpServletRequest() {
        try {
            // hystrix隔离策略会导致RequestContextHolder.getRequestAttributes()返回null
            // 解决方案:http://www.itmuch.com/spring-cloud-sum/hystrix-threadlocal/
            ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
            if (!ObjectUtils.isEmpty(attributes)){
                return attributes.getRequest();
            }
            return null;
        } catch (Exception e) {
            log.error("feign拦截器中获取HttpServletRequest error...",e.getMessage(),e);
            return null;
        }
    }

    /**
     * 获取所有的header中的参数
     * @param request
     * @return
     */
    private Map<String, String> getHeaders(HttpServletRequest request) {
        Map<String, String> map = new LinkedHashMap<>();
        Enumeration<String> enumeration = request.getHeaderNames();
        while (enumeration.hasMoreElements()) {
            String key = enumeration.nextElement();
            String value = request.getHeader(key);
            map.put(key, value);
        }
        return map;
    }


    /**
     * 获取request中的json数据
     * @param request
     * @return
     */
    public  String getRequestJsonString(HttpServletRequest request){
        try {

            int contentLength = request.getContentLength();
            if (contentLength < 0) {
                return null;
            }
            byte buffer[] = new byte[contentLength];
            for (int i = 0; i < contentLength;) {
                int readlen = request.getInputStream().read(buffer, i, contentLength - i);
                if (readlen == -1) {
                    break;
                }
                i += readlen;
            }

            String charEncoding = request.getCharacterEncoding();
            if (charEncoding == null) {
                charEncoding = "UTF-8";
            }
            return new String(buffer, charEncoding);
        } catch (Exception e) {
            log.error("解析request中的流异常....",e.getMessage(),e);
        }
        return null;
    }


}

 

 

途中代码处返回null

解决方法:自定义熔断策略

package com.wm.commonfeign.strategy;

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 lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Primary;
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;

/**
 * @class_name: CustomFeignHystrixConcurrencyStrategy
 * @description: 自定义隔离策略 解决RequestContextHolder.getRequestAttributes()问题 为null
 * @author: wm_yu
 * @create: 2019/07/26
 **/
@Component
@Primary
@Slf4j
public class CustomFeignHystrixConcurrencyStrategy extends HystrixConcurrencyStrategy{

    private HystrixConcurrencyStrategy hystrixConcurrencyStrategy;

    public CustomFeignHystrixConcurrencyStrategy() {
        try {
            this.hystrixConcurrencyStrategy = HystrixPlugins.getInstance().getConcurrencyStrategy();
            if (this.hystrixConcurrencyStrategy instanceof CustomFeignHystrixConcurrencyStrategy) {
                // 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.hystrixConcurrencyStrategy + "]," + "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.hystrixConcurrencyStrategy.getThreadPool(threadPoolKey, corePoolSize, maximumPoolSize, keepAliveTime,
                unit, workQueue);
    }

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

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

    @Override
    public <T> HystrixRequestVariable<T> getRequestVariable(HystrixRequestVariableLifecycle<T> rv) {
        return this.hystrixConcurrencyStrategy.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();
            }
        }
    }
}

配置fegin支持hystrix,并指定熔断的超时

 

feign:
  hystrix:
    enabled:  true

# hystrix配置
hystrix:
  shareSecurityContext: true
  command:
    default:
      execution:
        isolation:
          thread:
            timeoutInMilliseconds: 60000

 

feign提供给其他服务调用
 

package com.wm.examapi.feign;

import com.wm.commoncore.constant.ServiceConstant;
import com.wm.commoncore.model.Response;
import com.wm.commonfeign.config.CustomFeignConfig;
import com.wm.examapi.feign.fallback.ExaminationServiceClientFallbackImpl;
import com.wm.examapi.model.Examination;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;

/**
 * @class_name: ExaminationServiceClient
 * @description: 考试接口 .... FeignClient需要指定configuration为CustomFeignConfig,用于服务间调用携带token
 * @author: wm_yu
 * @create: 2019/07/26
 **/
@FeignClient(name = ServiceConstant.EXAM_SERVICE, configuration = CustomFeignConfig.class, fallback = ExaminationServiceClientFallbackImpl.class)
public interface ExaminationServiceClient {
    /**
     * 查询考试数量
     *
     * @param examination 租户标识
     * @return ResponseBean
     */
    /**
     * 注意这里只能用RequestMapping指定请求方法为post,不能使用@PostMapping
     * @param examination
     * @return
     */
    @RequestMapping(method = RequestMethod.POST,value = "/api/exam/getExaminationCount")
    Response<Integer> findExaminationCount(@RequestBody Examination examination);
}

配置熔断类:

package com.wm.examapi.feign.fallback;

import com.wm.commoncore.constant.BussiConstant;
import com.wm.commoncore.model.Response;
import com.wm.examapi.feign.ExaminationServiceClient;
import com.wm.examapi.model.Examination;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.RequestBody;


/**
 * @class_name: ExaminationServiceClientFallbackImpl
 * @description:  考试服务熔断
 * @author: wm_yu
 * @create: 2019/07/26
 **/
@Slf4j
@Component
public class ExaminationServiceClientFallbackImpl implements ExaminationServiceClient {

    private Throwable throwable;

    @Override
    public Response<Integer> findExaminationCount(@RequestBody Examination examination) {
        log.error("调用{}异常, {}, {}", "findExaminationCount", examination.getTenantCode(),throwable);
        return  Response.createError(BussiConstant.EXAM_SERVICE_FEIGN_ERROR,0);
    }

    public Throwable getThrowable() {
        return throwable;
    }

    public void setThrowable(Throwable throwable) {
        this.throwable = throwable;
    }
}


创建熔断工厂,生成熔断实例

package com.wm.examapi.feign.factory;

import com.wm.examapi.feign.ExaminationServiceClient;
import com.wm.examapi.feign.fallback.ExaminationServiceClientFallbackImpl;
import feign.hystrix.FallbackFactory;
import org.springframework.stereotype.Component;

/**
 * @class_name: UserServiceClientFallbackFactory
 * @description: 考试系统熔断器工厂
 * @author: wm_yu
 * @create: 2019/07/26
 **/
@Component
public class ExaminationServiceClientFallbackFactory implements FallbackFactory<ExaminationServiceClient> {
    @Override
    public ExaminationServiceClient create(Throwable throwable) {
        ExaminationServiceClientFallbackImpl examinationServiceClientFallback = new ExaminationServiceClientFallbackImpl();
        examinationServiceClientFallback.setThrowable(throwable);
        return examinationServiceClientFallback;
    }
}

启动类扫描feign包

package com.wm.userservice;

import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.openfeign.EnableFeignClients;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import org.springframework.web.filter.CorsFilter;

@SpringBootApplication
@EnableDiscoveryClient  //注册中心发现
@MapperScan("com.wm.userservice.mapper")
@EnableFeignClients(basePackages = {"com.wm.examapi.feign"}) //扫描fegin包
@ComponentScan({"com.wm.examapi.feign","com.wm.userservice","com.wm.commonfeign"})  //主要是需要扫描到内部的包(内部不同模块),如:fegin配置等
public class UserServiceApplication {

    public static void main(String[] args) {
        SpringApplication.run(UserServiceApplication.class, args);
    }


    /**
     * 跨域过滤器
     * @return
     */
    @Bean
    public CorsFilter corsFilter() {
        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        source.registerCorsConfiguration("/**", buildConfig());
        return new CorsFilter(source);
    }

    private CorsConfiguration buildConfig() {
        CorsConfiguration corsConfiguration = new CorsConfiguration();
        corsConfiguration.addAllowedOrigin("*");
        corsConfiguration.addAllowedHeader("*");
        corsConfiguration.addAllowedMethod("*");
        return corsConfiguration;
    }

}

这里多配置一个vue跨域解决的

 

本来是想在拦截器中输出日志信息,发现不行,这个待处理,目前个人理解的原因的:

 

后面采用官方的feign日志输出:

很简单:

创建一个日志配置类:

如下:

package com.wm.commonfeign.config;

import feign.Logger;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * @class_name: FeignLogConfig
 * @description: 配置feign日志输出
 * @author: wm_yu
 * @create: 2019/07/27
 **/

/**
 * Feign logging
 * A logger is created for each Feign client created. By default the name of the logger is the full class name of the interface used to create the Feign client. Feign logging only responds to the DEBUG level.
 *
 *   application.yml
 *
 * logging.level.project.user.UserClient: DEBUG
 *
 *
 * The Logger.Level object that you may configure per client, tells Feign how much to log. Choices are:
 *
 * 可以为每个客户端配置日志级别(Logger.Level),选项有:
 *
 * NONE, No logging (DEFAULT).
 * BASIC, Log only the request method and URL and the response status code and execution time.
 * HEADERS, Log the basic information along with request and response headers.
 * FULL, Log the headers, body, and metadata for both requests and responses.
 * For example, the following would set the Logger.Level to FULL:
 *
 * 下例将日志级别设为全部(FULL):
 *
 * @Configuration
 * public class FooConfiguration {
 *     @Bean
 *     Logger.Level feignLoggerLevel() {
 *         return Logger.Level.FULL;
 *     }
 * }
 */

@Configuration
public class FeignLogConfig {
        @Bean
        Logger.Level feignLoggerLevel() {
            return Logger.Level.FULL;
        }
}

注释掉的为官方文档中的解释

在yml中配置需要输出的日志级别和指明feign类

 

logging:
  level:
    root: info
     ####sql日志
    com.wm.examservice.mapper: debug
    #### feign日志
    com.wm.examapi.feign: debug

 

 

<think>好的,我现在需要解决用户在使用定时器远程调用Feign方法时,RequestContextHolder.getRequestAttributes()返回空的问题。首先,我得回忆一下用户提供的引用内容,以及之前的回答。 用户引用了几个关于Feign和RequestContextHolder的问题,特别是提到在Feign拦截器或Hystrix熔断时,获取RequestAttributes为null的情况。之前的回答中,用户的问题是关于定时任务调用Feign时的熔断降级,现在的新问题是关于RequestContextHolder在定时任务中无法获取请求属性。 首先,我需要理解为什么定时任务会导致RequestContextHolder.getRequestAttributes()返回null。通常,RequestContextHolder是基于ThreadLocal的,它存储当前线程的请求属性。但在定时任务中,任务可能是在一个独立的线程池中执行,脱离了原来的请求上下文,所以ThreadLocal的无法传递到新线程。 用户提供的引用中提到,在Feign拦截器中获取RequestAttributes为空,以及解决方案中设置子线程共享属性。比如引用[2]提到通过设置ServletRequestAttributes并允许子线程共享。这可能是一个关键点,即需要将主线程的请求属性传递到子线程。 另外,引用[5]提到在Hystrix熔断工厂中处理RequestContextHolder的问题,可能需要考虑Hystrix的线程隔离机制,导致线程切换后丢失ThreadLocal的。虽然用户现在的问题是定时任务,但Hystrix的线程隔离可能也是相关因素,尤其是如果Feign客户端启用了Hystrix的话。 接下来,我需要结合这些信息,找到定时任务调用Feign时RequestAttributes为空的解决方案。可能的解决方法包括: 1. **手动传递请求属性**:在定时任务中,主线程可能没有HTTP请求上下文,因为定时任务通常由调度线程触发,而不是HTTP请求线程。因此,需要手动设置RequestAttributes到定时任务的线程中。 2. **配置Hystrix使用相同线程**:如果使用了Hystrix,其默认的线程池隔离会导致线程切换,从而丢失ThreadLocal的。可以配置Hystrix使用信号量隔离或同步执行,避免线程切换。 3. **使用RequestContextListener或过滤器**:确保请求上下文被正确初始化和传递,特别是在异步或定时任务的环境中。 4. **Feign拦截器的调整**:在Feign拦截器中,如果无法获取到请求属性,可能需要从其他位置获取并设置请求头,比如从安全上下文或其他存储中。 用户的问题具体是在定时任务中调用Feign方法,此时没有HTTP请求上下文,所以RequestContextHolder自然为空。这时候需要分析定时任务本身的执行环境,以及Feign客户端在调用时的上下文传递机制。 可能的解决方案步骤: - **检查定时任务线程的上下文**:确认定时任务是否在Web请求的上下文中执行。通常不是,因此需要手动设置。 - **在定时任务中模拟请求属性**:如果Feign调用需要某些请求头(如认证信息、跟踪ID等),需要在定时任务中手动创建ServletRequestAttributes,并设置到RequestContextHolder中。 - **调整Hystrix的线程策略**:如果启用了Hystrix,确保其执行策略不会切换到新线程,或者传递上下文到新线程。 - **使用异步传播工具**:如TransmittableThreadLocal来传递ThreadLocal变量到子线程。 现在需要将这些思路整理成结构化的解决方案,参考用户提供的引用中的方法,比如引用[2]中的设置子线程共享属性,可能需要使用RequestContextHolder.setRequestAttributes并设置第三个参数为true,允许继承。 同时,用户之前的回答中提到了Feign配置和熔断处理,可能需要结合这些配置,确保在定时任务中正确初始化请求上下文,并在Feign拦截器中能够获取到这些属性。 例如,在定时任务的执行方法中,手动创建一个ServletRequestAttributes对象,并设置到RequestContextHolder中,这样在后续的Feign调用中,拦截器就能获取到这些属性,从而正确添加请求头。 还需要考虑线程池的问题,如果定时任务使用异步执行,需要确保上下文传递到异步线程中。比如使用TaskDecorator来包装任务,复制上下文到子线程。 总结可能的解决方案: 1. **手动设置请求上下文**:在定时任务代码中,创建并设置ServletRequestAttributes到当前线程的RequestContextHolder。 2. **配置Hystrix使用相同线程**:通过修改Hystrix的隔离策略,避免线程切换。 3. **使用线程上下文传递工具**:如TransmittableThreadLocal或Spring的RequestContextHolder的子线程共享设置。 4. **调整Feign拦截器逻辑**:如果某些请求头是必须的,但无法从请求上下文中获取,可能需要从其他存储(如数据库、缓存)中获取并手动设置到Feign请求模板中。 接下来需要验证这些方法的可行性,并参考用户提供的引用中的代码示例,比如引用[2]中的设置子线程共享属性,以及引用[4]中的Feign拦截器配置。 例如,在定时任务的执行方法中: ```java @Scheduled(cron = "0 0/5 * * * ?") public void scheduledTask() { // 手动创建并设置RequestAttributes HttpServletRequest request = ...; // 可能需要伪造一个请求 ServletRequestAttributes attributes = new ServletRequestAttributes(request); RequestContextHolder.setRequestAttributes(attributes, true); // 允许子线程继承 try { remoteServiceClient.getData("task123"); } finally { RequestContextHolder.resetRequestAttributes(); // 清理避免内存泄漏 } } ``` 但问题在于定时任务本身没有HTTP请求,如何伪造一个有效的HttpServletRequest对象?可能需要创建一个模拟请求,或者如果不需要实际的请求对象,仅需要传递某些头部信息,可以手动设置。 或者,如果Feign拦截器需要的只是特定的请求头,如认证token或跟踪ID,可以在定时任务中直接设置这些头部到Feign的请求模板中,而不依赖RequestContextHolder。 例如,在Feign配置中添加一个拦截器,检查当前线程是否有请求属性,如果没有,则从其他来源(如安全上下文、配置属性)获取所需的,并设置到请求头中。 此外,引用[4]中的Feign拦截器代码尝试获取请求属性,但在定时任务中可能为空,所以需要修改拦截器,当attributes为null时,从其他位置获取必要的信息,或者跳过设置某些头部的步骤。 另一种方法是使用ThreadLocal上下文持有者,在定时任务执行前将必要的请求参数存储到自定义的ThreadLocal中,然后在Feign拦截器中从该ThreadLocal中读取,而不是依赖于RequestContextHolder。 结合用户提供的引用,特别是引用[2]中的方法,可能需要在定时任务中设置RequestAttributes,并允许子线程共享。例如: ```java @Scheduled(cron = "0 0/5 * * * ?") public void scheduledTask() { // 创建模拟的ServletRequestAttributes MockHttpServletRequest request = new MockHttpServletRequest(); // 设置必要的请求头,如认证信息 request.addHeader("Authorization", "Bearer token123"); ServletRequestAttributes attributes = new ServletRequestAttributes(request); RequestContextHolder.setRequestAttributes(attributes, true); // 允许子线程共享 try { remoteServiceClient.getData("task123"); } finally { RequestContextHolder.resetRequestAttributes(); } } ``` 这里使用Spring的MockHttpServletRequest来模拟请求对象,手动添加需要的头部,然后设置到RequestContextHolder中,并允许子线程共享。这样,在Feign拦截器中就能通过RequestContextHolder获取到这些属性,进而添加请求头。 需要注意的是,这种方法可能需要引入Spring的测试模块来使用MockHttpServletRequest,或者自行实现一个简单的HttpServletRequest包装类。 另外,如果定时任务调用的Feign客户端需要传递某些特定的请求头,比如跟踪ID或认证令牌,而服务B依赖这些头信息进行验证,那么必须在定时任务触发时手动设置这些头,确保Feign调用时能正确携带。 此外,如果Hystrix或Sentinel的熔断机制导致线程切换,需要确保RequestAttributes能传递到新的线程中。例如,使用InheritableThreadLocal或者TransmittableThreadLocal来替代默认的ThreadLocal存储,但RequestContextHolder默认使用的是普通的ThreadLocal,因此在子线程中无法访问父线程的上下文。 在这种情况下,可以自定义一个RequestContextHolder,使用InheritableThreadLocal来存储属性,但这可能需要对Spring的机制有较深的了解,或者寻找其他解决方案。 另一个可能的解决方案是禁用Hystrix的线程隔离,改用信号量隔离,这样Feign调用会在同一线程中执行,避免线程切换导致ThreadLocal丢失。例如,在配置文件中设置: ```yaml hystrix: command: default: execution: isolation: strategy: SEMAPHORE ``` 但需要注意,信号量隔离可能影响系统的熔断和资源隔离能力,需根据实际情况评估。 总结,解决方案可能包括以下步骤: 1. **在定时任务中手动创建并设置RequestAttributes**,模拟HTTP请求的上下文,特别是需要的请求头。 2. **配置Feign拦截器**,使其能够处理没有实际HTTP请求的情况,或从其他来源获取必要的信息。 3. **调整Hystrix的线程隔离策略**,避免线程切换导致上下文丢失。 4. **使用线程上下文传递工具**,确保在异步或线程池任务中能够传递RequestAttributes。 现在需要将这些思路转化为具体的代码示例和配置步骤,帮助用户解决问题。同时,需要参考用户之前的问题和回答,确保一致性和准确性。</think>### Spring Feign 定时任务中 RequestContextHolder.getRequestAttributes() 为空的解决方案 #### 1. 问题根源分析 在定时任务中调用 Feign 客户端时,$RequestContextHolder.getRequestAttributes()$ 返回空的主要原因是: - **无 HTTP 请求上下文**:定时任务由调度线程触发,没有原生 HTTP 请求环境[^3] - **线程上下文隔离**:Hystrix/默认线程池切换线程后,ThreadLocal 存储丢失[^5] - **Feign 拦截器依赖缺失**:请求头传递逻辑依赖未正确初始化 #### 2. 完整解决方案 ##### 2.1 手动构建请求上下文(推荐方案) ```java @Scheduled(cron = "0 0/5 * * * ?") public void scheduledTask() { // 创建模拟请求对象 MockHttpServletRequest mockRequest = new MockHttpServletRequest(); mockRequest.addHeader("Authorization", "Bearer task_token"); mockRequest.addHeader("X-Trace-Id", UUID.randomUUID().toString()); // 绑定到当前线程 ServletRequestAttributes attributes = new ServletRequestAttributes(mockRequest); RequestContextHolder.setRequestAttributes(attributes, true); // 关键参数[^2] try { remoteService.getData(); } finally { RequestContextHolder.resetRequestAttributes(); } } ``` ##### 2.2 Feign 拦截器适配改造 ```java @Configuration public class FeignHeaderConfig { @Bean public RequestInterceptor headerInterceptor() { return template -> { // 优先尝试获取现有上下文 ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); if (attributes == null) { // 定时任务专用头生成逻辑 template.header("X-Source", "scheduler"); template.header("Authorization", generateTaskToken()); } else { // 正常请求处理逻辑 HttpServletRequest request = attributes.getRequest(); Enumeration<String> headers = request.getHeaderNames(); while (headers.hasMoreElements()) { String name = headers.nextElement(); template.header(name, request.getHeader(name)); } } }; } private String generateTaskToken() { // 实现定时任务专用令牌生成逻辑 return "TASK_" + Instant.now().getEpochSecond(); } } ``` ##### 2.3 Hystrix 线程策略调整(可选) ```yaml hystrix: command: default: execution: isolation: strategy: SEMAPHORE # 改为信号量隔离 semaphore: maxConcurrentRequests: 100 ``` #### 3. 高级场景处理 ##### 3.1 异步任务上下文传递 ```java @Bean public TaskExecutor taskExecutor() { ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); executor.setTaskDecorator(runnable -> { // 上下文传递装饰器 RequestAttributes context = RequestContextHolder.currentRequestAttributes(); return () -> { try { RequestContextHolder.setRequestAttributes(context, true); runnable.run(); } finally { RequestContextHolder.resetRequestAttributes(); } }; }); return executor; } ``` ##### 3.2 使用 TransmittableThreadLocal ```java public class ContextHolder { private static final TransmittableThreadLocal<Map<String,String>> context = new TransmittableThreadLocal<>(); public static void setContext(Map<String,String> headers) { context.set(headers); } public static Map<String,String> getContext() { return context.get(); } } // 在定时任务中设置 ContextHolder.setContext(ImmutableMap.of( "X-Trace-Id", UUID.randomUUID().toString() )); // Feign拦截器中获取 template.header("X-Trace-Id", ContextHolder.getContext().get("X-Trace-Id")); ``` #### 4. 验证方案 ```java // 测试用例示例 @Test void testSchedulerContext() { scheduledTask(); // 执行定时任务 verify(feignClient).getData(argThat(request -> { return request.headers().get("X-Trace-Id").size() > 0; })); } ```
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值