Spring Boot @Async嵌套调用不生效问题排查
Spring @Async服务内部嵌套调用不生效的解决方案
问题场景
已通过@EnableAsync开启Spring异步支持,自定义了前缀为ASYNC-的线程池,@Async注解在@RestController中能正常触发异步执行,但在服务内部嵌套调用BatchLogGateway#writeBatchLog()方法时,INSERT操作始终在原线程执行,日志中看不到自定义的异步线程编号。
核心原因
Spring的@Async依赖动态代理实现,只有通过Spring容器生成的代理对象调用方法时,异步增强逻辑才会生效。如果在服务内部直接调用自身的@Async方法(绕过代理),会直接执行原始方法,无法触发异步。
解决方案
1. 通过Spring容器获取代理对象调用
在服务类中注入ApplicationContext,通过容器获取自身的代理实例,再用代理对象调用异步方法:
@Service public class BatchLogGateway { @Autowired private NamedParameterJdbcTemplate jdbcTemplate; @Autowired private ApplicationContext applicationContext; private static final Logger logger = LoggerFactory.getLogger(BatchLogGateway.class); @Async public void writeBatchLog(int batchNo, String errorTxt) { MapSqlParameterSource namedParameters = new MapSqlParameterSource(); namedParameters.addValue("batchNo", batchNo); namedParameters.addValue("error", errorTxt); try { jdbcTemplate.update("INSERT INTO batches_error_log (error_log, batch_no) values (:error, :batchNo)", namedParameters); } catch (Exception e) { logger.error("writing into batchLog {} was unsuccessful", batchNo); } } // 内部调用时使用此方法 public void triggerWriteBatchLog(int batchNo, String errorTxt) { BatchLogGateway proxy = applicationContext.getBean(BatchLogGateway.class); proxy.writeBatchLog(batchNo, errorTxt); } }
内部调用时,调用triggerWriteBatchLog而非直接调用writeBatchLog,即可触发异步逻辑。
2. 拆分异步方法到独立服务类
将writeBatchLog方法迁移到独立的服务类中,比如BatchLogAsyncService:
@Service public class BatchLogAsyncService { @Autowired private NamedParameterJdbcTemplate jdbcTemplate; private static final Logger logger = LoggerFactory.getLogger(BatchLogAsyncService.class); @Async public void writeBatchLog(int batchNo, String errorTxt) { MapSqlParameterSource namedParameters = new MapSqlParameterSource(); namedParameters.addValue("batchNo", batchNo); namedParameters.addValue("error", errorTxt); try { jdbcTemplate.update("INSERT INTO batches_error_log (error_log, batch_no) values (:error, :batchNo)", namedParameters); } catch (Exception e) { logger.error("writing into batchLog {} was unsuccessful", batchNo); } } }
然后在原服务类中注入这个新服务,通过代理对象调用:
@Service public class BatchLogGateway { @Autowired private BatchLogAsyncService batchLogAsyncService; public void someInternalMethod(int batchNo, String errorTxt) { batchLogAsyncService.writeBatchLog(batchNo, errorTxt); } }
3. 利用AopContext获取当前代理
先修改配置类,开启暴露代理:
@Configuration @EnableAsync @EnableAspectJAutoProxy(exposeProxy = true) public class ThreadConfig { @Bean public Executor taskExecutor() { ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); executor.setCorePoolSize(60); executor.setMaxPoolSize(200); executor.setThreadNamePrefix("ASYNC-"); executor.initialize(); return executor; } }
然后在服务内部调用时,通过AopContext获取代理对象:
@Service public class BatchLogGateway { @Autowired private NamedParameterJdbcTemplate jdbcTemplate; private static final Logger logger = LoggerFactory.getLogger(BatchLogGateway.class); public void someInternalMethod(int batchNo, String errorTxt) { BatchLogGateway proxy = (BatchLogGateway) AopContext.currentProxy(); proxy.writeBatchLog(batchNo, errorTxt); } @Async public void writeBatchLog(int batchNo, String errorTxt) { MapSqlParameterSource namedParameters = new MapSqlParameterSource(); namedParameters.addValue("batchNo", batchNo); namedParameters.addValue("error", errorTxt); try { jdbcTemplate.update("INSERT INTO batches_error_log (error_log, batch_no) values (:error, :batchNo)", namedParameters); } catch (Exception e) { logger.error("writing into batchLog {} was unsuccessful", batchNo); } } }
验证
执行后查看日志,若出现ASYNC-前缀的线程名,说明异步逻辑已生效。
内容的提问来源于stack exchange,提问作者Triffids
相关产品推荐
相关产品推荐

