Initilizing a constructor and inject a Repository bean of Jpa in Quartz [duplicate] - spring

I have implemented Spring Quartz scheduler example using this link
I am having simple MyJobTwo.java component that has a method executeInternal() that is being called using CronTriggerFactoryBean.
This is my QuartzConfiguration.java
#Configuration
#ComponentScan("com.example")
public class QuartzConfiguration {
// we need to create a bean that will excuted by MethodInvokingJobDetailFactoryBean
// in this case we have myJobOne is the simple bean
#Bean
public MethodInvokingJobDetailFactoryBean methodInvokingJobDetailFactoryBean() {
MethodInvokingJobDetailFactoryBean obj = new MethodInvokingJobDetailFactoryBean();
obj.setTargetBeanName("myJobOne");
obj.setTargetMethod("myTask");
return obj;
}
// This trigger will schedule the job after 3 seconds and repeat after every 30 seconds for 3+1 times.
#Bean
public SimpleTriggerFactoryBean simpleTriggerFactoryBean(){
SimpleTriggerFactoryBean stFactory = new SimpleTriggerFactoryBean();
stFactory.setJobDetail(methodInvokingJobDetailFactoryBean().getObject());
stFactory.setStartDelay(3000);
stFactory.setRepeatInterval(30000);
stFactory.setRepeatCount(1);
return stFactory;
}
// We use it to configure complex job such as job scheduling using cron-expression
#Bean
public JobDetailFactoryBean jobDetailFactoryBean(){
JobDetailFactoryBean factory = new JobDetailFactoryBean();
factory.setJobClass(MyJobTwo.class);
// Map<String,Object> map = new HashMap<String,Object>();
// map.put("myJobOne", myJobOne);
// map.put(MyJobTwo.myJodOne, 1);
//factory.setJobDataAsMap(map);
//factory.setGroup("mygroup");
//factory.setName("myjob");
return factory;
}
// CronTriggerFactoryBean configures JobDetailFactoryBean
// We also configure start delay, trigger name, and cron-expression to schedule the job
#Bean
public CronTriggerFactoryBean cronTriggerFactoryBean(){
CronTriggerFactoryBean stFactory = new CronTriggerFactoryBean();
stFactory.setJobDetail(jobDetailFactoryBean().getObject());
stFactory.setStartDelay(3000);
//stFactory.setName("mytrigger");
//stFactory.setGroup("mygroup");
stFactory.setCronExpression("0 0/1 * 1/1 * ? *");
return stFactory;
}
// SchedulerFactoryBean use to register the triggers
// those registered triggers will be executed
#Bean
public SchedulerFactoryBean schedulerFactoryBean() {
SchedulerFactoryBean scheduler = new SchedulerFactoryBean();
scheduler.setTriggers(cronTriggerFactoryBean().getObject());
//scheduler.setTriggers(simpleTriggerFactoryBean().getObject());
return scheduler;
}
}
This is the bean that I am executing using CronTriggerFactoryBean.
MyJobTwo.java
#Component
public class MyJobTwo extends QuartzJobBean {
private SmtpMailSender smtpMailSender;
#Autowired
public MyJobTwo(MyJobOne myJobOne, SmtpMailSender smtpMailSender) {
super();
this.myJobOne = myJobOne;
this.smtpMailSender = smtpMailSender;
}
#Override
protected void executeInternal(JobExecutionContext ctx)
throws JobExecutionException {
System.out.println("this is the test");
myJobOne.myTask();
System.out.println("task is done");
}
}
Whenever I am trying to inject other beans and service I am getting these errors. Anyone having any idea what is causing these errors, what changes do I need to make?
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.example.job.MyJobTwo
at java.lang.Class.newInstance(Class.java:427)
at org.springframework.scheduling.quartz.AdaptableJobFactory.createJobInstance(AdaptableJobFactory.java:58)
at org.springframework.scheduling.quartz.AdaptableJobFactory.newJob(AdaptableJobFactory.java:41)
... 2 common frames omitted
Caused by: java.lang.NoSuchMethodException: com.example.job.MyJobTwo.<init>()
at java.lang.Class.getConstructor0(Class.java:3082)
at java.lang.Class.newInstance(Class.java:412)
... 4 common frames omitted

The default job factory implementation AdaptableJobFactory doesn't have autowiring capability.
To use dependency injection do following:
1.Create job factory
package com.concretepage.config;
import org.quartz.spi.TriggerFiredBundle;
import org.springframework.beans.factory.config.AutowireCapableBeanFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.scheduling.quartz.SpringBeanJobFactory;
public class AutowiringSpringBeanJobFactory extends SpringBeanJobFactory implements ApplicationContextAware {
private transient AutowireCapableBeanFactory beanFactory;
public void setApplicationContext(final ApplicationContext context) {
beanFactory = context.getAutowireCapableBeanFactory();
}
#Override
public Object createJobInstance(final TriggerFiredBundle bundle) throws Exception {
final Object job = super.createJobInstance(bundle);
beanFactory.autowireBean(job); //the magic is done here
return job;
}
}
Implementation is found on http://codrspace.com/Khovansa/spring-quartz-with-a-database/
2.Update schedulerFactoryBean declaration in QuartzConfiguration:
#Bean
public SchedulerFactoryBean schedulerFactoryBean() {
SchedulerFactoryBean scheduler = new SchedulerFactoryBean();
scheduler.setTriggers(simpleTriggerFactoryBean().getObject(), cronTriggerFactoryBean().getObject());
scheduler.setJobFactory(jobFactory());
return scheduler;
}
#Bean
public JobFactory jobFactory() {
return new AutowiringSpringBeanJobFactory();
}
Use setter-based injection instead of constructor injection

Related

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?
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;
}

Spring with Quartz, using SchedulerFactoryBean in custom service

I have job with a bean injected to it. I've acheaved using this solution.
In this solution the job trigger is setted during configuration of SchedulerFactoryBean bean in the config class.
But I want to schedule it in my custom SchedulerService service by colling scheduleTrackRetry method.
I try something like this(see below) but the job is not fired up at the appropriate time.
#Service
public class SchedulerService {
#Autowired
SchedulerFactoryBean quartzScheduler;
#Autowired
JobDetailFactoryBean jobDetailFactoryBean;
#Autowired
CronTriggerFactoryBean cronTriggerFactoryBean;
public void scheduleTrackRetry() {
cronTriggerFactoryBean.setJobDetail(jobDetailFactoryBean.getObject());
quartzScheduler.setTriggers(cronTriggerFactoryBean.getObject());
}
So, please tell me how could I acheave the desired behaviour?
Here is my job and conf classes:
#Component
public class TrackRetryJob implements Job {
private static final Logger LOGGER = LoggerFactory.getLogger(TrackRetryJob.class);
#Autowired
private TimeformBatchService timeformBatchService;
#Override
public void execute(JobExecutionContext context) throws JobExecutionException {
LOGGER.info("Run retry for fetching greyhound tracks");
timeformBatchService.consumeTracks();
}
}
#Configuration
public class QuartzConfig {
#Autowired
private ApplicationContext applicationContext;
#Bean
public SchedulerFactoryBean quartzScheduler() {
SchedulerFactoryBean quartzScheduler = new SchedulerFactoryBean();
// custom job factory of spring with DI support for #Autowired!
AutowiringSpringBeanJobFactory jobFactory = new AutowiringSpringBeanJobFactory();
jobFactory.setApplicationContext(applicationContext);
quartzScheduler.setJobFactory(jobFactory);
quartzScheduler.setTriggers(getTrigger().getObject());
return quartzScheduler;
}
#Bean
public JobDetailFactoryBean retryTrackFetch() {
JobDetailFactoryBean jobDetailFactory = new JobDetailFactoryBean();
jobDetailFactory.setJobClass(TrackRetryJob.class);
jobDetailFactory.setGroup("group1");
return jobDetailFactory;
}
#Bean
public CronTriggerFactoryBean getTrigger() {
CronTriggerFactoryBean cronTriggerFactoryBean = new CronTriggerFactoryBean();
cronTriggerFactoryBean.setJobDetail(retryTrackFetch().getObject());
cronTriggerFactoryBean.setCronExpression("0 53 * * * ?");
cronTriggerFactoryBean.setGroup("group1");
return cronTriggerFactoryBean;
}
}
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;
}
}
I found the solution here
I could not use SchedulerFactoryBean as a normal bean. When I try to inject it, spring inject Scheduler bean. Therefore my service should look like this:
#Service
public class SchedulerService {
#Autowired
Scheduler scheduler;
}

Spring boot quartz 2.2.1 compilation error

This is the code i have used for saving new cron job to the postgresql database.But it is showing some compile time exceptions.I have originally migrated quartz 1.8.6 to 2.2.1 with spring boot web app.see the attachment.
private static final String JOBNAME = "sampleJobRunner";
private static final String JOB_GROUPNAME = "sampleGroup";
public void saveScheduledTime(String name) throws SchedulerException, ParseException {
Scheduler scheduler;
CronTrigger trigger;
String cronExp = null;
scheduler = this.quartzScheduler.getObject();
trigger = (CronTrigger) scheduler.getTrigger(name, JOB_GROUPNAME);
if (scheduler != null) {
cronExp = "0 0 12 ? 1 MON#1 *";
if (trigger != null) {
rescheduleJob(scheduler, trigger.getName(), cronExp);
} else {
createNewCronTrigger(scheduler, cronExp, name);
}
}
}
private void rescheduleJob(Scheduler scheduler, String triggerName, String cronExp)
throws SchedulerException, ParseException {
CronTrigger trigger = (CronTrigger) scheduler.getTrigger(triggerName, JOB_GROUPNAME);
trigger.setCronExpression(cronExp);
scheduler.rescheduleJob(triggerName, JOB_GROUPNAME, trigger);
}
private void createNewCronTrigger(Scheduler scheduler, String cronExp, String sentType)
throws SchedulerException, ParseException {
CronTrigger trigger = new CronTrigger(sentType, JOB_GROUPNAME, JOBNAME, JOB_GROUPNAME, cronExp);
scheduler.scheduleJob(trigger);
scheduler.start();
}
Spring boot quartz configuration
#Configuration
#ConditionalOnProperty(name = "quartz.enabled")
public class SchedulerConfig {
#Bean
public JobFactory jobFactory(ApplicationContext applicationContext) {
AutowiringSpringBeanJobFactory jobFactory = new AutowiringSpringBeanJobFactory();
jobFactory.setApplicationContext(applicationContext);
return jobFactory;
}
#Bean
public SchedulerFactoryBean schedulerFactoryBean(DataSource dataSource, JobFactory jobFactory,
#Qualifier("sampleJobTrigger") Trigger sampleJobTrigger) throws IOException {
SchedulerFactoryBean factory = new SchedulerFactoryBean();
// this allows to update triggers in DB when updating settings in config
// file:
factory.setOverwriteExistingJobs(true);
factory.setDataSource(dataSource);
factory.setJobFactory(jobFactory);
factory.setQuartzProperties(quartzProperties());
// factory.setTriggers(sampleJobTrigger);
return factory;
}
#Bean
public Properties quartzProperties() throws IOException {
PropertiesFactoryBean propertiesFactoryBean = new PropertiesFactoryBean();
propertiesFactoryBean.setLocation(new ClassPathResource("/quartz.properties"));
propertiesFactoryBean.afterPropertiesSet();
return propertiesFactoryBean.getObject();
}
#Bean
public JobDetailFactoryBean sampleJobDetail() {
return createJobDetail(SampleJob.class);
}
#Bean(name = "sampleJobTrigger")
public SimpleTriggerFactoryBean sampleJobTrigger(#Qualifier("sampleJobDetail") JobDetail jobDetail,
#Value("${samplejob.frequency}") long frequency) {
return createTrigger(jobDetail, frequency);
}
private static JobDetailFactoryBean createJobDetail(Class jobClass) {
JobDetailFactoryBean factoryBean = new JobDetailFactoryBean();
factoryBean.setJobClass(jobClass);
// job has to be durable to be stored in DB:
factoryBean.setDurability(true);
return factoryBean;
}
private static SimpleTriggerFactoryBean createTrigger(JobDetail jobDetail, long pollFrequencyMs) {
SimpleTriggerFactoryBean factoryBean = new SimpleTriggerFactoryBean();
factoryBean.setJobDetail(jobDetail);
factoryBean.setStartDelay(0L);
factoryBean.setRepeatInterval(pollFrequencyMs);
factoryBean.setRepeatCount(SimpleTrigger.REPEAT_INDEFINITELY);
// in case of misfire, ignore all missed triggers and continue :
factoryBean.setMisfireInstruction(SimpleTrigger.MISFIRE_INSTRUCTION_RESCHEDULE_NEXT_WITH_REMAINING_COUNT);
return factoryBean;
}
}
To fix the complilation errors in your code:
use scheduler.getTrigger(new TriggerKey(name, JOB_GROUPNAME));
use scheduler.rescheduleJob(new TriggerKey(name, JOB_GROUPNAME), trigger);
cast CronTrigger to CronTriggerImpl which has the setCronExpression() method
See my sample project for Spring Boot + Quartz on github: https://github.com/davidkiss/spring-boot-quartz-demo. I updated the SchedulerConfig class with a new createCronTrigger helper method that allows to create cron triggers in Spring Boot:
private 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;
}
If you use Spring Boot in your project, I believe using the SchedulerConfig class is a cleaner way of configuring quartz jobs in Spring Boot then calling the saveScheduledTime() method.
In order to create any cron triggers, I'd add additional methods in the SchedulerConfig class to create the appropriate trigger beans similar to sampleJobTrigger.
This is purely a result of the migration from Quartz 1.8.6 to 2.2.1. Quartz did some code changes with 2.x version. You need to make changes in your application to respect that. Have a look at this: http://www.quartz-scheduler.org/documentation/quartz-2.x/migration-guide.html

Quartz + Spring Batch in Spring boot

I'm trying to develop an spring application which have integration of Quartz and Spring Batch. For some reasons, i'm not able to run it properly and getting some compilation errors.
Code:
QuartzConfiguration
#Configuration
#ComponentScan("com.concretepage")
public class QuartzConfiguration {
#Bean
public MethodInvokingJobDetailFactoryBean methodInvokingJobDetailFactoryBean() {
MethodInvokingJobDetailFactoryBean obj = new MethodInvokingJobDetailFactoryBean();
obj.setTargetBeanName("jobone");
obj.setTargetMethod("myTask");
return obj;
}
#Bean
public CronTriggerFactoryBean cronTriggerFactoryBean(){
CronTriggerFactoryBean stFactory = new CronTriggerFactoryBean();
SpringBatchJobs batch = new SpringBatchJobs();
stFactory.setJobDetail(batch.job()); // here i'm getting some compilcation error like "The method job() from the type SpringBatchJobs refers to the missing type Job"
stFactory.setStartDelay(3000);
stFactory.setName("mytrigger");
stFactory.setGroup("mygroup");
stFactory.setCronExpression("0/1 * * * * ?");
return stFactory;
}
#Bean
public SchedulerFactoryBean schedulerFactoryBean() {
SchedulerFactoryBean scheduler = new SchedulerFactoryBean();
scheduler.setTriggers(cronTriggerFactoryBean().getObject());
return scheduler;
}
}
Spring Batch:
#EnableBatchProcessing
public class SpringBatchJobs {
#Autowired
private JobBuilderFactory jobs;
#Autowired
private StepBuilderFactory steps;
#Bean
protected Tasklet tasklet() {
return new Tasklet() {
#Override
public RepeatStatus execute(StepContribution contribution,
ChunkContext context) {
return RepeatStatus.FINISHED;
}
};
}
#Bean
public Job job() throws Exception {
return this.jobs.get("job").start(step1()).build();
}
#Bean
protected Step step1() throws Exception {
return this.steps.get("step1").tasklet(tasklet()).build();
}
}
The problem come on the following line
stFactory.setJobDetail(batch.job()); // here i'm getting some compilcation error like "The method job() from the type SpringBatchJobs refers to the missing type Job"
How can i run Spring Batch jobs using Quartz2 in spring boot? Any idea
Spring batch Job need to be launched by using spring batch job launcher.
Please refer below link for exact details on how to do it.
https://examples.javacodegeeks.com/enterprise-java/spring/batch/quartz-spring-batch-example/

spring batch with annotations

I'm currently trying to create a batch with the spring annotations but the batch is never called. No error occurs, my batch isn't called. Its a simple batch that retrieves values from the database and add messages in a queue (rabbitmq).
The main configuration class:
#Configuration
#EnableBatchProcessing
public class BatchInfrastructureConfiguration {
#Bean
public JobLauncher getJobLauncher() throws Exception {
SimpleJobLauncher jobLauncher = new SimpleJobLauncher();
jobLauncher.setJobRepository(getJobRepository());
jobLauncher.afterPropertiesSet();
return jobLauncher;
}
public JobRepository getJobRepository() throws Exception {
MapJobRepositoryFactoryBean factory = new MapJobRepositoryFactoryBean();
factory.setTransactionManager(new ResourcelessTransactionManager());
factory.afterPropertiesSet();
return (JobRepository) factory.getObject();
}
}
The configuration class specific to my batch
#Configuration
#Import(BatchInfrastructureConfiguration.class)
public class PurchaseStatusBatchConfiguration {
#Inject
private JobBuilderFactory jobBuilders;
#Inject
private StepBuilderFactory stepBuilders;
#Bean
public Job purchaseStatusJob(){
return jobBuilders.get("purchaseStatusJob")
.start(step())
.build();
}
#Bean
public Step step(){
return stepBuilders.get("purchaseStatusStep")
.tasklet(new PurchaseStatusBatch())
.build();
}
}
The batch class:
public class PurchaseStatusBatch implements Tasklet {
#Inject
private PurchaseRepository purchaseRepository;
#Inject
#Qualifier(ApplicationConst.BEAN_QUALIFIER_PURCHASE_QUEUE)
private RabbitTemplate rabbitTemplate;
#Override
public RepeatStatus execute(StepContribution contribution, ChunkContext chunkContext) throws Exception {
PurchaseDto purchaseDto;
PurchaseMessage purchaseMessage;
List<Purchase> notVerifiedPurchase = purchaseRepository.findByVerified(false);
for (Purchase purchase : notVerifiedPurchase) {
purchaseDto = new PurchaseDto();
purchaseDto.setOrderId(purchase.getOrderId());
purchaseDto.setProductId(purchase.getProductId());
purchaseDto.setPurchaseToken(purchase.getPurchaseToken());
purchaseDto.setUserScrapbookKey(purchase.getUserScrapbookKey());
purchaseMessage = new PurchaseMessage();
purchaseMessage.setPurchaseDto(purchaseDto);
rabbitTemplate.convertAndSend(purchaseMessage);
}
return null;
}
}
The job runner (class calling the batch):
#Service
public class PurchaseStatusJobRunner {
#Inject
private JobLocator jobLocator;
#Inject
private JobLauncher jobLauncher;
//#Scheduled(fixedDelay = 3000L)
//#Scheduled(cron="* * * * *") // every 1 minute
#Scheduled(fixedDelay = 3000L)
public void runJob() throws JobExecutionAlreadyRunningException, JobRestartException, JobInstanceAlreadyCompleteException, JobParametersInvalidException, NoSuchJobException {
jobLauncher.run(jobLocator.getJob("purchaseStatusJob"), new JobParameters());
}
}
Short answer: The problem is that your PurchaseStatusBatch instance is not a spring bean... then all its attributes are null (they have not been injected)
Try this :
#Bean
public Step step(){
return stepBuilders.get("purchaseStatusStep")
.tasklet(purchaseStatusBatch())
.build();
}
#Bean
public PurchaseStatusBatch purchaseStatusBatch() {
return new PurchaseStatusBatch()
}
When you want to have feedback on your job execution, just use the JobExecution instance returned by the JobLauncher. You can get the ExitStatus, you can get all exceptions catched by the JobLauncher, and more information.
Another solution to have feedback would be providing a real database to your JobRepository, and then check execution statuses in it.

Resources