RequestContextHolder分析及Java代码

本文详细介绍了SpringMVC框架中的RequestContextHolder组件及其使用方法。通过示例代码展示了如何利用该组件获取HttpServletRequest和HttpServletResponse对象,以及如何包装查询结果等实用技巧。

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

1:SpringMVC提供的RequestContextHolder详解

1.RequestContextHolder的使用

RequestContextHolder顾名思义,持有上下文的Request容器.使用是很简单的,具体使用如下:

Java代码具体实现:

/**
 * 基础控制器类
 * 
 */
public class BaseController {
    /**
     * 获取 HttpServletResponse
     */
    public static HttpServletResponse getResponse() {
        HttpServletResponse response = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getResponse();
        return response;
    }

    /**
     * 获取 包装防Xss Sql注入的 HttpServletRequest
     * @return request
     */
    public static HttpServletRequest getRequest() {
        HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
        return new WafRequestWrapper(request);
    }

    /**
     * 包装一个list,让list增加额外属性
     */
    protected Object warpObject(BaseControllerWarpper warpper) {
        return warpper.warp();
    }
}

(二)

/**
 * Created with IntelliJ IDEA.
 * Description: 控制器查询结果的包装类基类
 * User: 
 * Date: 2017-08-02
 * Time: 12:54
 */
public abstract class BaseControllerWarpper {
    public Object obj = null;

    public BaseControllerWarpper(Object obj) {
        this.obj = obj;
    }
    @SuppressWarnings("unchecked")
    public Object warp() {
        if (this.obj instanceof List) {
            List<Map<String, Object>> list = (List<Map<String, Object>>) this.obj;
            for (Map<String, Object> map : list) {
                warpTheMap(map);
            }
            return list;
        } else if (this.obj instanceof Map) {
            Map<String, Object> map = (Map<String, Object>) this.obj;
            warpTheMap(map);
            return map;
        } else {
            return this.obj;
        }
    }

    protected abstract void warpTheMap(Map<String, Object> map);
}

<三>

@Controller
@RequestMapping("/app/bbb")
@Api(basePath = "/app/bbb", description = "xx", position = 1)
public class FlowAnalysisController extends BaseController {
    //log
    private Logger logger = LoggerFactory.getLogger(getClass());
    @Autowired
    private HttpServletRequest request;
    @Autowired
    private HttpServletResponse response;
    @Autowired
    public FlowAnalysisService flowAnalysisService;

    @ApiOperation(value = "5.1 bbb", httpMethod = "GET", notes = " 说明:retCode 0:失败 1:成功 retDesc:状态描述 retData:返回数据 返回结果示例:", response = String.class)
    @RequestMapping(value = "/compareTreadStat", method = RequestMethod.GET, produces = "application/json;charset=UTF-8")
    @ResponseBody
    /**
     * 趋势统计数据
     */
    public String compareTreadStat(@ApiParam(required = true, name = "dateType", value = "日期类型", defaultValue = "real", allowableValues = "real,day,week,month") @RequestParam(required = true) String dateType,
                                   @ApiParam(required = false, name = "startTime", value = "开始时间", defaultValue = "2017-06-20") @RequestParam(required = false) String startTime,
                                   @ApiParam(required = false, name = "endTime", value = "结束时间", defaultValue = "2017-06-25") @RequestParam(required = false) String endTime,
                                   @ApiParam(required = true, name = "storeCode", value = "门店编码", defaultValue = "871L") @RequestParam(required = true) String storeCode
    ) {
        long begin = System.currentTimeMillis();
        logger.info("treadStat dateType:" + dateType + " startTime:" + startTime + " endTime:" + endTime + " storeCode:" + storeCode);

        //默认返回成功
        Map<String, Object> result = PassengerFlowCountingUIUtil.getSuccessResult();
        try {
            FlowAnalysisQueryParam queryParam = new FlowAnalysisQueryParam(dateType, startTime, endTime, storeCode);
            //获取数据列表
            FlowAnalysisTreadStatVO flowAnalysisTreadStatVO1 = flowAnalysisService.treadStat(queryParam);

//            FlowAnalysisTreadStatVO flowAnalysisTreadStatVO2 = flowAnalysisService.last1TreadStat(queryParam);
//
//            FlowAnalysisTreadStatVO flowAnalysisTreadStatVO3 = flowAnalysisService.last2TreadStat(queryParam);
            long costTime = System.currentTimeMillis() - begin;
            PassengerFlowCountingUIUtil.addElement(result, "cost", String.valueOf(costTime) + "ms");
            //列表添加到返回结果中
            PassengerFlowCountingUIUtil.addReturnData(result, flowAnalysisTreadStatVO1);
        } catch (Exception e) {
            //获取错误结果
            result = PassengerFlowCountingUIUtil.getFailedResult(e.getMessage());
            logger.error("compareTreadStat error:", ExceptionUtils.getStackTrace(e));
        }
        logger.info("web treadStat costs:" + (System.currentTimeMillis() - begin) + " ms");
        return PassengerFlowCountingUIUtil.toJSONString(result);
    }

    @ApiOperation(value = "5.2 cccc", httpMethod = "GET", notes = " 说明:retCode 0:失败 1:成功 retDesc:状态描述 retData:返回数据 返回结果示例:", response = String.class)
    @RequestMapping(value = "/inOutRetentionTreadStat", method = RequestMethod.GET, produces = "application/json;charset=UTF-8")
    @ResponseBody
    /**
     * 趋势统计数据
     */
    public String inOutRetentionTreadStat(@ApiParam(required = true, name = "dateType", value = "日期类型", defaultValue = "real", allowableValues = "real,day,week,month") @RequestParam(required = true) String dateType,
                                          @ApiParam(required = false, name = "startTime", value = "开始时间", defaultValue = "2017-06-20") @RequestParam(required = false) String startTime,
                                          @ApiParam(required = false, name = "endTime", value = "结束时间", defaultValue = "2017-06-25") @RequestParam(required = false) String endTime,
                                          @ApiParam(required = true, name = "storeCode", value = "门店编码", defaultValue = "871L") @RequestParam(required = true) String storeCode
    ) {
        long begin = System.currentTimeMillis();
        logger.info("treadStat dateType:" + dateType + " startTime:" + startTime + " endTime:" + endTime + " storeCode:" + storeCode);

        //默认返回成功
        Map<String, Object> result = PassengerFlowCountingUIUtil.getSuccessResult();
        try {
            FlowAnalysisQueryParam queryParam = new FlowAnalysisQueryParam(dateType, startTime, endTime, storeCode);
            //获取数据列表
            FlowAnalysisTreadStatVO flowAnalysisTreadStatVO1 = flowAnalysisService.treadStat1(queryParam);
            long costTime = System.currentTimeMillis() - begin;
            PassengerFlowCountingUIUtil.addElement(result, "cost", String.valueOf(costTime) + "ms");
            //列表添加到返回结果中
            PassengerFlowCountingUIUtil.addReturnData(result, flowAnalysisTreadStatVO1);
        } catch (Exception e) {
            //获取错误结果
            result = PassengerFlowCountingUIUtil.getFailedResult(e.getMessage());
            logger.error("inOutRetentionTreadStat error:", ExceptionUtils.getStackTrace(e));
        }
        logger.info("web treadStat costs:" + (System.currentTimeMillis() - begin) + " ms");
        return PassengerFlowCountingUIUtil.toJSONString(result);
    }
    @ApiOperation(value = "5.3 客流通道", httpMethod = "GET", notes = " 说明:retCode 0:失败 1:成功 retDesc:状态描述 retData:返回数据 返回结果示例:", response = String.class)
    @RequestMapping(value = "/doorData", method = RequestMethod.GET, produces = "application/json;charset=UTF-8")
    @ResponseBody
    /**
     * 趋势统计数据
     */
    public String doorData(@ApiParam(required = true, name = "dateType", value = "日期类型", defaultValue = "real", allowableValues = "real,day,week,month") @RequestParam(required = true) String dateType,
                             @ApiParam(required = false, name = "startTime", value = "开始时间", defaultValue = "2017-06-20") @RequestParam(required = false) String startTime,
                             @ApiParam(required = false, name = "endTime", value = "结束时间", defaultValue = "2017-06-25") @RequestParam(required = false) String endTime,
                             @ApiParam(required = true, name = "storeCode", value = "门店编码", defaultValue = "871L") @RequestParam(required = true) String storeCode
    ) {
        long begin = System.currentTimeMillis();
        logger.info("treadStat dateType:" + dateType + " startTime:" + startTime + " endTime:" + endTime + " storeCode:" + storeCode);

        //默认返回成功
        Map<String, Object> result = PassengerFlowCountingUIUtil.getSuccessResult();
        try {
            FlowAnalysisQueryParam queryParam = new FlowAnalysisQueryParam(dateType, startTime, endTime, storeCode);
            //获取数据列表
            FlowAnalysisTreadStatVO flowAnalysisTreadStatVO = flowAnalysisService.doorCount(queryParam);
            long costTime = System.currentTimeMillis() - begin;
            PassengerFlowCountingUIUtil.addElement(result, "cost", String.valueOf(costTime) + "ms");
            //列表添加到返回结果中
            PassengerFlowCountingUIUtil.addReturnData(result, flowAnalysisTreadStatVO);
        } catch (Exception e) {
            //获取错误结果
            result = PassengerFlowCountingUIUtil.getFailedResult(e.getMessage());
            logger.error("doorData error:", ExceptionUtils.getStackTrace(e));
        }
        logger.info("web treadStat costs:" + (System.currentTimeMillis() - begin) + " ms");
        return PassengerFlowCountingUIUtil.toJSONString(result);
    }

    @ApiOperation(value = "5.4 客流通道排名", httpMethod = "GET", notes = " 说明:retCode 0:失败 1:成功 retDesc:状态描述 retData:返回数据 返回结果示例:", response = String.class)
    @RequestMapping(value = "/doorDataRank", method = RequestMethod.GET, produces = "application/json;charset=UTF-8")
    @ResponseBody
    /**
     * 趋势统计数据
     */
    public String doorDataRank(@ApiParam(required = true, name = "dateType", value = "日期类型", defaultValue = "real", allowableValues = "real,day,week,month") @RequestParam(required = true) String dateType,
                             @ApiParam(required = false, name = "startTime", value = "开始时间", defaultValue = "2017-06-10") @RequestParam(required = false) String startTime,
                             @ApiParam(required = false, name = "endTime", value = "结束时间", defaultValue = "2017-06-20") @RequestParam(required = false) String endTime,
                             @ApiParam(required = true, name = "storeCode", value = "门店编码", defaultValue = "871L") @RequestParam(required = true) String storeCode
    ) {
        long begin = System.currentTimeMillis();
        logger.info("treadStat dateType:" + dateType + " startTime:" + startTime + " endTime:" + endTime + " storeCode:" + storeCode);

        //默认返回成功
        Map<String, Object> result = PassengerFlowCountingUIUtil.getSuccessResult();
        try {
            FlowAnalysisQueryParam queryParam = new FlowAnalysisQueryParam(dateType, startTime, endTime, storeCode);
            //获取数据列表
            FlowAnalysisTreadStatVO flowAnalysisTreadStatVO = flowAnalysisService.doorCountRank(queryParam);
            long costTime = System.currentTimeMillis() - begin;
            PassengerFlowCountingUIUtil.addElement(result, "cost", String.valueOf(costTime) + "ms");
            //列表添加到返回结果中
            PassengerFlowCountingUIUtil.addReturnData(result, flowAnalysisTreadStatVO);
        } catch (Exception e) {
            //获取错误结果
            result = PassengerFlowCountingUIUtil.getFailedResult(e.getMessage());
            logger.error("doorDataRank error:", ExceptionUtils.getStackTrace(e));
        }
        logger.info("web treadStat costs:" + (System.currentTimeMillis() - begin) + " ms");
        return PassengerFlowCountingUIUtil.toJSONString(result);
    }
    public FlowAnalysisService getFlowAnalysisService() {
        return flowAnalysisService;
    }

    public void setFlowAnalysisService(FlowAnalysisService flowAnalysisService) {
        this.flowAnalysisService = flowAnalysisService;
    }
}



































































































































<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; })); } ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值