0

Spring Boot 1.5

Quartz 2.2

I dynamically create and schedule Quartz jobs during runtime with a Quartz-configured as a jdbc-job-store. These jobs need to be persistent between app executions. During the job execution, I need access to the full Spring context (Spring-managed beans and JPA transactions).

However, if I try to Autowire anything into my job, then I get an error like.. "Unsatisfied dependency expressed through field myAutowiredField"

I can't figure this out. I have found tons of examples of people showing how to get autowiring to work in a Quartz job, but almost all of these just have a static, hard-coded job. Not a job dynamically created at runtime.

The example at the following URL comes the closest. It dynamically creates jobs and autowiring works great in them. However, it's a ram job store. As soon as I switch to jdbc, I'm back to square one.

https://icecarev.com/2016/11/05/spring-boot-1-4-and-quartz-scheduling-runtime-created-job-instances-from-a-configuration-file/

I have also looked at these..

Spring + Hibernate + Quartz: Dynamic Job

inject bean reference into a Quartz job in Spring?

.... etc.

But again, their solutions all seem to be missing something. For example, they rely on static jobs, triggers, or just plain don't work, etc.

Anybody have any tips or links to up-to-date resources?

Thanks!

Edit 1

Something happens to the spring context when the job is fired. Here's some code to illustrate.

In the first autowireBean() call (this is done during the Spring Boot configuration), it doesn't throw an error. NOTE: At this point, there's no use for this, I'm just showing that it does 'work' here.

In the second autowireBean() call (this is when the job is fired), it fails. This is the 'real' call.

public class AutowiringSpringBeanJobFactory extends SpringBeanJobFactory
{

    private transient AutowireCapableBeanFactory beanFactory;



    public AutowiringSpringBeanJobFactory(ApplicationContext context)
    {
        super();
        this.beanFactory = context.getAutowireCapableBeanFactory();

        MyJobClass job = new MyJobClass();
        beanFactory.autowireBean(new MyJobClass()); /** no problem **/
        beanFactory.initializeBean(job, "job");

    }

    @Override
    protected Object createJobInstance(final TriggerFiredBundle bundle) throws Exception 
    {
        final Object job = super.createJobInstance(bundle); /* job is an instance MyJobClass.. same as above */
        beanFactory.autowireBean(job); /** "Unsatisfied dependency" exception **/
        beanFactory.initializeBean(job, "job");
        return job;
    }
}

Edit 2

Well, I seem to have got it working, however, I don't know if there will be consequences. Here's the culprit in org.springframework.scheduling.quartz.AdaptableJobFactory

protected Object createJobInstance(TriggerFiredBundle bundle) throws Exception {
    return bundle.getJobDetail().getJobClass().newInstance();
}

It seems that bundle.getJobDetail().getJobClass() returns some 'proxy' type of my job class. But basically it 'says' it's returning my correct Job class, but it's different. It has the same name, but I can't cast it. For example...

Object job = bundle.getJobDetail().getJobClass().newInstance();
MyJobClass myJob = (MyJobClass) job;

Throws an error saying that I can't cast com.company.MyJobClass to com.company.MyJobClass.

So here's my 'fix'...

@Override
protected Object createJobInstance(final TriggerFiredBundle bundle) throws Exception 
{
    String className = bundle.getJobDetail().getJobClass().getName();
    Object job = Class.forName(className).newInstance();
    beanFactory.autowireBean(job);
    job = beanFactory.applyBeanPostProcessorsAfterInitialization(job, "job");  // Without this, @Transactional in my job bean doesn't work
    beanFactory.initializeBean(job, "job");
    return job;
}

Since I'm not calling super() anymore, I lose a handy feature of SpringBeanJobFactory, but I'm OK with that for the moment. I guess bottom line is that autowireBean() needs to be called before this sucker gets wrapped in a transactional proxy.

Community
  • 1
  • 1
Jim Ott
  • 725
  • 13
  • 24

1 Answers1

0

When you mention "Not a job dynamically created at runtime.", I'm going to assume that at some point you do something similar to: MyJob job = new MyJob();

If you want to inject dependencies in MyJob using Spring's @Autowire, one approach that comes to my mind is:

  • Annotate MyJob class with: @Configurable(dependencyCheck = true)
  • Run the Java process using a Java agent: java -javaagent:<path to spring-agent-${spring.version}.jar> ...
ootero
  • 3,235
  • 2
  • 16
  • 22
  • Thanks for the note. Unfortunately, the @Configurable(dependencyCheck = true) doesn't seem to do anything for me though. I added some more info to my post. – Jim Ott May 16 '17 at 15:53
  • Did you added the -agent argument to the java command? You would need both – ootero May 16 '17 at 17:20
  • Actually no. I don't really understand what that does... and it sounds like something I would try as a last resort. I 'think' I have a workable solution now. Time will tell. haha. – Jim Ott May 16 '17 at 18:42
  • Again, basically the combination of @Configurable(dependencyCheck = true) and Spring agent allows objects to be instantiated via A a = new A() but now those objects can have dependencies injected via @Autowire – ootero May 16 '17 at 19:12
  • OK thanks. Once I get my app put back together and cleaned up, maybe I will try this out. It just seems kind of scary because it seems like I would be making a fundamental low-level change that could affect a lot of things. Also, I don't really like another command-line requirement. But if I get time, I will try it out out of curiosity. Thanks for your suggestions. – Jim Ott May 16 '17 at 19:20