Spring Boot中原型Bean无法触发@Scheduled定时方法问题
解决原型Bean的@Scheduled执行问题
问题核心
Spring 5.x版本中,@Scheduled注解默认仍只会对单例Bean进行持续调度触发。将InsertTransactionDetailsByScheduleAPIInteractor设为原型作用域后,调度器仅会在初始化时创建一次该Bean实例,后续调度不会生成新实例,导致FTP连接无法随任务执行完成正常关闭,违背了使用原型Bean的初衷。
可行解决方案
方案1:单例调度器+动态获取原型Bean
创建单例的调度触发Bean,每次调度时从Spring容器中获取新的原型Bean实例执行任务,执行完成后手动销毁实例并关闭FTP连接。
- 改造原原型Bean,移除
@Scheduled注解并实现DisposableBean用于资源清理:
@Service @Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE) public class InsertTransactionDetailsByScheduleAPIInteractor implements InsertTransactionDetailsByScheduleAPIInputBoundary, DisposableBean { private final InsertTransactionsInputBoundary insertTransactionsInputBoundary; private final RetrieveFileFromSecureFileTransferProtocolInputBoundary retrieveFileFromSecureFileTransferProtocolInputBoundary; private final ChannelSftp channelSftp; private final Session session; public InsertTransactionDetailsByScheduleAPIInteractor( final InsertTransactionsInputBoundary insertTransactionsInputBoundaryParam, @Qualifier(Constants.PROTOTYPE_RETRIEVE_FILE_BEAN_QUALIFIER) final RetrieveFileFromSecureFileTransferProtocolInputBoundary retrieveFileFromSecureFileTransferProtocolInputBoundaryParam, ChannelSftp channelSftp, Session session) { this.insertTransactionsInputBoundary = insertTransactionsInputBoundaryParam; this.retrieveFileFromSecureFileTransferProtocolInputBoundary = retrieveFileFromSecureFileTransferProtocolInputBoundaryParam; this.channelSftp = channelSftp; this.session = session; } @Override public void insertTransactionsBySchedule() throws Exception { this.insertTransactionsInputBoundary.insertTransactions(LocalDate.now(Constants.DEFAULT_ZONE_ID), this.retrieveFileFromSecureFileTransferProtocolInputBoundary); } @Override public void destroy() throws Exception { // 手动关闭FTP连接资源 if (channelSftp != null && channelSftp.isConnected()) { channelSftp.disconnect(); } if (session != null && session.isConnected()) { session.disconnect(); } } }
- 创建单例调度触发类:
@Service public class ScheduledTaskTrigger { private final ApplicationContext applicationContext; public ScheduledTaskTrigger(ApplicationContext applicationContext) { this.applicationContext = applicationContext; } @Scheduled(initialDelay = 5, fixedRate = 1, timeUnit = TimeUnit.SECONDS) public void triggerTask() throws Exception { // 每次调度获取全新的原型Bean实例 InsertTransactionDetailsByScheduleAPIInteractor interactor = applicationContext.getBean(InsertTransactionDetailsByScheduleAPIInteractor.class); try { interactor.insertTransactionsBySchedule(); } finally { // 执行完成后销毁实例,释放FTP连接 if (interactor instanceof DisposableBean) { ((DisposableBean) interactor).destroy(); } } } }
方案2:使用TaskScheduler手动调度
通过TaskScheduler自定义调度逻辑,每次任务触发时创建原型Bean实例,执行完成后清理资源。
@Configuration public class CustomSchedulerConfig { private final ApplicationContext applicationContext; private final TaskScheduler taskScheduler; public CustomSchedulerConfig(ApplicationContext applicationContext, TaskScheduler taskScheduler) { this.applicationContext = applicationContext; this.taskScheduler = taskScheduler; } @PostConstruct public void initScheduledTask() { // 按指定周期调度任务 taskScheduler.scheduleAtFixedRate(() -> { InsertTransactionDetailsByScheduleAPIInteractor interactor = applicationContext.getBean(InsertTransactionDetailsByScheduleAPIInteractor.class); try { interactor.insertTransactionsBySchedule(); } catch (Exception e) { // 异常处理逻辑 e.printStackTrace(); } finally { // 销毁实例释放资源 if (interactor instanceof DisposableBean) { try { ((DisposableBean) interactor).destroy(); } catch (Exception ex) { ex.printStackTrace(); } } } }, 5000, 1000); // initialDelay=5秒,fixedRate=1秒 } }
原方案失效原因
Spring的@Scheduled注解处理器在容器初始化阶段,仅会扫描并注册单例Bean的调度方法。原型Bean仅会在调度器初始化时被创建一次,后续调度不会重新生成实例,无法实现“每次调度获取新Bean”的需求。
内容的提问来源于stack exchange,提问作者Philippe Gioseffi
相关产品推荐
相关产品推荐

