3

I managed to configure and schedule a Quartz job using JobStoreTX persistent store in Spring Boot ( version 4.2.5 ). Here is how I schedule the job. First :

public class MyJob implements Job{
    @Autowired
    IService service;

    @Override
      public void execute(JobExecutionContext context) throws JobExecutionException {
        service.doSomething();
      }
}

@Autowired seems like it wont work in a Quartz job implementation because it wont be instantiated by Spring. Hence, im facing the famous JavaNullPointerException.

Second, in order to get hold of Spring-managed beans in a Quartz job, I used org.springframework.scheduling.quartz.SchedulerFactoryBean to manage the Quartz lifecycle :

public class MyJob implements Job{

    @Override
      public void execute(JobExecutionContext context) throws JobExecutionException {
        try {
            ApplicationContext applicationContext = (ApplicationContext) context.getScheduler().getContext().get("applicationContext");
            IService service= applicationContext.getBean(IService.class);
            service.getManualMaxConfig();
        } catch (SchedulerException e) {
            e.printStackTrace();
        }
      }
}

And then :

<bean id="scheduler"
    class="org.springframework.scheduling.quartz.SchedulerFactoryBean">
    <property name="applicationContextSchedulerContextKey" value="applicationContext" />

</bean>

The sad news is that im also facing JavaNPE. I also try these suggestions, in vain ..

LINK

Whats wrong with what im doing?

Update 1 : Before trying to inject service, i tried to pass some Params as @ritesh.garg suggests.

public class MyJob implements Job{

    private String someParam;
    private int someParam2;

    public void setSomeParam(String someParam) {
        this.someParam = someParam;
    }


    public void setSomeParam2(int someParam2) {
        this.someParam2 = someParam2;
    }

    @Override
      public void execute(JobExecutionContext context) throws JobExecutionException {
        System.out.println("My job is running with "+someParam+' '+someParam2);
      }
}

And my jobBean.xml looks like :

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

 <bean id="scheduler"
    class="org.springframework.scheduling.quartz.SchedulerFactoryBean">
    <property name="applicationContextSchedulerContextKey" value="applicationContext" />

</bean> 


<bean id="myJob" class="org.springframework.scheduling.quartz.JobDetailFactoryBean">
    <property name="jobClass" value="com.quartz.service.MyJob"/>
    <property name="jobDataAsMap">
        <map>
            <entry key="someParam" value="some value"/>
            <entry key="someParam2" value="1"/>
        </map>
    </property>
</bean>

</beans>

I dont know why, but the parameters arent passed and it prints :

My job is running with null 0

Ps : I imported the jobBean.xml into Application.java . So i dont know what am i missing ?

Update 2 : Here is my detailed code :

@Component
public class JobScheduler{
    Timer timer = new Timer();    
   @PostConstruct
    public void distributeAutomaticConf(){
        try {
            timer.schedule(new ServiceImpl(), 10000);
        } catch (Exception e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }       
    }  
}

Service Impl :

@Transactional
@Component
public class ServiceImpl extends TimerTask implements IService{

@Override
    public void run() {
         final SchedulerFactory factory = new StdSchedulerFactory();
            Scheduler scheduler = null;

            try {
              scheduler = factory.getScheduler();


                  final JobDetailImpl jobDetail = new JobDetailImpl();
                  jobDetail.setName("My job executed only once.. ");
                  jobDetail.setJobClass(MyJob.class);

              SimpleTrigger trigger = (SimpleTrigger) newTrigger()
                        .withIdentity("trigger_", "group_")
                        .build(); 
              scheduler.start();
              scheduler.scheduleJob(jobDetail, trigger);

              System.in.read();
              if (scheduler != null) {
                scheduler.shutdown();
              }
            } catch (final SchedulerException e) {
              e.printStackTrace();
            } catch (final IOException e) {
              e.printStackTrace();
            }
    }
}

MyJob :

public class MyJob extends QuartzJobBean{

    @Autowired
    IService service;
    @Override
    protected void executeInternal(JobExecutionContext arg0) throws JobExecutionException {         SpringBeanAutowiringSupport.processInjectionBasedOnCurrentContext(this);    
        service.doSomething();  
    }
}

jobBean.xml :

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

 <bean id="scheduler"
    class="org.springframework.scheduling.quartz.SchedulerFactoryBean">
    <property name="applicationContextSchedulerContextKey" value="applicationContext" />

</bean> 


<bean id="myJob" class="org.springframework.scheduling.quartz.JobDetailFactoryBean">
    <property name="jobClass" value="com.quartz.service.MyJob"/>
    <property name="jobDataAsMap">
        <map>
            <entry key="someParam" value="some value"/>
            <entry key="someParam2" value="1"/>
        </map>
    </property>
</bean>

</beans>

quartz.properties :

org.quartz.scheduler.instanceName = DefaultQuartzScheduler
org.quartz.scheduler.rmi.export = false
org.quartz.scheduler.rmi.proxy = false
org.quartz.scheduler.wrapJobExecutionInUserTransaction = false
org.quartz.threadPool.class = org.quartz.simpl.SimpleThreadPool
org.quartz.threadPool.threadCount = 10
org.quartz.threadPool.threadPriority = 5
org.quartz.threadPool.threadsInheritContextClassLoaderOfInitializingThread = true

org.quartz.jobStore.misfireThreshold = 60000

org.quartz.jobStore.class = org.quartz.impl.jdbcjobstore.JobStoreTX
org.quartz.jobStore.driverDelegateClass=org.quartz.impl.jdbcjobstore.PostgreSQLDelegate
#org.quartz.jobStore.driverDelegateClass = org.quartz.impl.jdbcjobstore.StdJDBCDelegate
org.quartz.jobStore.dataSource = myDS
org.quartz.jobStore.tablePrefix = QRTZ_

org.quartz.dataSource.myDS.driver = org.postgresql.Driver
org.quartz.dataSource.myDS.URL = jdbc:postgresql://localhost:5432/myDB
org.quartz.dataSource.myDS.user = admin
org.quartz.dataSource.myDS.password = admin
org.quartz.dataSource.myDS.maxConnections = 10

org.quartz.scheduler.skipUpdateCheck=true

console :

java.lang.NullPointerException: null
    at com.quartz.service.MyJob.executeInternal(MyJob.java:27) ~[classes/:na]
    at org.springframework.scheduling.quartz.QuartzJobBean.execute(QuartzJobBean.java:113) ~[spring-context-support-3.1.2.RELEASE.jar:3.1.2.RELEASE]
    at org.quartz.core.JobRunShell.run(JobRunShell.java:202) ~[quartz-2.2.1.jar:na]
    at org.quartz.simpl.SimpleThreadPool$WorkerThread.run(SimpleThreadPool.java:573) [quartz-2.2.1.jar:na]

2016-06-05 11:35:16.839 ERROR 25452 --- [eduler_Worker-1] org.quartz.core.ErrorLogger              : Job (DEFAULT.My job executed only once..  threw an exception.

org.quartz.SchedulerException: Job threw an unhandled exception.
    at org.quartz.core.JobRunShell.run(JobRunShell.java:213) ~[quartz-2.2.1.jar:na]
    at org.quartz.simpl.SimpleThreadPool$WorkerThread.run(SimpleThreadPool.java:573) [quartz-2.2.1.jar:na]
Caused by: java.lang.NullPointerException: null
    at com.quartz.service.MyJob.executeInternal(MyJob.java:27) ~[classes/:na]
    at org.springframework.scheduling.quartz.QuartzJobBean.execute(QuartzJobBean.java:113) ~[spring-context-support-3.1.2.RELEASE.jar:3.1.2.RELEASE]
    at org.quartz.core.JobRunShell.run(JobRunShell.java:202) ~[quartz-2.2.1.jar:na]
    ... 1 common frames omitted
Community
  • 1
  • 1
Daniel
  • 584
  • 3
  • 8
  • 20
  • you need to use `extends QuartzJobBean` instead of `implements Job`. Also see [my answer here](http://stackoverflow.com/a/32361507/3883957) for a better way to inject the dependencies, IMO. – yishaiz Jun 05 '16 at 09:50
  • After remplacing `implements Job` with `extends QuartzJobBean` and adding `SpringBeanAutowiringSupport.processInjectionBasedOnCurrentContext(this);` it still prints null and 0 .. – Daniel Jun 05 '16 at 10:17
  • can you post your updated code? Also I want to make sure, there is only one NPE bug which is for the `IService service` in the Job? – yishaiz Jun 05 '16 at 10:36
  • Also I prefer to define those beans in the App itself, e.g. [here](http://stackoverflow.com/a/27917980/3883957), along with `@Bean public SchedulerFactoryBean schedulerFactoryBean() { final SchedulerFactoryBean bean = new SchedulerFactoryBean(); bean.setDataSource(dataSource); bean.setQuartzProperties(quartzProperties()); return bean; }`, but this may not be related to your bug itself. – yishaiz Jun 05 '16 at 10:39
  • your updated code doesn't include `@Autowired IService service;` nor `SpringBeanAutowiringSupport.processInjectionBasedOnCurrentContext(this);` nor `service.doSomething();` in the `MyJob.java` code – yishaiz Jun 05 '16 at 11:09
  • No it includes `SpringBeanAutowiringSupport.processInjectionBasedOnCurrentContext(this);` ( just see at the very right.. ). Concerning the autowired, as i said at the update 1, i would like to try passing some Params first before using the autowired annotation. – Daniel Jun 05 '16 at 11:18
  • I didn't use those setters method so I can't help you if you choose to do it that way, I'm not sure its injected by the `SpringBeanAutowiringSupport.processInjectionBasedOnCurrentContext(this);` code as by using `@Autowired`. If you want, you are welcome to try what I've suggested. – yishaiz Jun 05 '16 at 11:31
  • I didn't use those setters method via XML(only in Java code by putting it in the JobData when I've created the JobDetail) – yishaiz Jun 05 '16 at 11:37
  • Here is the NPE using Autowired .. – Daniel Jun 05 '16 at 11:40
  • I assume this service Autowiring works if not in the MyJob class. Since you work with xml configurations, have you seen [this guide](http://codrspace.com/Khovansa/spring-quartz-with-a-database/)? – yishaiz Jun 05 '16 at 11:59
  • Hi Daniel, Looking at your detailed code it seems like you are unnecessarily mixing up things. As apparent from the code, you are inheriting TimerTask in the implementation for Iservice Iface. And with this implementation, you are creating MyJob related entities (job detail, trigger and scheduler). But in the same MyJob, you are trying to autowire the same Iservice Iface. This does not make complete sense. For an example on simple implementation take a look on this: http://www.mkyong.com/spring/spring-quartz-scheduler-example/ – ritesh.garg Jun 06 '16 at 03:11
  • What I mentioned earlier in my answer/comment about passing those parameters in form of jobDataAsMap did not work for you because that context is not applicable for the job you are creating by JobDetailImpl. It is essentially an attribute of JobDetailImpl and you can set it in the code as well by using this: `jobDetail.getJobDataMap.put("param", param);`. However, as i mentioned in my previous comment, the way your code is setup seems incorrect to me. I would recommend writing a simple implementation. You do not need a timer task. A job, task, trigger and scheduler is all it needs. – ritesh.garg Jun 06 '16 at 03:15

4 Answers4

4

I have experienced the same problem in past. My understanding on this issue is that beans instantiated in spring context cannot be injected in quartz context simply by using @Autowired annotation.

I managed to solve it by using setter based dependency injection. But the same is mentioned in the "LINK" you have added in the original post.

Pasting the relevant information from the link:

Update: Replaced implements Job with extends QuartzJobBean

public class MyJob extends QuartzJobBean {
    private String someParam;
    private int someParam2;

    public void setSomeParam(String someParam) {
        this.someParam = someParam;
    }

    public void setSomeParam2(String someParam2) {
        this.someParam2 = someParam2;
    }

    @Override
    public void execute(JobExecutionContext context) throws JobExecutionException {
        System.out.println("My job is running with "+someParam+' '+someParam2);
    }
}

Here, someParam and someParam2 are being injected via setter dependency injection. Now the other part that makes this complete is to pass someParam and someParam2 in jobDataAsMap

<bean id="myJob" class="org.springframework.scheduling.quartz.JobDetailFactoryBean">
    <property name="jobClass" value="com.my.MyJob"/>
    <property name="jobDataAsMap">
        <map>
            <entry key="someParam" value="some value"/>
            <entry key="someParam2" value="1"/>
        </map>
    </property>
</bean>

In your case, it would be a value-ref="IserviceBeanId", instead of 'value' in entry. I would be surprised as well as curious, if this did not/does not work for you.

ritesh.garg
  • 3,725
  • 1
  • 15
  • 14
  • This could be useful to you as well: http://stackoverflow.com/questions/6990767/inject-bean-reference-into-a-quartz-job-in-spring/15211030#15211030 – ritesh.garg Jun 04 '16 at 00:14
  • Can you try again after replacing `implements Job` with `extends QuartzJobBean`? I remember that I was using QuartzJobBean along with a cron trigger (type of trigger should not be related to the injection issue). To see a working example for reference, you can check this: http://www.mkyong.com/spring/spring-quartz-scheduler-example/ – ritesh.garg Jun 05 '16 at 01:42
  • The dependency injection via setters finally works ! Now i add `value-ref="service" ` in entry to use the service method but it throws NPE. Here is the project : https://github.com/soufianeid/quartzSpring – Daniel Jun 06 '16 at 19:31
  • Yea, the code setup in this project seems correct. You need to name your setter correctly for IService It should be `public void setService(IService service)` since 'service' is the key in jobBean.xml. Also you will need to add the bean declaration for Iservice implementation in jobBean.xml and use the id of that bean as a value-ref – ritesh.garg Jun 06 '16 at 21:04
  • `` Also, you will need to replace `@Component("service"')` on MyJob with @Component("some other name") because you are declaring a bean named service separately – ritesh.garg Jun 07 '16 at 17:12
  • Done : https://github.com/soufianeid/quartz2 But i still have this error : `Caused by: java.io.NotSerializableException: Unable to serialize JobDataMap for insertion into database because the value of property 'jobDetail' is not serializable: com.service.ServiceImpl ` – Daniel Jun 08 '16 at 19:05
  • Hmm. JobDataMap paramters need to "implement Serializable". You'd need to replace interface-implementation classes (Iservice, ServiceImpl) with one concrete Service class and have it implement Serializable. Use that class as the bean – ritesh.garg Jun 08 '16 at 19:15
  • Done. But : `Caused by: java.io.InvalidClassException: com.example.MyJob; class invalid for deserialization`. Seems like Quartz JobDataMap doesn't work for not-primitive types .. If so, how to overcome this issue? – Daniel Jun 08 '16 at 22:22
  • 2
    Here is a simple implementation with non-primitive type. I think you are over-complicating your setup. https://github.com/ritesh-garg/QuartzExample.git – ritesh.garg Jun 09 '16 at 02:24
  • Thanks for the example but it wont work with Spring 4. However, i found this tutorial who helped me to make it works : http://websystique.com/spring/spring-4-quartz-scheduler-integration-example/ – Daniel Jun 09 '16 at 17:03
2

I fix my problem implementing "InitializingBean" in my job;

    public class MyJob extends QuartzJobBean implements InitializingBean {

        private String someParam;
        private int someParam2;

        public void setSomeParam(String someParam) {
            this.someParam = someParam;
        }

        public void setSomeParam2(String someParam2) {
            this.someParam2 = someParam2;
        }

        @Override
        public void execute(JobExecutionContext context) throws JobExecutionException {
            System.out.println("My job is running with "+someParam+' '+someParam2);
        }
        @Override
        public void afterPropertiesSet() throws Exception {
        }
    }
2

The correct way from the most of the examples I've seen is to make your Job interface implementation a @Component

@Component
public class MyJob implements Job{ 
     @Autowired IService service; 
     @Override 
     public void execute(JobExecutionContext context) throws JobExecutionException{ 
          service.doSomething();
     } 
}
Nikolay Hristov
  • 1,602
  • 1
  • 15
  • 22
0

We can use JobDataMap to pass the objects.

example: here restTemplate is Autowired.

JobDataMap newJobDataMap = new JobDataMap();
newJobDataMap.put("restTemplate", restTemplate);

JobDetail someJobDetail = JobBuilder
.newJob(QuartzJob.class)
.withIdentity(jobName, GROUP)
.usingJobData(newJobDataMap)
.build();
aytek
  • 1,842
  • 24
  • 32