如何从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
相关产品推荐
相关产品推荐

