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;
@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
问题解析是:
可能的原因:
- 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,添加线程的上下文传播
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,异常没有捕获到,这个也是一个误区。
更多推荐


所有评论(0)