java每日精进 5.27【异步实现】
第一部分:异步任务实现的逐步解析
项目通过 spring-boot-starter-job 技术组件提供异步任务功能,利用 Spring 的 @Async 注解和线程池执行异步操作。结合 TransmittableThreadLocal 解决上下文传递问题(如多租户上下文)。以下以 API 访问日志模块 的异步记录为例,逐步解析实现过程。
1.1 异步任务实现原理
异步任务实现基于以下步骤:
- 启用异步支持:通过 @EnableAsync 启用 Spring 的异步功能,配置线程池。
- 上下文传递:使用 TransmittableThreadLocal(TTL)确保异步线程继承主线程的上下文(如租户 ID)。
- 添加 @Async 注解:在需要异步执行的方法上添加 @Async,将其提交到线程池执行。
- 业务逻辑:异步方法调用核心逻辑(如插入日志),不阻塞主线程。
1.2 代码实现步骤
步骤 1:配置异步支持
在 YudaoAsyncAutoConfiguration 类中,启用异步功能并配置线程池以支持 TransmittableThreadLocal。
// 导入必要的 Spring 注解,用于自动配置和异步支持
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.beans.BeansException;
import com.alibaba.ttl.TtlRunnable;// 使用 @AutoConfiguration 注解,标记此类为 Spring Boot 自动配置类
// 该注解表明此类会被 Spring Boot 的自动配置机制自动加载,优先级高于 @Configuration
@AutoConfiguration
// 使用 @EnableAsync 注解,启用 Spring 应用上下文中的异步方法执行支持
// 允许使用 @Async 注解的方法在独立的线程中运行
@EnableAsync
public class YudaoAsyncAutoConfiguration {// 定义一个 Spring Bean,用于自定义 ThreadPoolTaskExecutor 类型的 bean// @Bean 注解将方法的返回值注册为 Spring 容器管理的 bean// 返回值: BeanPostProcessor 类型的 bean,用于在 bean 初始化时进行自定义处理@Beanpublic BeanPostProcessor threadPoolTaskExecutorBeanPostProcessor() {// 返回一个匿名内部类,实现 BeanPostProcessor 接口// BeanPostProcessor 是一个 Spring 扩展点,允许在 bean 生命周期中对其进行自定义处理return new BeanPostProcessor() {// 实现 BeanPostProcessor 接口的 postProcessBeforeInitialization 方法// 该方法在 bean 初始化之前(例如 @PostConstruct 之前)被调用// 用途: 检查 bean 是否为 ThreadPoolTaskExecutor 类型,并为其添加任务装饰器// 输入参数:// - bean: 当前正在处理的 bean 实例(Spring 容器中的任意 bean)// - beanName: 该 bean 在 Spring 应用上下文中的名称// 返回值: 返回修改后的 bean 实例,或原 bean 实例(如果未修改)// 异常: 如果 bean 处理过程中发生错误,抛出 BeansException@Overridepublic Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {// 判断当前 bean 是否为 ThreadPoolTaskExecutor 类型的实例// 如果不是,则直接返回原 bean,不做任何修改if (!(bean instanceof ThreadPoolTaskExecutor)) {return bean;}// 将 bean 转换为 ThreadPoolTaskExecutor 类型// ThreadPoolTaskExecutor 是 Spring 提供的线程池任务执行器,用于异步任务处理ThreadPoolTaskExecutor executor = (ThreadPoolTaskExecutor) bean;// 为线程池任务执行器设置任务装饰器,使用 Alibaba 的 TtlRunnable// TtlRunnable 是 Alibaba TransmittableThreadLocal 的工具类,用于在异步线程中传递 ThreadLocal 变量// 作用: 确保 ThreadLocal 变量(例如上下文信息)在异步任务中可以被正确传递和访问executor.setTaskDecorator(TtlRunnable::get);// 返回修改后的 ThreadPoolTaskExecutor 实例return executor;}};}
}
解释:
- @EnableAsync:启用 Spring 的异步支持,允许使用 @Async 注解。
- BeanPostProcessor:拦截 ThreadPoolTaskExecutor bean 的初始化过程,添加 TtlRunnable 装饰器。
- TransmittableThreadLocal:由 Alibaba 提供的 TTL 库(com.alibaba:transmittable-thread-local)确保线程上下文(如租户 ID)从主线程传递到异步线程。
- TtlRunnable:TTL 提供的装饰器,将主线程的上下文包装到异步任务中,解决异步执行时上下文丢失问题(如 TenantContextHolder 的租户 ID)。
步骤 2:引入依赖
在 yudao-module-system-infra 模块中,引入 yudao spring-boot-starter-job 依赖以启用异步功能。
<dependency><groupId>cn.iocoder.boot</groupId><artifactId>yudao spring-boot-starter-job</artifactId>
</dependency>
解释:
- 依赖作用:yudao spring-boot-starter-job 提供异步任务和定时任务的支持,包含必要的线程池配置和 TTL 集成。
- 模块化:yudao 项目将异步功能封装为 starter,方便模块复用。
步骤 3:定义异步接口方法
在 ApiAccessLogApi 接口中,定义异步方法 createApiAccessLogAsync,并添加 @Async 注解。
public interface ApiAccessLogApi {/*** 创建 API 访问日志** @param createDTO 创建信息*/void createApiAccessLog(@Valid ApiAccessLogCreateReqDTO createDTO);/*** 【异步】创建 API 访问日志** @param createDTO 访问日志 DTO*/@Asyncdefault void createApiAccessLogAsync(ApiAccessLogCreateReqDTO createDTO) {createApiAccessLog(createDTO);}
}
解释:
- createApiAccessLog:同步方法,执行日志插入逻辑。
- createApiAccessLogAsync:异步方法,通过 @Async 标记,调用同步方法。default 关键字允许在接口中提供默认实现。
- @Async:指示 Spring 将方法提交到线程池异步执行,调用后立即返回,不阻塞主线程。
步骤 4:实现服务逻辑
在 ApiAccessLogApiImpl 和 ApiAccessLogServiceImpl 类中,实现日志插入逻辑。
@Service
@Validated
public class ApiAccessLogApiImpl implements ApiAccessLogApi {@Resourceprivate ApiAccessLogService apiAccessLogService;@Overridepublic void createApiAccessLog(ApiAccessLogCreateReqDTO createDTO) {apiAccessLogService.createApiAccessLog(createDTO);}
}
@Service
@Validated
@Slf4j
public class ApiAccessLogServiceImpl implements ApiAccessLogService {@Resourceprivate ApiAccessLogMapper apiAccessLogMapper;@Overridepublic void createApiAccessLog(ApiAccessLogCreateReqDTO createDTO) {ApiAccessLogDO apiAccessLog = BeanUtils.toBean(createDTO, ApiAccessLogDO.class);apiAccessLog.setRequestParams(StrUtils.maxLength(apiAccessLog.getRequestParams(), REQUEST_PARAMS_MAX_LENGTH));apiAccessLog.setResultMsg(StrUtils.maxLength(apiAccessLog.getResultMsg(), RESULT_MSG_MAX_LENGTH));if (TenantContextHolder.getTenantId() != null) {apiAccessLogMapper.insert(apiAccessLog);} else {// 忽略租户上下文,避免插入失败TenantUtils.executeIgnore(() -> apiAccessLogMapper.insert(apiAccessLog));}}
}
解释:
- ApiAccessLogApiImpl:实现接口,调用 ApiAccessLogService 的同步方法。
- ApiAccessLogServiceImpl:
- 转换 DTO 为 DO(ApiAccessLogDO)。
- 限制请求参数和结果消息的长度(REQUEST_PARAMS_MAX_LENGTH, RESULT_MSG_MAX_LENGTH)。
- 处理多租户:如果上下文有租户 ID,直接插入;否则使用 TenantUtils.executeIgnore 忽略租户隔离插入日志。
- 异步调用:业务代码调用 createApiAccessLogAsync,该方法通过 @Async 在线程池中执行 createApiAccessLog,不阻塞主线程。
步骤 5:使用异步方法
在业务逻辑中,调用 ApiAccessLogApi.createApiAccessLogAsync 异步记录日志。
1.3 实现总结
- 异步支持:通过 @EnableAsync 和 ThreadPoolTaskExecutor 启用异步,配置 TTL 解决上下文传递。
- 依赖:引入 yudao spring-boot-starter-job,提供线程池和异步功能。
- 方法定义:在接口中定义异步方法,添加 @Async 注解,调用同步逻辑。
- 业务逻辑:异步方法插入日志,结合多租户支持,确保数据正确性。
- 优势:异步记录日志不阻塞主线程,提升系统性能。