多线程之HardCodedTarget(type=OssFileClient, name=file, url=http://file)异常

多线程之HardCodedTarget(type=OssFileClient, name=file, url=http://file)异常

摘要: 文档描述了多线程环境下调用Feign客户端OssFileClient时出现的HardCodedTarget异常。异常发生在异步保存文件到ES时,Feign调用未返回预期结果而直接打印了客户端对象。问题分析指出可能原因:1)Feign调用失败但未被捕获;2)异步线程缺少Spring上下文导致认证失败。解决方案包括:1)配置支持上下文传播的线程池TaskDecorator;2)手动传递请求和Security上下文到异步线程。文中提供了ThreadPoolConfig配置类和ContextCopyingTaskDecorator实现,确保主线程上下文能正确传递到异步任务中。

前言

1,异常场景如下,文件上传使用多线程调用微服务OssFile异步保存文件,日志报多线程之HardCodedTarget(type=OssFileClient, name=file, url=http://file)异常

原代码如下

1,主业务代码

c 复制代码
    @Override
    @Transactional(rollbackFor = Exception.class)
    public Long inserTemergencyProcessingMessage(TemergencyProcessingDto dto) {
        // 1.获取当前登录人的信息
        // 2.紧急处理演练文档保存...
        ... 
        // 5.异步添加索引到ES
        CompletableFuture.runAsync(() -> {
            log.info("紧急处理演练文档开始异步:{} , {}",temergencyPlanEntity , flIds);
            temergencyProcessingEsService.indextemergencyProcessing(temergencyPlanEntity, flIds);
        }, threadCustomPoolExecutor);
        return planEntity.getId();
    }

保存ES的代码如下

c 复制代码
 @Override
    public void indextemergencyProcessing(TemergencyPlanEntity planEntity, Set<Long> flIds) {
        try {
           	// ... 业务代码查询文件信息 
            // 3.构建ES文档
            TemergencyPlanEsDocument esDocument = buildEsDocument(planEntity, fileListEntities, fileContentsMap);
            // 4.索引到ES
            // 新增文档 - 请求对象
            IndexRequest indexRequest = new IndexRequest("temergencn_plans").id(planEntity.getId().toString());
            // 添加文档数据,数据转换为Json
           ...
        } catch (IOException e) {
            log.error("...的ES失败,...的id是:{} ", planEntity.getId(), e);
        }
    }

2,错误日志如下

... ... fiIds:[54]2025-08-26 18:52:52.905 [pool-2-thread-1] INFO com.xx.xxxefileapi.service.impl.TemergencyProcessingEsService - smartFileClient 获取的数据是:HardCodedTarget(type=OssFileClient, name=file, url=http://file)

2025-08-26 18:52:52.910 [http-nio-16710-exec-3] INFO

3,问题排查思路

1,发现异步线程中调用了smartOssFileClient.queryFileListByIds(flIds),但是在日志中并没有打印出调用该Feign客户端后的结果(即没有打印fileListEntitiesr 返回的数据是:),而是直接打印了ossFileClient对象,显示为HardCodedTarget(type=OssFileClient, name=file, url=http://file)。

1.1 Feign客户端调用失败,但是没有异常捕获,但是查看代码,在indextemergencyProcessing方法中捕获的是IOException,而Feign调用可能抛出的是FeignException,属于RuntimeException,所以没有被捕获,但奇怪的是也没有看到异常日志。

1.2 线程上下文问题:Feign调用通常依赖于Spring的上下文(如请求拦截器、负载均衡等),而在异步线程中,可能无法获取到正确的上下文,导致Feign调用失败。

但是,从日志中看到,在异步线程中打印了ossFileClient对象,说明该对象不是null,而且Feign客户端已经正常创建。

另外,注意到在异步线程打印日志的同时,主线程(http-nio-16710-exec-3)打印了AuthInterceptor的后置处理日志。这提示我们可能异步线程中缺少了某些上下文,例如安全上下文、请求头等,导致Feign调用时没有正确的认证信息。

解决方案:

1,确保Feign调用能够传递必要的请求头(如认证信息)。可以使用Feign的拦截器,或者自定义请求拦截器,在异步线程中手动设置请求头。

2,检查异步线程的线程池配置,是否支持上下文传播。如果你使用的是Spring Boot 3.x,可以考虑使用Spring Boot的异步支持并配置任务装饰器(TaskDecorator)来传递上下文。如果是较低版本,可以考虑使用其他方式(如InheritableThreadLocal)或者手动传递上下文。

4,手动传递上下文到异步线程

1,配置线程支持上下文传递

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

c 复制代码
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 {

    @Bean("threadCustomPoolExecutorAsync")
    public ThreadPoolTaskExecutor threadCustomPoolExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(coreSize);
        executor.setMaxPoolSize(maxSize);
        executor.setQueueCapacity(blockQueueSize);
        executor.setTaskDecorator(new ContextCopyingTaskDecorator());
        executor.setThreadNamePrefix("Async-Executor-");
        executor.initialize();
        return executor;
    }
    public static class ContextCopyingTaskDecorator implements TaskDecorator {
        @Override
        public @NotNull Runnable decorate(@NotNull Runnable runnable) {
            // 捕获主线程上下文
            RequestAttributes requestContext = RequestContextHolder.currentRequestAttributes();
            SecurityContext securityContext = SecurityContextHolder.getContext();

            return () -> {
                try {
                    // 将主线程上下文设置到异步线程
                    RequestContextHolder.setRequestAttributes(requestContext, true);
                    SecurityContextHolder.setContext(securityContext);
                    runnable.run();
                } finally {
                    // 清理异步线程上下文
                    RequestContextHolder.resetRequestAttributes();
                    SecurityContextHolder.clearContext();
                }
            };
        }
    }
}
2,手动传递上下文到异步线程
c 复制代码
@Resource
private ThreadPoolExecutor threadCustomPoolExecutor;
    
@Override
@Transactional(rollbackFor = Exception.class)
public Long inserTemergencyProcessingMessage(ContingencyPlanDto dto) {
    // ... [原有代码] ...

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

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

最终代码调试到重点:

1,手动传递并恢复上下文(RequestContext + SecurityContext)

2,配置线程池的TaskDecorator确保上下文传播。

3,确认Feign拦截器正确配置用于 token 传递。

喜欢我的文章记得点个在看,或者点赞,持续更新中ing...

相关推荐
Bruce_Liuxiaowei3 小时前
权限维持:操作系统后门技术分析与防护
网络·安全·web安全
利刃大大4 小时前
【高并发服务器:HTTP应用】十六、HttpContext上下文模块 && HttpServer服务器模块&& 服务器测试
运维·服务器·http·高并发·项目
是专家不是砖家5 小时前
rk3562 udp发送带宽500Mbps出现丢包问题
网络·网络协议·udp·rk3562·udp丢包·t507
wanhengidc5 小时前
云手机通常使用什么架构
服务器·网络·安全·游戏·智能手机·云计算
在路上看风景6 小时前
5.1 路由选择算法
网络
xiaoxiongip6666 小时前
假设两个设备在不同网段,网关怎么设置才能通呢
网络·爬虫·python·https·智能路由器
Full Stack Developme6 小时前
java.net.http 包详解
java·http·.net
Li zlun7 小时前
TCP/IP协议:互联网的基石与通信灵魂
网络·网络协议·tcp/ip
SSL店小二7 小时前
IP SSL证书申请全过程及注意事项
服务器·网络·网络协议·https·ssl
NewCarRen7 小时前
自动驾驶与联网车辆网络安全:系统级威胁分析与韧性框架
网络·网络安全·自动驾驶