Autowiring in Quartz multiple Jobs with Spring Boot not working

12,795

Solution 1

This is a modified version of the http://www.baeldung.com/spring-quartz-schedule that you referenced for handling multiple Quartz Jobs in one configuration file. For brevity I'm not including the entire QrtzSheduler class just the replacement for the scheduler method and the use of @Qualifier reference in the Triggers:

...
@Bean
public Scheduler scheduler(Map<String, JobDetail> jobMap, Set<? extends Trigger> triggers) throws SchedulerException, IOException {

    StdSchedulerFactory factory = new StdSchedulerFactory();
    factory.initialize(new ClassPathResource("quartz.properties").getInputStream());

    logger.debug("Getting a handle to the Scheduler");
    Scheduler scheduler = factory.getScheduler();
    scheduler.setJobFactory(springBeanJobFactory());
    Map<JobDetail,Set<? extends Trigger>> triggersAndJobs = new HashMap<JobDetail,Set<? extends Trigger>>;
    for(JobDetail jobDetail : jobMap.getValues()){
        for(Trigger trigger : triggers){
            if(trigger.getJobKey().equals(jobDetail.getKey())){
               Set<Trigger> set = new HashSet<>();
               set.add(trigger);
               triggerAndJobs.put(jobDetail,set);
            }
        }
    }
    scheduler.scheduleJobs(triggersAndJobs, false);

    logger.debug("Starting Scheduler threads");
    scheduler.start();
    return scheduler;
}

@Bean(name="jobOne")
public JobDetail jobDetailOne() {

    ...
}

@Bean(name="jobTwo")
public JobDetail jobDetailTwo() {

   ...
}

@Bean
public Trigger triggerOne(@Qualifier("jobOne")JobDetail jobDetail) {

   ...
}

@Bean
public Trigger triggerTwo(@Qualifier("jobTwo")JobDetail jobDetail) {

   ...
}

Solution 2

You are trying to use spring boot configuration with Spring 4.2.

Try changing the following methods inside Job class as follows

@Bean(name = "jobBean1")
public JobDetail job() {
    return SchedulerConfig.createJobDetail(this.getClass()).getObject();
}

@Bean(name = "jobBean1Trigger")
public CronTrigger jobTrigger(@Qualifier("jobBean1")JobDetail jobDetail) {
    return SchedulerConfig.createCronTrigger(jobDetail, frequency).getObject();
}

Also use spring 4.3 since you need

@Autowired
List<Trigger> triggers;

I believe Collection Autowire works only in 4.3

Solution 3

I faced the same issue and after a bit of struggling I was able to resolve it. It is probably realted to the annotation that is used in the job class. I see that you are using the @Component, as it was in my case. The quartz scheduler configuration instead, is annotated with @Configuration tag.

The solution is to annotate your jobs with @Configuration tag. My guess is that the @Bean's constructed in @Componentannotated classes are not completley constructed/mapped in the phase where @Configuration annotated ones are initialized.

Share:
12,795
Admin
Author by

Admin

Updated on June 13, 2022

Comments

  • Admin
    Admin almost 2 years

    I'm try to use quartz scheduler in spring. I get below exception when configuring multiple jobs

    Parameter 0 of method jobTrigger in Job2 required a bean of type 'org.quartz.JobDetail' that could not be found.

    quartz - v2.3, Spring - v4.2.x

    Config Class

    @Configuration
     public class SchedulerConfig {
    
    private static final Logger LOG = LoggerFactory.getLogger(SchedulerConfig.class);
    
    @Autowired
    List<Trigger> triggers;
    
    @Bean
    public JobFactory jobFactory(ApplicationContext applicationContext) {
        AutowiringSpringBeanJobFactory jobFactory = new AutowiringSpringBeanJobFactory();
        jobFactory.setApplicationContext(applicationContext);
        return jobFactory;
    }
    
    @Bean
    public SchedulerFactoryBean schedulerFactoryBean(JobFactory jobFactory)  throws IOException {
        SchedulerFactoryBean factory = new SchedulerFactoryBean();
              factory.setAutoStartup(true);
        factory.setJobFactory(jobFactory);
             factory.setQuartzProperties(quartzProperties());
        if (triggers != null && !triggers.isEmpty()) {
             LOG.info("starting jobs... Total Triggers - " + triggers.size());
            factory.setTriggers(triggers.toArray(new Trigger[triggers.size()]));
        }
    
        return factory;
    }
    
    @Bean
    public Properties quartzProperties() throws IOException {
        PropertiesFactoryBean propertiesFactoryBean = new PropertiesFactoryBean();
        propertiesFactoryBean.setLocation(new ClassPathResource("/quartz.properties"));
        propertiesFactoryBean.afterPropertiesSet();
        return propertiesFactoryBean.getObject();
    }
    
    
    public static CronTriggerFactoryBean createCronTrigger(JobDetail jobDetail, String cronExpression) {
        CronTriggerFactoryBean factoryBean = new CronTriggerFactoryBean();
        factoryBean.setJobDetail(jobDetail);
        factoryBean.setCronExpression(cronExpression);
        factoryBean.setMisfireInstruction(SimpleTrigger.MISFIRE_INSTRUCTION_FIRE_NOW);
        return factoryBean;
    }
    
    public static JobDetailFactoryBean createJobDetail(Class jobClass) {
        JobDetailFactoryBean factoryBean = new JobDetailFactoryBean();
        factoryBean.setJobClass(jobClass);
        factoryBean.setDurability(true);
        return factoryBean;
    }
    

    SpringBeanJobFactory

    public final class AutowiringSpringBeanJobFactory extends SpringBeanJobFactory implements ApplicationContextAware {
    
    private static final Logger LOG = LoggerFactory.getLogger(AutowiringSpringBeanJobFactory.class);
    
    private transient AutowireCapableBeanFactory beanFactory;
    
    @Override
    public void setApplicationContext(final ApplicationContext context) {
        beanFactory = context.getAutowireCapableBeanFactory();
    }
    
    @Override
    protected Object createJobInstance(final TriggerFiredBundle bundle) throws Exception {
        final Object job = super.createJobInstance(bundle);
        LOG.info("create job instance");
        beanFactory.autowireBean(job);
        return job;
    }
    

    }

    Job 1

    @Component
    @DisallowConcurrentExecution
    public class Job1 implements Job {
    
    private final Logger log = LoggerFactory.getLogger(this.getClass());
    
    @Value("${schedule}")
    private String frequency;
    
    @Autowired
    private Service service;
    
    @Override
    public void execute(JobExecutionContext jobExecutionContext) {
        log.info("execute");
    }
    
    @Bean(name = "jobBean1")
    public JobDetailFactoryBean job() {
        return SchedulerConfig.createJobDetail(this.getClass());
    }
    
    @Bean(name = "jobBean1Trigger")
    public CronTriggerFactoryBean jobTrigger(@Qualifier("jobBean1")JobDetail jobDetail) {
        return SchedulerConfig.createCronTrigger(jobDetail, frequency);
    }
    

    Job 2

    @Component
    @DisallowConcurrentExecution
    public class Job2 implements Job {
    
    private final Logger log = LoggerFactory.getLogger(this.getClass());
    
    @Value("${schedule}")
    private String frequency;
    
    @Autowired
    private Service service;
    
    @Override
    public void execute(JobExecutionContext jobExecutionContext) {
        log.info("execute");
    }
    
    @Bean(name = "jobBean2")
    public JobDetailFactoryBean job() {
        return SchedulerConfig.createJobDetail(this.getClass());
    }
    
    @Bean(name = "jobBean2Trigger")
    public CronTriggerFactoryBean jobTrigger(@Qualifier("jobBean2")JobDetail jobDetail) {
        return SchedulerConfig.createCronTrigger(jobDetail, frequency);
    }
    

    The Service class has Spring JPA repos. The root cause of the problem is the below autowired service. If I remove the below autowired service from both the jobs, it works fine.

    @Autowired private Service service;

    If there is only one job with this autowired bean, then there is no exception. How to configure multiple jobs using the same autowired dependency? What is cause this issue?