You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何从RejectedExecutionHandler中获取@Async注解方法的入参

解决方案

问题原因

Spring 对标注 @Async 的方法会做 AOP 封装,提交到线程池的任务实际是 Spring 内部包装的 FutureTask 实例,你的业务方法入参会被封装在内部的 Callable 代理对象中,直接从传入拒绝策略的 Runnable 对象无法直接读取原始参数,这就是你能看到 arg2 字段但无法直接取值的原因。

方案1:自定义TaskDecorator透传参数(推荐)

该方案兼容性强,不受Spring版本迭代影响,是最稳定的实现方式。

  • 第一步:实现自定义任务装饰器和任务包装类,在任务提交阶段就提取并保存@Async方法的签名、入参信息
import org.springframework.core.task.TaskDecorator;
import org.springframework.aop.interceptor.AsyncExecutionAspectSupport;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.aop.aspectj.MethodSignature;

public class AsyncTaskContextDecorator implements TaskDecorator {
    @Override
    public Runnable decorate(Runnable runnable) {
        // 提交任务的主线程中提取@Async方法的元信息
        MethodSignature signature = (MethodSignature) RequestContextHolder.getRequestAttributes()
                .getAttribute(AsyncExecutionAspectSupport.METHOD_SIGNATURE, RequestAttributes.SCOPE_REQUEST);
        Object[] args = (Object[]) RequestContextHolder.getRequestAttributes()
                .getAttribute(AsyncExecutionAspectSupport.METHOD_ARGUMENTS, RequestAttributes.SCOPE_REQUEST);
        // 包装原任务,存储方法信息和入参
        return new AsyncTaskWrapper(runnable, signature.getMethod().getName(), args);
    }

    // 自定义任务包装类
    public static class AsyncTaskWrapper implements Runnable {
        private final Runnable delegate;
        private final String methodName;
        private final Object[] args;

        public AsyncTaskWrapper(Runnable delegate, String methodName, Object[] args) {
            this.delegate = delegate;
            this.methodName = methodName;
            this.args = args;
        }

        @Override
        public void run() {
            delegate.run();
        }

        public String getMethodName() { return methodName; }
        public Object[] getArgs() { return args; }
    }
}
  • 第二步:将装饰器绑定到@Async对应的线程池配置中
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.AsyncConfigurer;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import java.util.concurrent.Executor;

@Configuration
@EnableAsync
public class AsyncThreadPoolConfig implements AsyncConfigurer {
    @Override
    public Executor getAsyncExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        // 你的常规线程池配置
        executor.setCorePoolSize(4);
        executor.setMaxPoolSize(8);
        executor.setQueueCapacity(100);
        executor.setThreadNamePrefix("async-thread-");
        // 绑定自定义任务装饰器
        executor.setTaskDecorator(new AsyncTaskContextDecorator());
        // 绑定你自定义的拒绝策略
        executor.setRejectedExecutionHandler(new CustomRejectedExecutionHandler());
        executor.initialize();
        return executor;
    }
}
  • 第三步:在自定义拒绝策略中直接读取包装类的参数
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.concurrent.RejectedExecutionHandler;
import java.util.concurrent.ThreadPoolExecutor;
import com.alibaba.fastjson2.JSON;

public class CustomRejectedExecutionHandler implements RejectedExecutionHandler {
    private static final Logger log = LoggerFactory.getLogger(CustomRejectedExecutionHandler.class);

    @Override
    public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
        // 识别@Async包装的任务
        if (r instanceof AsyncTaskContextDecorator.AsyncTaskWrapper) {
            AsyncTaskContextDecorator.AsyncTaskWrapper asyncTask = (AsyncTaskContextDecorator.AsyncTaskWrapper) r;
            log.error("异步任务被丢弃,方法名:{},入参:{}", asyncTask.getMethodName(), JSON.toJSONString(asyncTask.getArgs()));
        } 
        // 你原有处理显式submit任务的逻辑
        else {
            log.error("任务被丢弃,线程名:{},线程池活跃线程数:{}", Thread.currentThread().getName(), executor.getActiveCount());
        }
        // 原有拒绝逻辑,可按需选择抛出异常/直接丢弃等
    }
}

方案2:反射读取Spring封装任务的参数(兼容旧场景)

如果不想修改现有线程池的装饰器配置,可以直接通过反射读取你Debug看到的arg2字段,需要注意适配你当前使用的Spring版本的内部字段名:

// 拒绝策略中添加参数解析方法
private Object[] getAsyncTaskArgs(Runnable runnable) {
    try {
        // 读取FutureTask内部封装的callable字段
        Field callableField = FutureTask.class.getDeclaredField("callable");
        callableField.setAccessible(true);
        Object callableProxy = callableField.get(runnable);
        // 按你Debug看到的字段名读取入参,示例中为arg$2
        Field argsField = callableProxy.getClass().getDeclaredField("arg$2");
        argsField.setAccessible(true);
        return (Object[]) argsField.get(callableProxy);
    } catch (Exception e) {
        // 解析失败返回空数组,避免影响拒绝逻辑
        return new Object[0];
    }
}

该方案的缺点是Spring版本迭代可能会修改内部封装类的字段名,存在兼容性风险。

注意事项

打印入参日志时需要注意入参对象的序列化兼容性,若存在不可序列化的对象,需要针对性做序列化适配,避免打日志时抛出额外异常。


内容的提问来源于stack exchange,提问作者william

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.10.04 01:27:03