/ Java Programming


Injecting CDI managed beans into Quarz jobs

Quarz Job Scheduler

If you have worked with enterprise applications in the past you have most likely at some point in time needed to schedule some jobs to run at either some interval or possibly at some specific point in time. One of the go-to solutions to do this has for a long time been using the Quarz Job Scheduler.

Here is an example of a typical Quarz job you might create:

class MyJob implements Job {
	
	@Override
	void execute(JobExecutionContext context){
		// Execute some code	
	}
}

and then in your application you would schedule it pretty much like this

class MyApp {

    void startMyApp() {
 
       // Get scheduler and start it
       def scheduler = StdSchedulerFactory.defaultScheduler
       scheduler.start()

       // Create a QuarzJob to run
       def job = JobBuilder.newJob(MyJob).build()

       // Create a Trigger to trigger the job every five minutes 
       def runEveryFiveMinutes = CronScheduleBuilder.cronSchedule('0 0/5 * 1/1 * ? *')
       def trigger = TriggerBuilder.newTrigger()
			.withSchedule(runEveryFiveMinutes)
			.forJob(job)			
			.build()

       // Register Job and Trigger with the scheduler
       scheduler.scheduleJob(job, trigger) 
    }
}

CDI

Another technology you might have come across with is CDI, or JSR 299 as it also is called. What it does is basically allow you to inject, say a service, directly into a bean, without needing to manage its lifecycle (meaning you do not need to construct the service object, nor clean it up yourself, it is managed by CDI semi-automatically). This is usually done to decouple the actual implementation from the interface and allows the clients to only care about the interfaces.

A typical example of using this is when implementing a service like this:

interface MyService {
    List<Person> getPersons()
}
@ApplicationScoped
class MyServiceImpl implements MyService {

    @PostConstruct
    void init() { 
        // Setup database connection 
    }
   
    @PreDestroy
    void tearDown() { 
        // Close  database connection
    }

    @Override
    List<Person> getPersons() {
        // Retrieve persons from database
    }
}

And the way you usually get the service in your app is

@ApplicationScoped
class MyApp {

    @Inject 
    MyService service
 
    @PostConstruct
    void startMyApp() { 
         // Print all names of the registered persons
         service.persons.each { println it.name }
    }

The problem

Now that we know the technologies, say we want to use MyService inside our Quarz MyJob.

Following the above example, we would write the following job and schedule it the way we did above:

class MyJob implements Job {
	
    @Inject 
    MyService service
 
    @Override
    void execute(JobExecutionContext context){
        // Print all names of the registered persons every 5 minutes
        service.persons.each { println it.name }
    }
}

But, when the scheduler would run the job it would not print each persons name, but instead throw a NullPointerException stating that service is null!

Why is that?

For CDI to work it needs to manage the whole bean creation chain so it can inject the correct instances into the beans. But when we create a Quarz job using JobBuilder.newJob(MyJob).build() the JobBuilder is creating job instances that CDI does not know anything about and so never has a chance to inject the service into the job.


The solution

So how do we tell the Quarz scheduler that it should not create the bean instance itself, but, instead delegate the creation of the bean instance to CDI?

To do that we need to create our own factory for creating jobs with CDI.

@ApplicationScoped
class CDIJobFactory implements JobFactory {
	
	@Inject
	BeanManager beanManager
	
	@Override
	Job newJob(TriggerFiredBundle bundle, Scheduler scheduler) {
	    def jobClazz = bundle.jobDetail.jobClass
	    def bean = beanManager.getBeans(jobClazz).first()
	    def ctx = beanManager.createCreationalContext(bean)				
	    beanManager.getReference(bean, jobClazz, ctx)		
	}
}

What this factory will do is use the CDI BeanManager to create the job instances with. This way, the instances will be properly managed by CDI and we can use CDI injection inside the jobs.

So how do we use this factory to create the jobs with, lets combine both MyApp implementations from the Quarz example and the CDI example into one application and use our job factory to create the project. It would look like this:

@ApplicationScoped
class MyApp {
  
    @Inject 
    CDIJobFactory jobFactory

    void startMyApp() {
 
       // Get scheduler and start it
       def scheduler = StdSchedulerFactory.defaultScheduler
       
       // Use the CDI managed job factory
       scheduler.jobFactory = jobFactory
       
       // Start scheduler
       scheduler.start()

       // Create a QuarzJob to run
       def job = JobBuilder.newJob(MyJob).build()

       // Create a Trigger to trigger the job every five minutes 
       def runEveryFiveMinutes = CronScheduleBuilder.cronSchedule('0 0/5 * 1/1 * ? *')
       def trigger = TriggerBuilder.newTrigger()
			.withSchedule(runEveryFiveMinutes)
			.forJob(job)			
			.build()

       // Register Job and Trigger with the scheduler
       scheduler.scheduleJob(job, trigger) 
    }
}

Now, the JobBuilder will use our CDIJobFactory to create the MyJob and the service will be injected properly into the instance for the execute method to use.

A word about scopes

In this example I have used the @ApplicationScope extensively to denote that the beans we create should always exist as long as the application is running. However, if you are building a web application you might have beans that have @SessionScope and will only live for the duration of the HTTP session. These beans won't work with Quarz jobs as nothing guarantees that there exists a HTTP Session when the Quarz trigger runs.