@Async异步日志及@EnableAsync底层机制
面试速记
@EnableAsync通过@Import引入ProxyAsyncConfiguration激活异步处理,获取到核心是AsyncAnnotationBeanPostProcessor,会在 Bean 初始化阶段扫描@Async方法,利用 AOP 动态代理生成代理对象。当调用被@Async标记的方法时,代理会通过AsyncExecutionInterceptor拦截请求,解析线程池(优先自定义,否则默认),将任务封装为Callable提交到线程池异步执行。
简单实例
针对需要获取到执行结果做记录的日志操作,除了环绕通知中方法执行后获取到返回值处理,还可以在controller方法内调用目标方法返回之前进行异步日志记录,步骤如下:
/**
* 删除角色
*/
@Override
@PostMapping("/remove")
public ResponseObject<Boolean> groupDel(@RequestBody RoleUpdateReqDTO reqDTO) {
ResponseObject<Boolean> result = roleService.groupDel(reqDTO);
// 异步新增操作日志
roleService.deleteRoleOperateLogAdd(result, reqDTO.getRoleId());
return result;
}
service层中对参数进行处理封装后最终调用这个方法:
@Async("operateLogExecutor")//指定线程池异步执行
public void asyncOperateLogAdd(OperateLogDO operateLog) {
operateLogMapper.insert(operateLog);
}
通过参数指定线程池操作实现异步记录日志,此处需要配置线程池,并且开启@EnableAsync
@EnableAsync//开启异步执行机制
@Configuration
public class ThreadPoolConf {
/**
* 线程池配置
*/
@Bean
public Executor operateLogExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
// 设置核心线程数量
executor.setCorePoolSize(Runtime.getRuntime().availableProcessors());
// 设置最大线程池大小
executor.setMaxPoolSize(Runtime.getRuntime().availableProcessors() * 2);
// 设置队列容量为200个
executor.setQueueCapacity(200);
executor.setThreadNamePrefix("commander-manager-operateLogExecutor-");
// 由调用线程处理该任务
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
// 线城池等待其它任务完成再销毁
executor.setWaitForTasksToCompleteOnShutdown(true);
// 设置线城池的等待时间,如果超过这个时间还没有销毁就强制销毁.
executor.setAwaitTerminationSeconds(60);
executor.initialize();
return executor;
}
@EnableAsync底层机制
@EnableAsync 是 Spring 异步编程的核心注解,其底层机制涉及 代理模式(AOP)、后置处理器(BeanPostProcessor) 和 任务执行器(TaskExecutor) 的协同工作。以下是其激活 @Async 处理的底层实现原理:
1. 启用异步的入口:@EnableAsync
当在配置类上添加 @EnableAsync 时,Spring 会通过 @Import 机制 加载异步处理的核心组件 AsyncConfigurationSelector,其作用如下:
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(AsyncConfigurationSelector.class)//加载异步处理的核心组件
public @interface EnableAsync {
// ...
}
-
AsyncConfigurationSelector:根据 Spring 版本或配置,选择具体的异步配置类。- 默认情况下,选择
ProxyAsyncConfiguration(基于代理的异步配置)。 - 若需要响应式编程支持,可能选择其他配置类(如
ReactiveAsyncConfiguration)。
public class AsyncConfigurationSelector extends AdviceModeImportSelector<EnableAsync> { private static final String ASYNC_EXECUTION_ASPECT_CONFIGURATION_CLASS_NAME = "org.springframework.scheduling.aspectj.AspectJAsyncConfiguration"; @Override @Nullable public String[] selectImports(AdviceMode adviceMode) { switch (adviceMode) { case PROXY: return new String[] {ProxyAsyncConfiguration.class.getName()}; case ASPECTJ: return new String[] {ASYNC_EXECUTION_ASPECT_CONFIGURATION_CLASS_NAME}; default: return null; } } } - 默认情况下,选择
2. 核心配置类:ProxyAsyncConfiguration
ProxyAsyncConfiguration 是异步处理的 配置中枢,主要负责注册 AsyncAnnotationBeanPostProcessor(关键后置处理器):
@Configuration
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
public class ProxyAsyncConfiguration extends AbstractAsyncConfiguration {
@Bean(name = TaskManagementConfigUtils.ASYNC_ANNOTATION_PROCESSOR_BEAN_NAME)
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
public AsyncAnnotationBeanPostProcessor asyncAdvisor() {
// 创建 AsyncAnnotationBeanPostProcessor
AsyncAnnotationBeanPostProcessor bpp = new AsyncAnnotationBeanPostProcessor();
// 配置自定义的 AsyncAnnotationAdvisor(负责处理 @Async 注解)
bpp.configure(this.executor, this.exceptionHandler);
// 设置代理目标类(CGLIB 或 JDK 动态代理)
bpp.setProxyTargetClass(this.proxyTargetClass);
bpp.setOrder(this.order);
return bpp;
}
}
3. 异步注解处理器:AsyncAnnotationBeanPostProcessor
AsyncAnnotationBeanPostProcessor 是 Bean 后置处理器,负责在 Bean 初始化阶段扫描 @Async 注解,并为其创建 AOP 代理:
关键步骤:
- Bean 初始化后处理
在
postProcessAfterInitialization方法中,遍历所有 Spring Bean,检查是否存在@Async注解的方法。 - 创建 Advisor
通过
AsyncAnnotationAdvisor创建 AOP 切面,定义以下内容:- Pointcut:匹配所有被
@Async注解标记的方法。 - Advice:使用
AsyncExecutionInterceptor拦截方法调用,将其提交到线程池异步执行。
- Pointcut:匹配所有被
- 生成代理对象
若 Bean 中存在
@Async方法,则通过 JDK 动态代理 或 CGLIB 代理 生成代理对象,替换原始 Bean。
4. 方法拦截器:AsyncExecutionInterceptor
AsyncExecutionInterceptor 是实际处理异步执行的核心拦截器,继承自 MethodInterceptor。其工作流程如下:
拦截方法调用
- 确定线程池
根据
@Async注解的value参数(如operateLogExecutor),查找对应的TaskExecutor线程池:- 若未指定线程池名称,使用默认的
TaskExecutor(默认是SimpleAsyncTaskExecutor)。 - 若找不到指定线程池,抛出异常。
- 若未指定线程池名称,使用默认的
- 提交任务到线程池
将原始方法的调用封装为
Callable任务,提交到线程池:
public Object invoke(final MethodInvocation invocation) throws Throwable {
// 获取线程池
Executor executor = getExecutor(invocation.getMethod());
// 封装为 Callable 任务
Callable<Object> task = () -> {
try {
return invocation.proceed();
} catch (Throwable ex) {
throw new IllegalStateException("Async method异常", ex);
}
};
// 提交到线程池
return doSubmit(task, executor, invocation.getMethod().getReturnType());
}
处理返回值
- 若方法返回
Future或CompletableFuture,返回实际的异步结果。 - 若方法无返回值(
void),直接提交任务,不等待结果。
5. 线程池选择与执行
线程池解析逻辑
@Async指定名称:优先从 Spring 容器中查找对应名称的ExecutorBean。- 未指定名称:查找默认的
ExecutorBean(默认名称为taskExecutor或通过AsyncConfigurer配置)。 - 无任何配置:回退到
SimpleAsyncTaskExecutor(每次创建新线程,不推荐生产使用)。
线程池任务执行
- 核心线程池参数:通过
ThreadPoolTaskExecutor配置核心线程数、队列容量、拒绝策略等。 - 任务提交顺序:优先使用核心线程 → 队列 → 最大线程 → 拒绝策略。
6. 上下文传递
默认情况下,异步线程不会继承主线程的上下文(如 SecurityContext、Transaction)。若需传递上下文,可通过以下方式:
TaskDecorator:自定义装饰器,复制上下文到子线程:
@Bean("operateLogExecutor")
public Executor operateLogExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setTaskDecorator(new ContextCopyingTaskDecorator());
// 其他配置...
return executor;
}
总结:@EnableAsync 的底层流程
- 启动阶段
@EnableAsync导入ProxyAsyncConfiguration,注册AsyncAnnotationBeanPostProcessor。 - Bean 初始化阶段
AsyncAnnotationBeanPostProcessor扫描所有 Bean,为包含@Async方法的 Bean 创建代理。 - 方法调用阶段
代理对象拦截
@Async方法调用,通过AsyncExecutionInterceptor将任务提交到线程池。 - 任务执行阶段 线程池分配独立线程执行原始方法逻辑,主线程继续执行后续流程。
关键源码类
AsyncAnnotationBeanPostProcessor |
扫描 @Async 方法并创建代理。 |
AsyncAnnotationAdvisor |
定义切面逻辑(Pointcut + Advice)。 |
AsyncExecutionInterceptor |
拦截方法调用,提交任务到线程池。 |
ThreadPoolTaskExecutor |
Spring 封装的线程池实现,替代原生 ThreadPoolExecutor。 |
SimpleAsyncTaskExecutor |
默认线程池(非池化,每次新建线程)。 |
正浩创新EcoFlow公司福利 515人发布