10

In an application, since I converted it from a classical Spring webapp (deployed in a system Tomcat) to a Spring Boot (V1.2.1) application I face the problem that the Quartz-based scheduled jobs are not working anymore.

I schedule these Quartz jobs like this:

// My own Schedule object which holds data about what to schedule when
Schedule schedule = scheduleService.get(id of the schedule);

String scheduleId = schedule.getId();

JobKey jobKey = new JobKey(scheduleId);
TriggerKey triggerKey = new TriggerKey(scheduleId);

JobDataMap jobData = new JobDataMap();
jobData.put("scheduleId", scheduleId);

JobBuilder jobBuilder = JobBuilder.newJob(ScheduledActionRunner.class)
    .withIdentity(jobKey)
    .withDescription(schedule.getName())
    .usingJobData(jobData);

JobDetail job = jobBuilder.build();

TriggerBuilder triggerBuilder = TriggerBuilder.newTrigger()
    .forJob(jobKey)
    .withIdentity(triggerKey)
    .withDescription(schedule.getName());

triggerBuilder = triggerBuilder.withSchedule(CronScheduleBuilder.cronSchedule(schedule.toCronExpression()));

Trigger trigger = triggerBuilder.build();

org.quartz.Scheduler scheduler = schedulerFactoryBean.getScheduler();

scheduler.scheduleJob(job, trigger);

ScheduledActionRunner:

@Component
public class ScheduledActionRunner extends QuartzJobBean {

    @Autowired
    private ScheduleService scheduleService;

    public ScheduledActionRunner() {
    }

    @Override
    public void executeInternal(final JobExecutionContext context) throws JobExecutionException {
        SpringBeanAutowiringSupport.processInjectionBasedOnCurrentContext(this);
        final JobDataMap jobDataMap = context.getMergedJobDataMap();
        final String scheduleId = jobDataMap.getString("scheduleId");
        final Schedule schedule = scheduleService.get(scheduleId);
        // here it goes BANG since scheduleService is null
    }
}

ScheduleService is a classical Spring service which fetches data from Hibernate. As I said above, this worked fine until I moved to Spring Boot.

When I implemented this code with the classical Spring application, SpringBeanAutowiringSupport.processInjectionBasedOnCurrentContext(this); did the trick to take care of autowiring the service.

What is needed to make this work again in the Spring Boot environment ?

Edit:

At the end I chose to move away from using Quartz in favour of Spring's ThreadPoolTaskScheduler.The code was much simplified and it works as expected.

1
  • Comment to answer from Dewfy (since I do not have enough rep to comment): I also had to mark the method with the @Transactional annotation, since I got the error that the hibernate session was not attached.
    – gooboo
    Commented May 25, 2017 at 23:09

2 Answers 2

12

The SpringBeanAutowiringSupport uses the web application context, which is not available in your case. If you need a spring managed beans in the quartz you should use the quartz support provided by spring. This will give you full access to all the managed beans. For more info see the quartz section at spring docs at http://docs.spring.io/spring/docs/current/spring-framework-reference/html/scheduling.html. Also see following example of usage quartz with spring managed beans. Example is based on your code. So you can change the first code snippet (where the quartz initialization is done) with follwoing spring alternatives.

Create job detail factory

@Component
public class ScheduledActionRunnerJobDetailFactory extends JobDetailFactoryBean {

    @Autowired
    private ScheduleService scheduleService;

    @Override
    public void afterPropertiesSet() {
       setJobClass(ScheduledActionRunner.class);
       Map<String, Object> data = new HashMap<String, Object>();
       data.put("scheduleService", scheduleService);
       setJobDataAsMap(data);
       super.afterPropertiesSet();
   }
}

Create the trigger factory

@Component
public class ActionCronTriggerFactoryBean extends CronTriggerFactoryBean {

   @Autowired
   private ScheduledActionRunnerJobDetailFactory jobDetailFactory;

   @Value("${cron.pattern}")
   private String pattern;

   @Override
   public void afterPropertiesSet() throws ParseException {
       setCronExpression(pattern);
       setJobDetail(jobDetailFactory.getObject());
       super.afterPropertiesSet();
   }

}

And finally create the SchedulerFactory

@Component
public class ActionSchedulerFactoryBean extends SchedulerFactoryBean {

   @Autowired
   private ScheduledActionRunnerJobDetailFactory jobDetailFactory;

   @Autowired
   private ActionCronTriggerFactoryBean triggerFactory;

   @Override
   public void afterPropertiesSet() throws Exception {
       setJobDetails(jobDetailFactory.getObject());
       setTriggers(triggerFactory.getObject());
       super.afterPropertiesSet();
   }

}
2
  • Thank you @Babl for your answer. Before trying out your proposed code, my only and last question is: Why did it work before changed the Application to use Spring Boot? Prior Spring Boot it was already based on JavaConfig and Spring 4.1, and the scheduling worked fine.
    – yglodt
    Commented Jan 25, 2015 at 19:29
  • 1
    As I mentioned earlier, the SpringBeanAutowiringSupport uses the web application context to find the beans and inject them to your object, but since the spring boot application is not fully a web application the context is null. So if you enable a logs for SpringBeanAutowiringSupport at debug leve,you will see a devug message hich tells that the target class was not constructed in a Spring web application. Any ways turning on the debug leve for spring will give you lots of internal information on the issue ;)
    – Babl
    Commented Jan 25, 2015 at 20:16
9

My answer not fully matches to you question, but Spring expose you another ability - to start cron-expression based scheduler on any service.

Using Spring.Boot you can configure your application to use scheduler by simple placing

@EnableScheduling
public class Application{
....

After that just place following annotation on public(!) method of @Service

@Service
public class MyService{
...
    @Scheduled(cron = "0 * * * * MON-FRI")
    public void myScheduledMethod(){
    ....
    }
2
  • 3
    Thanks, I am aware of this and use it already for Schedules which are static. The problem I describe above comes from Schedules which can be set by the user, so I need to add/edit/remove them at runtime, which as far as I know is not possible with the @Scheduled annotation.
    – yglodt
    Commented Jan 28, 2015 at 21:37
  • 1
    @agilob What exactly do you ask? May you post stand-alone question and give me link there. Origin question did not reference to "multicluster". But may be you can take a look at Spring Cloud (with Global Lock and Leader Election topics)
    – Dewfy
    Commented Jun 8, 2018 at 10:01

Not the answer you're looking for? Browse other questions tagged or ask your own question.