Feign 调用为服务报 HardCodedTarget(type=xxxClient, name=xxxfile, url=http://file)异常

前言

业务场景如下:
1,本项目需要新增文案数据,文案可以绑定文件,比如【文档类:xls txt ppt word pdf … 图片类:jpg jpeg jif … 音频类:mp3 mp4 avi …】
2,文件上传保存的服务是在另一个服务 我们这里取名叫 smart_app_file 为服务

代码如下

线程池的配置如下:

package com.xx.xx.config;

import lombok.Data;
import org.jetbrains.annotations.NotNull;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.task.TaskDecorator;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;

import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

/**
 * @author psd
 */
@Data
@Configuration
public class ThreadPoolConfig {

    @Value("${threadPool.coreSize}")
    Integer coreSize;

    @Value("${threadPool.maxSize}")
    Integer maxSize;
    @Value("${threadPool.keepLiveTime}")
    Integer keepLiveTime;

    @Value("${threadPool.blockQueueSize}")
    Integer blockQueueSize;

    @Bean
    public ThreadPoolExecutor threadPoolExecutor() {
        return new ThreadPoolExecutor(coreSize, maxSize, keepLiveTime, TimeUnit.SECONDS, new LinkedBlockingQueue<>(blockQueueSize));
    }
}

业务代码异步

    @ApiOperation("根据dto新增文案的数据")
    @PostMapping("/insertxxxxPlan")
    public Long insertxxxxPlan(@Validated @RequestBody PlanDto dto) {
        return cxxxPlanService.insertxxxxPlan(dto);
    }
@Override
    @Transactional(rollbackFor = Exception.class)
    public Long insertContingencyPlan(ContingencyPlanDto dto) {
        // 1.业务代码
        ... 
        // 4.修改smart_app_file中文件列表 应用个数+1. 【这里是调用smartAppFile微服务】
        int addRefCount = smartAppFileClient.batchAddRefCount(flIds);
        log.info("调用 smart_app_file 修改应用个数影响的行数有:{}", addRefCount);
        
        // 5.异步添加索引到ES
        CompletableFuture.runAsync(() -> {
            log.info("应急预案开始异步:{} , {}",planEntity , flIds);
            contingxxxPlanEsService.indexCxxxPlan(planEntity, flIds);
        }, threadCustomPoolExecutor);
        return planEntity.getId();
    }

调用远程服务的代码如下



 @Override
    public void indexCxxxPlan(ContingencyPlanEntity planEntity, Set<Long> flIds) {
       
        try {
                       log.info("smartAppFileClient 获取的数据是:{}",smartFileClient);

            // 1.获取文件信息
            R<List<FileListEntity>> fileListEntitiesr = smartAppFileClient.queryFileListByIds(flIds);
            log.info("fileListEntitiesr 返回的数据是:{} " , fileListEntitiesr);
            List<FileListEntity> fileListEntities = fileListEntitiesr.getData();
          ...
           
            ...
            // 3.构建ES文档
            ...
            // 4.索引到ES
            // 新增文档 - 请求对象
            IndexRequest indexRequest = new IndexRequest("contingency_app_plans").id(planEntity.getId().toString());
            // 添加文档数据,数据转换为Json
            String contxxxEsDocJson = objectMapper.writeValueAsString(esDocument);
            indexRequest.source(contxxxEsDocJson,  XContentType.JSON);
            IndexResponse response = esClient.index(indexRequest, RequestOptions.DEFAULT);
            log.info("新增的应急xxx的ES的结果是:{} , id是:{}", response.getResult(), response.getId());
        } catch (IOException e) {
            log.error("新增应急xxx的ES失败,应急xxx的id是:{} ", planEntity.getId(), e);
        }
    }

Feign 的代码如下


package com.xxx.xxx.service.feign;

import com.xxx;
import com.xxxo.xxx.common.web.R;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;

import java.util.List;
import java.util.Set;

/**
 * @author psd 调用 smart_app_file 模块的接口
 */
@FeignClient("file")
public interface SmartAppFileClient {

    

    /**
     * 根据ids查询文件列表集合
     *
     * @param ids
     *            文件id
     * @return List<FileListEntity>
     */
    @PostMapping("/file/queryFileListByIds")
    public R<List<FileListEntity>> queryFileListByIds(@RequestBody Set<Long> ids);

打印日志报
INFO com.xx.nx x x.service.impl.ContingencyAppPlanEsServiceImpl - smartFileClient 获取的数据是:HardCodedTarget(type=SmartAppFileClient, name=file, url=http://file)
2025-08-26 18:52:52.910 [http-nio-16710-exec-3] INFO

问题解析是:

可能的原因:

  1. Feign客户端调用失败,但没有抛出异常(因为被捕获了?),但是查看代码,在indexContingencyPlan方法中捕获的是IOException,而Feign调用可能抛出的是FeignException,属于RuntimeException,所以没有被捕获,但奇怪的是也没有看到异常日志。
  2. 线程上下文问题:Feign调用通常依赖于Spring的上下文(如请求拦截器、负载均衡等),而在异步线程中,可能无法获取到正确的上下文,导致Feign调用失败。
    但是,从日志中看到,在异步线程中打印了smartAppFileClient对象,说明该对象不是null,而且Feign客户端已经正常创建。
    另外,注意到在异步线程打印日志的同时,主线程(http-nio-16710-exec-3)打印了AuthInterceptor的后置处理日志。这提示我们可能异步线程中缺少了某些上下文,例如安全上下文、请求头等,导致Feign调用时没有正确的认证信息。
    解决方案:
  3. 确保Feign调用能够传递必要的请求头(如认证信息)。可以使用Feign的拦截器,或者自定义请求拦截器,在异步线程中手动设置请求头。
  4. 检查异步线程的线程池配置,是否支持上下文传播。如果你使用的是Spring Boot 3.x,可以考虑使用Spring Boot的异步支持并配置任务装饰器(TaskDecorator)来传递上下文。如果是较低版本,可以考虑使用其他方式(如InheritableThreadLocal)或者手动传递上下文。
    但是,从代码中看到,你使用了自定义的线程池threadCustomPoolExecutor,那么需要确保这个线程池配置了上下文传播。

解决思路

1,添加线程的上下文传播
2,组装可以上下文传播的线程池
3,异步调用获取上下文传递上下文

步骤如下:

1.添加maven依赖
<dependency>
    <groupId>org.springframework.security</groupId>
    <artifactId>spring-security-core</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-web</artifactId>
</dependency>
2. 手动传递上下文到异步线程
@Override
@Transactional(rollbackFor = Exception.class)
public Long insertxxxContingencyPlan(ContingencyxxxPlanDto dto) {
    // ... [原有代码] ...

    // 捕获当前请求上下文和安全上下文
    RequestAttributes requestAttributes = RequestContextHolder.currentRequestAttributes();
    SecurityContext securityContext = SecurityContextHolder.getContext();

    // 5.异步添加索引到ES
    CompletableFuture.runAsync(() -> {
        try {
            // 恢复上下文到异步线程
            RequestContextHolder.setRequestAttributes(requestAttributes);
            SecurityContextHolder.setContext(securityContext);
            
            log.info("xxxx开始异步:{} , {}", planEntity, flIds);
            contingencyPlanEsService.indexContingencyPlan(planEntity, flIds);
        } finally {
            // 清理上下文避免内存泄漏
            RequestContextHolder.resetRequestAttributes();
            SecurityContextHolder.clearContext();
        }
    }, threadCustomPoolExecutor);
    
    return planEntity.getId();
}
3. 配置线程池支持上下文传递

如果使用自定义线程池(threadCustomPoolExecutor),需确保其支持上下文传播。推荐使用 TaskDecorator:

@Override
@Transactional(rollbackFor = Exception.class)
public Long insertContingencyPlan(ContingencyPlanDto dto) {
    // ... [原有代码] ...

    // 捕获当前请求上下文和安全上下文
    RequestAttributes requestAttributes = RequestContextHolder.currentRequestAttributes();
    SecurityContext securityContext = SecurityContextHolder.getContext();

    // 5.异步添加索引到ES
    CompletableFuture.runAsync(() -> {
        try {
            // 恢复上下文到异步线程
            RequestContextHolder.setRequestAttributes(requestAttributes);
            SecurityContextHolder.setContext(securityContext);
            
            log.info("应急预案开始异步:{} , {}", planEntity, flIds);
            contingencyPlanEsService.indexContingencyPlan(planEntity, flIds);
        } finally {
            // 清理上下文避免内存泄漏
            RequestContextHolder.resetRequestAttributes();
            SecurityContextHolder.clearContext();
        }
    }, threadCustomPoolExecutor);
    
    return planEntity.getId();
}

总结:

1,异步调用smartAppFile 开始时候没有传上下文的参数,一般情况下会被拦截掉
2,本地测试时候当时是直接多数据源调用数据库查询的数据,发到测试环境是走的微服务调用
3,异常没有捕获到,这个也是一个误区。

更多推荐