Spring Boot QuartzAutoConfiguration 2.1.6.RELEASE 和 2.2.2.RELEASE 之间的区别

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

我们使用的是 Spring Boot 2.1.6.RELEASE。 之后我们将 spring 版本更新为 2.2.2.RELEASE。当我们更改版本时,我们注意到我们的 quartz 作业不起作用。我们有多个作业,我们将它们配置如下。经过一些研究,我发现 QuartzAutoConfiguration class 之间存在一些差异。 我如何在 spring 2.2.2.RELEASE 中注入我的触发器。有什么简单的方法吗?我不想写很多触发器和触发器细节。

我的配置

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 版本 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 版本 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

我注意到的主要区别是;在 2.1.6 版本中,Quartz AutoConfiguration 是 "Trigger" 数组,但 2.2.2 没有 "Trigger" 数组。

您正在公开单个 Trigger[] bean,而不是多个 Trigger bean。您应该为每个 Trigger 定义一个 bean。您还应该对每个 JobDetail 执行相同的操作。这是 Spring Boot 2.1.x 的意外工作,因为您依赖使用 ObjectProvider<Trigger[]> 的自动配置。自动配置的目的是消耗所有 Trigger 个 bean,应用程序上下文在注入它们之前将这些 bean 转换为一个数组。

如果您不想定义多个 TriggerJobDetail beans,您最好自己配置 SchedulerFactoryBean 而不是依赖自动配置。

Spring 总有一些魔力:)

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;
    }
}