迁移Spring+Quartz项目至Spring Boot时出现无法解析循环引用错误
问题描述
将集成Quartz的旧版Spring项目迁移至最新版Spring Boot时,使用如下配置代码:
import org.springframework.beans.factory.InitializingBean; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @Configuration public class QuartzConfiguration implements InitializingBean { public static final Logger LOGGER = LoggerFactory.getLogger(QuartzConfiguration.class); public static final String JOB_KEY = "FinalizationJob"; public static final String KEY = JOB_KEY + "_Trigger"; private ApplicationContext context; @Autowired public QuartzConfiguration(ApplicationContext context) { this.context = context; } @Bean(name = "finalJobDetail") public JobDetail batchExpireUpdateJobDetail() { return JobBuilder.newJob(BatchExpireUpdateJob.class) .withIdentity(JobKey.jobKey(JOB_KEY)) .storeDurably() .build(); } @Override public void afterPropertiesSet() { Trigger newTrigger = newTrigger() .forJob(JOB_KEY) .withIdentity(triggerKey(KEY)) .withSchedule(repeatSecondlyForever(100)) .build(); try { Scheduler obj = getSchedulerInstanceFromApplicationContext(); if (obj.checkExists(triggerKey(KEY))) { obj.rescheduleJob(triggerKey(KEY), newTrigger); } else { obj.scheduleJob(newTrigger); } } catch (SchedulerException | NullPointerException e) { LOGGER.error("error {}", JOB_KEY, e); } } private Scheduler getSchedulerInstanceFromApplicationContext() { Scheduler obj = null; String [] beans = context.getBeanDefinitionNames(); for (String bean: beans) { if (context.getBean(bean) instanceof Scheduler) { obj = (Scheduler) context.getBean(bean); break; } } return obj; } @DisallowConcurrentExecution private static class BatchExpireUpdateJob implements Job { private DatabaseService databaseService; @Autowired public BatchExpireUpdateJob(DatabaseService databaseService) { this.databaseService = databaseService; } @Override public void execute(JobExecutionContext context) throws JobExecutionException { try { LOGGER.info("Updating status"); databaseService.updateStatus(); } catch (Exception e) { LOGGER.error("Error ", e); throw new JobExecutionException(e); } } } }
运行代码时抛出循环依赖报错,报错信息如下:
Invocation of init method failed; nested exception is org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'quartzConfiguration': Requested bean is currently in creation: Is there an unresolvable circular reference? ┌──->──┐ | QuartzConfiguration defined in file C:\......\QuartzConfiguration.class] └──<-──┘
问题原因
报错核心是配置类自引用触发的循环依赖,触发逻辑如下:
QuartzConfiguration标注了@Configuration注解,Spring创建这个配置类Bean时,会在Bean本身初始化完成前,先执行类内@Bean方法注册组件,同时触发InitializingBean接口的afterPropertiesSet()初始化回调。- 你在
afterPropertiesSet()方法中写了遍历容器所有Bean定义查找Scheduler的逻辑,此时QuartzConfiguration本身还处于创建中状态,遍历过程中尝试获取这个未完成初始化的Bean,直接抛出BeanCurrentlyInCreationException异常。 - 除此之外代码还有两个Quartz集成的隐性问题,就算绕开循环依赖后续也会运行失败:
- 把
BatchExpireUpdateJob定义为配置类的私有静态内部类,Quartz默认通过反射创建Job实例时无法访问这个私有类,也无法注入其中的DatabaseService依赖 - 手动遍历ApplicationContext查找Scheduler是完全冗余的逻辑,Spring Boot集成Quartz的自动配置会直接把
Scheduler注册为容器Bean,可以直接注入使用。
- 把
修复方案
不要手动在Bean初始化阶段操作Scheduler、遍历容器查找Bean,直接利用Spring Boot的Quartz自动配置能力,把JobDetail、Trigger都声明为容器Bean,由Spring自动完成调度绑定,修正后的代码如下:
import org.quartz.*; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.scheduling.quartz.QuartzJobBean; import static org.quartz.SimpleScheduleBuilder.repeatSecondlyForever; import static org.quartz.TriggerBuilder.newTrigger; import static org.quartz.TriggerKey.triggerKey; @Configuration public class QuartzConfiguration { private static final Logger LOGGER = LoggerFactory.getLogger(QuartzConfiguration.class); public static final String JOB_KEY = "FinalizationJob"; public static final String TRIGGER_KEY = JOB_KEY + "_Trigger"; @Bean(name = "finalJobDetail") public JobDetail batchExpireUpdateJobDetail() { return JobBuilder.newJob(BatchExpireUpdateJob.class) .withIdentity(JobKey.jobKey(JOB_KEY)) .storeDurably() .build(); } @Bean public Trigger batchExpireUpdateTrigger() { return newTrigger() .forJob(batchExpireUpdateJobDetail()) .withIdentity(triggerKey(TRIGGER_KEY)) .withSchedule(repeatSecondlyForever(100)) .build(); } @DisallowConcurrentExecution public static class BatchExpireUpdateJob extends QuartzJobBean { private final DatabaseService databaseService; @Autowired public BatchExpireUpdateJob(DatabaseService databaseService) { this.databaseService = databaseService; } @Override protected void executeInternal(JobExecutionContext context) throws JobExecutionException { try { LOGGER.info("Updating status"); databaseService.updateStatus(); } catch (Exception e) { LOGGER.error("Job execute error", e); throw new JobExecutionException(e); } } } }
核心调整点:
- 移除
InitializingBean接口实现,去掉手动遍历容器查找Scheduler、手动调用调度API绑定触发器的逻辑,从根源上避免初始化阶段的自引用问题 - 将Trigger声明为
@Bean,Spring Boot会自动识别容器内的JobDetail和Trigger完成调度配置,不需要手动写判断触发器存在、重设触发器的逻辑 - Job类继承Spring提供的
QuartzJobBean,保证Job实例由Spring容器创建,可正常注入Service层依赖,同时将Job类的访问权限改为public,避免Quartz反射实例化失败 - 移除冗余的ApplicationContext注入和遍历查找逻辑,完全适配Spring Boot的自动配置规则,减少自定义维护成本
内容的提问来源于stack exchange,提问作者Peter Penzov
相关产品推荐
相关产品推荐

