Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Spring Quartz Setting up Constructor based Injection with Parent Class Constructor

I have a spring boot project that I want to use quartz with to run certain jobs at certain times. I have this class layout:

abstract public class AbstractFoo {
    protected final FooB fooB;

    public AbstractFoo(FooB fooB) {
        this.fooB = fooB;
    }
}

@Service
public class SomeJob extends AbstractFoo implements Job {
    public SomeJob(FooB fooB) {
        super(fooB);
    }

    @Override
    public void execute(final JobExecutionContext context) throws JobExecutionException {
        //do stuff
    }
}

However, when I run this job I get the following error:

2017-12-06 14:18:01,383  ERROR --- [quartz-jobs] org.quartz.core.ErrorLogger                                                                : An error occured instantiating job to be executed. job= 'jobGroup.someJob' 
org.quartz.SchedulerException: Job instantiation failed
    at org.springframework.scheduling.quartz.AdaptableJobFactory.newJob(AdaptableJobFactory.java:45)
    at org.quartz.core.JobRunShell.initialize(JobRunShell.java:127)
    at org.quartz.core.QuartzSchedulerThread.run(QuartzSchedulerThread.java:375)
Caused by: java.lang.InstantiationException: com.jobs.SomeJob
    at java.lang.Class.newInstance(Class.java:427)
    at org.springframework.scheduling.quartz.AdaptableJobFactory.createJobInstance(AdaptableJobFactory.java:58)
    at org.springframework.scheduling.quartz.SpringBeanJobFactory.createJobInstance(SpringBeanJobFactory.java:74)
    at com.config.AutowiringSpringBeanJobFactory.createJobInstance(AutowiringSpringBeanJobFactory.java:27)
    at org.springframework.scheduling.quartz.AdaptableJobFactory.newJob(AdaptableJobFactory.java:41)
    ... 2 common frames omitted
Caused by: java.lang.NoSuchMethodException: com.jobs.SomeJob.<init>()
    at java.lang.Class.getConstructor0(Class.java:3082)
    at java.lang.Class.newInstance(Class.java:412)
    ... 6 common frames omitted

I already have an autowire factory like so:

public final class AutowiringSpringBeanJobFactory extends SpringBeanJobFactory implements
        ApplicationContextAware {

    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);
        beanFactory.autowireBean(job);
        return job;
    }
}

And here's my quartz config:

@Configuration
public class QuartzConfig {
    @Autowired
    private DataSource dataSource;
    @Autowired
    private PlatformTransactionManager transactionManager;
    @Autowired
    private ApplicationContext applicationContext;

    @Bean
    public SchedulerFactoryBean quartzScheduler() {
        SchedulerFactoryBean quartzScheduler = new SchedulerFactoryBean();

        quartzScheduler.setDataSource(dataSource);
        quartzScheduler.setTransactionManager(transactionManager);
        quartzScheduler.setOverwriteExistingJobs(true);
        quartzScheduler.setSchedulerName("quartz-jobs");

        AutowiringSpringBeanJobFactory jobFactory = new AutowiringSpringBeanJobFactory();
        jobFactory.setApplicationContext(applicationContext);
        quartzScheduler.setJobFactory(jobFactory);

        quartzScheduler.setQuartzProperties(quartzProperties());

        Trigger[] triggers = {someJobTrigger().getObject();
        quartzScheduler.setTriggers(triggers);

        return quartzScheduler;
    }

    @Bean
    public CronTriggerFactoryBean someJobTrigger() {
        CronTriggerFactoryBean cronTriggerFactoryBean = new CronTriggerFactoryBean();
        cronTriggerFactoryBean.setJobDetail(someJob().getObject());
        cronTriggerFactoryBean.setCronExpression(cronExp);
        cronTriggerFactoryBean.setGroup(someGroup);
        return cronTriggerFactoryBean;
    }

    @Bean
    public JobDetailFactoryBean someJob() {
        JobDetailFactoryBean jobDetailFactory = new JobDetailFactoryBean();
        jobDetailFactory.setJobClass(SomeJob.class);
        jobDetailFactory.setGroup(someGroup);
        jobDetailFactory.setDurability(true);
        return jobDetailFactory;
    }

    @Bean
    public Properties quartzProperties() {
        PropertiesFactoryBean propertiesFactoryBean = new PropertiesFactoryBean();
        propertiesFactoryBean.setLocation(new ClassPathResource("/quartz.properties"));
        Properties properties = null;
        try {
            propertiesFactoryBean.afterPropertiesSet();
            properties = propertiesFactoryBean.getObject();
        } catch (IOException e) {
        }

        return properties;
    }
}

How do I get Quartz to wire in the appropriate dependencies through the constructor?

like image 361
Richard Avatar asked Jan 29 '23 02:01

Richard


1 Answers

As stated in docs:

One of the ramifications of this behavior is the fact that jobs must have a no-argument constructor (when using the default JobFactory implementation).

You essentially using default JobFactory with autowiring capability added. What I found from my personal tests is that autowiring will not work with constructor injection. Also, don't mark your job with Spring annotations (Component, Service, e.c.t) as it has no effect.

To solve your problem, refactor your job to have default constructor and autowire dependencies with field injection (maybe setter injection will work too).

abstract public class AbstractFoo {
    @Autowired
    protected FooB fooB;
}

public class SomeJob extends AbstractFoo implements Job {

    @Override
    public void execute(final JobExecutionContext context) throws JobExecutionException {
        //do stuff
    }
}
like image 67
dev4Fun Avatar answered Jan 31 '23 20:01

dev4Fun