springspring-bootquartz-schedulerquartz

Unresolvable circular reference in Quartz job configuration


I'm trying to migrate old Spring project with Quartz to the latest 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);
      }
    }
  }
}

When I run the code I get:

The dependencies of some of the beans in the application context form a cycle:

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]
└──<-──┘

Solution

  • Looks like you upgraded to Spring Boot 2.6 so "Circular references are prohibited by default" that is the reason you get BeanCurrentlyInCreationException you can take a look here circular prohibited by default so the issue here is because first when having annotated @Configuration for QuartzConfiguration a bean is created for. So when you call to get all bean names context.getBeanDefinitionNames() here you should find the QuartConfiguration bean name which is being a referenced by context.getBean(bean) this makes a circular reference for the same QuartConfiguration bean then the BeanCurrentlyInCreationException is thrown, so you have two options to solve this:

    First option

    You might call the Scheduler bean directly. You don't have the need to loop.

    private Scheduler getSchedulerInstanceFromApplicationContext() {
        return context.getBean(Scheduler.class);
    }
    

    Second option

    Allow circular references by setting a property in application.properties or application.yml which the property is:

    spring.main.allow-circular-references=true