You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

迁移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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.30 04:24:13