Search code examples
springspring-bootquartz-schedulerspring-scheduled

Difference between Spring Boot QuartzAutoConfiguration 2.1.6.RELEASE & 2.2.2.RELEASE


We were using Spring Boot 2.1.6.RELEASE. after that we updated spring version to 2.2.2.RELEASE. When we change the version we noticed our quartz jobs not working. We have multiple jobs and we configured them like below. After some reasearch i found some differences between in QuartzAutoConfiguration class. How can i inject my triggers in spring 2.2.2.RELEASE. Is there any easy way? I dont want to write to many triggers and trigger details.

MyConfig

import io.rkpc.commons.util.ApplicationReflectionUtil;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.quartz.*;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Profile;
import org.springframework.scheduling.quartz.QuartzJobBean;
import org.springframework.util.CollectionUtils;

import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;

@Slf4j
@Configuration
@ConfigurationProperties(prefix = "quartz")
@Profile("quartz")
@Data
public class JobConfig {

    private List<Job> jobs;

    @Bean
    public JobDetail[] jobDetail() throws SchedulerConfigException {
        Set<Class<QuartzJobBean>> subClasses = ApplicationReflectionUtil.getSubClasses(QuartzJobBean.class, "io.rkpc");
        List<JobDetail> jobDetails = new ArrayList<>();
        for (Class<QuartzJobBean> quartzJobBeanClass : subClasses) {
            Job job = getJob(quartzJobBeanClass.getSimpleName());
            if (job.isEnabled()) {
                JobDetail jobDetail = JobBuilder.newJob(quartzJobBeanClass)
                        .withIdentity(quartzJobBeanClass.getSimpleName())
                        .storeDurably()
                        .build();
                jobDetails.add(jobDetail);
            }
        }
        return jobDetails.toArray(new JobDetail[0]);
    }

    @Bean
    public Trigger[] jobATrigger(JobDetail[] jobADetails) throws SchedulerConfigException {
        List<Trigger> triggers = new ArrayList<>();
        for (JobDetail jobDetail : jobADetails) {
            Job job = getJob(jobDetail.getKey().getName());
            CronTrigger trigger = TriggerBuilder.newTrigger().forJob(jobDetail)
                    .withIdentity(jobDetail.getKey().getName().concat("Trigger"))
                    .withSchedule(CronScheduleBuilder.cronSchedule(job.getCron()))
                    .build();
            triggers.add(trigger);
        }
        return triggers.toArray(new Trigger[0]);
    }

    private Job getJob(String name) throws SchedulerConfigException {
        List<Job> filteredJobs = jobs.stream().filter(job -> job.getName().equals(name)).collect(Collectors.toList());
        if (CollectionUtils.isEmpty(filteredJobs) || filteredJobs.size() > 1) {
            log.error("{} is not configured", name);
            throw new SchedulerConfigException("Job is not configured");
        }

        return filteredJobs.get(0);
    }

    @Data
    public static class Job {
        private String name;
        private String cron;
        private boolean enabled;
    }
}

QuartzAutoConfiguration.java Spring version 2.1.6 github url ; https://github.com/spring-projects/spring-boot/blob/v2.1.6.RELEASE/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/quartz/QuartzAutoConfiguration.java

QuartzAutoConfiguration.java Spring version 2.2.2 github url https://github.com/spring-projects/spring-boot/blob/v2.2.2.RELEASE/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/quartz/QuartzAutoConfiguration.java

Main difference i notice is ; in 2.1.6 version Quartz AutoConfiguration was "Trigger" array but 2.2.2 doesn't have "Trigger" array.


Solution

  • Spring has always some magic :)

    import io.rkpc.commons.util.ApplicationReflectionUtil;
    import lombok.Data;
    import lombok.RequiredArgsConstructor;
    import lombok.extern.slf4j.Slf4j;
    import org.quartz.*;
    import org.quartz.impl.JobDetailImpl;
    import org.quartz.impl.triggers.CronTriggerImpl;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.beans.factory.support.DefaultListableBeanFactory;
    import org.springframework.beans.factory.support.GenericBeanDefinition;
    import org.springframework.boot.autoconfigure.AutoConfigureBefore;
    import org.springframework.boot.autoconfigure.quartz.QuartzAutoConfiguration;
    import org.springframework.boot.context.properties.ConfigurationProperties;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.context.annotation.Profile;
    import org.springframework.scheduling.quartz.QuartzJobBean;
    import org.springframework.util.CollectionUtils;
    
    import javax.annotation.PostConstruct;
    import javax.validation.constraints.NotNull;
    import java.text.ParseException;
    import java.util.List;
    import java.util.Set;
    import java.util.stream.Collectors;
    
    @Slf4j
    @Configuration
    @ConfigurationProperties(prefix = "quartz")
    @Profile("quartz")
    @Data
    @AutoConfigureBefore({QuartzAutoConfiguration.class})
    @RequiredArgsConstructor(onConstructor = @__({@Autowired, @NotNull}))
    public class JobConfig {
    
        private final List<Job> jobs;
        private final DefaultListableBeanFactory beanFactory;
    
        @PostConstruct
        public void init() throws SchedulerConfigException, ParseException {
            Set<Class<QuartzJobBean>> subClasses = ApplicationReflectionUtil.getSubClasses(QuartzJobBean.class, "io.rkpc");
    
            for (Class<QuartzJobBean> quartzJobBeanClass : subClasses) {
                Job job = getJob(quartzJobBeanClass.getSimpleName(), jobs);
                if (job.isEnabled()) {
                    JobDetailImpl jobDetail = (JobDetailImpl) JobBuilder.newJob(quartzJobBeanClass)
                            .withIdentity(quartzJobBeanClass.getSimpleName())
                            .storeDurably()
                            .build();
                    CronTriggerImpl trigger = (CronTriggerImpl) TriggerBuilder.newTrigger().forJob(jobDetail)
                            .withIdentity(jobDetail.getKey().getName().concat("Trigger"))
                            .withSchedule(CronScheduleBuilder.cronSchedule(job.getCron()))
                            .build();
    
                    GenericBeanDefinition jobBeanDefinition = new GenericBeanDefinition();
                    jobBeanDefinition.setBeanClass(JobDetailImpl.class);
                    jobBeanDefinition.getPropertyValues().addPropertyValue("jobClass", quartzJobBeanClass);
                    jobBeanDefinition.getPropertyValues().addPropertyValue("key", jobDetail.getKey());
                    jobBeanDefinition.getPropertyValues().addPropertyValue("durability", jobDetail.isDurable());
                    beanFactory.registerBeanDefinition(quartzJobBeanClass.getSimpleName(), jobBeanDefinition);
    
                    GenericBeanDefinition triggerBeanDefinition = new GenericBeanDefinition();
                    triggerBeanDefinition.setBeanClass(CronTriggerImpl.class);
                    triggerBeanDefinition.getPropertyValues().addPropertyValue("jobKey", trigger.getJobKey());
                    triggerBeanDefinition.getPropertyValues().addPropertyValue("key", trigger.getKey());
                    triggerBeanDefinition.getPropertyValues().addPropertyValue("cronExpression", new CronExpression(trigger.getCronExpression()));
                    beanFactory.registerBeanDefinition(trigger.getName(), triggerBeanDefinition);
                }
            }
        }
    
        public Job getJob(String name, List<Job> jobs) throws SchedulerConfigException {
            List<Job> filteredJobs = jobs.stream().filter(job -> job.getName().equals(name)).collect(Collectors.toList());
            if (CollectionUtils.isEmpty(filteredJobs) || filteredJobs.size() > 1) {
                log.error("{} is not configured", name);
                throw new SchedulerConfigException("Job is not configured");
            }
    
            return filteredJobs.get(0);
        }
    
        @Data
        public static class Job {
            private String name;
            private String cron;
            private boolean enabled;
        }
    }