Search code examples
springquartz-schedulerautowiredlifecyclequartz

How to autowired in quartz?


Previously I had set it up to autowired in a quartz job.
Note here.

But, the autowired of the job inner class will fail.

My job code example is here.

public class MyJob extends QuartzJobBean {

    @Autowired
    private Hello hello; //<--- this is suceess!

    @Override
    public void executeInternal(JobExecutionContext context) {
         //...

         Do do = new Do();
         do.doSomething();

         //...
    }
}

Do.java

public class Do {
    @Autowired
    priavte Do2 do2; // <---- ***this is null !***

    //...                
}

Why is this happening?
How do I solve it and what concepts should I know more?


Solution

  • Quartz jobs are not ran in the same context as spring so autowired objects become null within the same class. You have to make Quartz Spring aware.

    First add sprint-context-support

    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-context-support</artifactId>
      <version>${spring.version}</version>
    </dependency>
    

    Then create a Application Context Holder that is Application Context Aware

    @Component
    public final class ApplicationContextHolder extends SpringBeanJobFactory implements ApplicationContextAware {
    
      private static ApplicationContext context;
    
      private transient AutowireCapableBeanFactory beanFactory;
    
      @Override
      public void setApplicationContext(ApplicationContext ctx) throws BeansException {
        beanFactory = ctx.getAutowireCapableBeanFactory();
        context = ctx;
      }
    
      @Override
      protected Object createJobInstance(final TriggerFiredBundle bundle) throws Exception {
        final Object job = super.createJobInstance(bundle);
        beanFactory.autowireBean(job);
        return job;
      }
    
      public static ApplicationContext getContext() {
        return context;
      }
    }
    

    Then you can create your Quartz Scheduler Configuration Class

    @Configuration
    public class QuartzSchedulerConfiguration {
    
      @Autowired
      private ApplicationContext applicationContext;
    
      /**
       * Create the job factory bean
       * @return Job factory bean
       */
      @Bean
      public JobFactory jobFactory() {
        ApplicationContextHolder jobFactory = new ApplicationContextHolder();
        jobFactory.setApplicationContext(applicationContext);
        return jobFactory;
      }
    
      /**
       * Create the Scheduler Factory bean
       * @return scheduler factory object
       */
      @Bean
      public SchedulerFactoryBean schedulerFactory() {
        SchedulerFactoryBean factory = new SchedulerFactoryBean();
        factory.setAutoStartup(true);
        factory.setSchedulerName("My Scheduler");
        factory.setOverwriteExistingJobs(true);
        factory.setJobFactory(jobFactory());
        return factory;
      }
    }
    

    Now this will place your quartz scheduler in the same context as Spring, so you can now create a SchedulerService class.

    @Service
    public class SchedulerService {
    
      @Autowired
      private SchedulerFactoryBean schedulerFactory;
    
      private Scheduler scheduler;
    
      /**
       * Initialize the scheduler service
       */
      @PostConstruct
      private void init() {
        scheduler = schedulerFactory.getScheduler();
      }
    }
    

    now you can populate this class with methods to create your schedules using the scheduler object and when the task is triggered the class that extends Job will be context aware with spring and the autowired objects will no longer be null

    to address the follow up question implement the ApplicationContextHolder component then autowire it into your SchedulerConfig class

    @Autowire
    ApplicationContextHolder holder
    
    @Bean
    // injecting SpringLiquibase to ensure liquibase is already initialized and created the quartz tables: 
    public JobFactory jobFactory(SpringLiquibase springLiquibase) {
      AutowiringSpringBeanJobFactory jobFactory = new AutowiringSpringBeanJobFactory();
      jobFactory.setApplicationContext(holder);
      return jobFactory;
    }