Feign 调用为服务报 `HardCodedTarget(type=xxxClient, name=xxxfile, url=http://file)`异常
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;@Beanpublic 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.异步添加索引到ESCompletableFuture.runAsync(() -> {log.info("应急预案开始异步:{} , {}",planEntity , flIds);contingxxxPlanEsService.indexCxxxPlan(planEntity, flIds);}, threadCustomPoolExecutor);return planEntity.getId();}
调用远程服务的代码如下
@Overridepublic 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());// 添加文档数据,数据转换为JsonString 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
问题解析是:
可能的原因:
- Feign客户端调用失败,但没有抛出异常(因为被捕获了?),但是查看代码,在
indexContingencyPlan
方法中捕获的是IOException
,而Feign调用可能抛出的是FeignException,属于RuntimeException,所以没有被捕获,但奇怪的是也没有看到异常日志。 - 线程上下文问题:Feign调用通常依赖于Spring的上下文(如请求拦截器、负载均衡等),而在异步线程中,可能无法获取到正确的上下文,导致Feign调用失败。
但是,从日志中看到,在异步线程中打印了smartAppFileClient
对象,说明该对象不是null,而且Feign客户端已经正常创建。
另外,注意到在异步线程打印日志的同时,主线程(http-nio-16710-exec-3)打印了AuthInterceptor的后置处理日志。这提示我们可能异步线程中缺少了某些上下文,例如安全上下文、请求头等,导致Feign调用时没有正确的认证信息。
解决方案: - 确保Feign调用能够传递必要的请求头(如认证信息)。可以使用Feign的拦截器,或者自定义请求拦截器,在异步线程中手动设置请求头。
- 检查异步线程的线程池配置,是否支持上下文传播。如果你使用的是Spring Boot 3.x,可以考虑使用Spring Boot的异步支持并配置任务装饰器(TaskDecorator)来传递上下文。如果是较低版本,可以考虑使用其他方式(如InheritableThreadLocal)或者手动传递上下文。
但是,从代码中看到,你使用了自定义的线程池threadCustomPoolExecutor
,那么需要确保这个线程池配置了上下文传播。
解决思路
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.异步添加索引到ESCompletableFuture.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.异步添加索引到ESCompletableFuture.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,异常没有捕获到,这个也是一个误区。