欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

如何在Spring Boot中使用Quartz

程序员文章站 2023-10-21 21:06:25
在网上看到关于Spring Boot整合Quartz的方式都看起来不是太好用,太复杂。一般都会定义一个实现了QuartzJobBean 的任务类(这个类不会交给Spring管理)。然后给这个Job配置相应的JobDetail和Trigger。具体操作如下:1:定义一个任务public class DateTimeJob extends QuartzJobBean { @Override protected void executeInternal(JobExecuti...

在网上看到关于Spring Boot整合Quartz的方式都看起来不是太好用,太复杂。

一般都会定义一个实现了QuartzJobBean 的任务类(这个类不会交给Spring管理)。然后给这个Job配置相应的JobDetail和Trigger。

具体操作如下:

1:定义一个任务


public class DateTimeJob extends QuartzJobBean {
 
    @Override
    protected void executeInternal(JobExecutionContext jobExecutionContext) throws JobExecutionException {
        //任务内容
    }

2:配置相应的JobDetail和Trigger

@Configuration
public class QuartzConfig {
    @Bean
    public JobDetail printTimeJobDetail(){
        return JobBuilder.newJob(DateTimeJob.class)//任务类
                .withIdentity("DateTimeJob")//可以给该JobDetail起一个id
                .usingJobData("msg", "Hello Quartz")//关联键值对
                .build();
    }
    @Bean
    public Trigger printTimeJobTrigger() {
        CronScheduleBuilder cronScheduleBuilder = CronScheduleBuilder.cronSchedule("0/1 * * * * ?");
        return TriggerBuilder.newTrigger()
                .forJob(printTimeJobDetail())//关联上述的JobDetail
                .withIdentity("quartzTaskService")//给Trigger起个名字
                .withSchedule(cronScheduleBuilder)
                .build();
    }
}

上面是配置一个Quartz任务需要的操作,然后还需要为每个任务调用scheduler.scheduleJob(省略了Quartz其他的配置,只是给出了任务配置)。

可以看到几个问题:

1:创建的任务中只是一个空的方法,在实际使用过程中会有Spring容器中的Bean注入到任务中去,那它们能成功注入到里面去吗?因为我们的任务是交给JobBuilder去创建了,这个任务的Bean会不会存放到Spring容器不清楚。

2:每次添加一个任务都需要创建任务和对应的JobDetail和Trigger。在JobDetail和Trigger的配置基本都是一个套路配置,所以会有很多重复的代码。

 

 

如何优雅的在Spring Boot中整合Quartz呢?

首先解决第一个问题。创建的任务会被加入到Spring上下文中吗?

我们看看Quartz的任务执行过程

org.quartz.core.QuartzSchedulerThread#run

for (int i = 0; i < ((List) bndles).size(); ++i) {
            TriggerFiredResult result = (TriggerFiredResult) ((List) bndles).get(i);
            TriggerFiredBundle bndle = result.getTriggerFiredBundle();
            Exception exception = result.getException();
            if (exception instanceof RuntimeException) {
                this.getLog().error("RuntimeException while firing trigger " + triggers.get(i), exception);
                this.qsRsrcs.getJobStore().releaseAcquiredTrigger((OperableTrigger) triggers.get(i));
            } else if (bndle == null) {
                this.qsRsrcs.getJobStore().releaseAcquiredTrigger((OperableTrigger) triggers.get(i));
            } else {
                JobRunShell shell = null;

                try {
            //重点创建一个新的任务
                    shell = this.qsRsrcs.getJobRunShellFactory().createJobRunShell(bndle);
                    shell.initialize(this.qs);
                } catch (SchedulerException var28) {
                    this.qsRsrcs.getJobStore().triggeredJobComplete((OperableTrigger) triggers.get(i), bndle.getJobDetail(), CompletedExecutionInstruction.SET_ALL_JOB_TRIGGERS_ERROR);
                    continue;
                }
                //将任务在另一个线程池执行

                if (!this.qsRsrcs.getThreadPool().runInThread(shell)) {
                    this.getLog().error("ThreadPool.runInThread() return false!");
                    this.qsRsrcs.getJobStore().triggeredJobComplete((OperableTrigger) triggers.get(i), bndle.getJobDetail(), CompletedExecutionInstruction.SET_ALL_JOB_TRIGGERS_ERROR);
                }
            }
        }

所以每次执行任务都会重新创建一个新的任务。这个任务的实例不会存放到Spring容器中。

任务中无法使用Spring的Bean解决方案:

1.重写SpringBeanJobFactory。重写这个工厂的createJobInstance方法,在创建完任务实例后,使用Spring提供的autowireBean方法去注入相应的Bean到这个任务实例的成员变量。

class AutowiringSpringBeanJobFactory extends SpringBeanJobFactory {

        private transient AutowireCapableBeanFactory beanFactory;

        public void setApplicationContext(final ApplicationContext context) {
            beanFactory = context.getAutowireCapableBeanFactory();
        }

        @Override
        protected Object createJobInstance(final TriggerFiredBundle bundle)
                throws Exception {
            final Object job = super.createJobInstance(bundle);
            beanFactory.autowireBean(job);
            return job;
        }
    }

2.将这个工厂配置到Quartz。

@Bean
    public JobFactory jobFactory(ApplicationContext applicationContext) {
        AutowiringSpringBeanJobFactory factory = new AutowiringSpringBeanJobFactory();
        factory.setApplicationContext(applicationContext);
        return factory;
    }

@Bean
    public SchedulerFactoryBean schedulerFactoryBean(JobFactory jobFactory) throws IOException {
        SchedulerFactoryBean factoryBean = new SchedulerFactoryBean();
        factoryBean.setJobFactory(jobFactory);
        factoryBean.setOverwriteExistingJobs(true);
        return factoryBean;
    }

这样配置后任务就能使用Spring的容器的Bean了。

 

第二个问题,模板代码多。

可以使用父类模板方法设计模式来解决。

public abstract class AbstractQuartzJobBean extends QuartzJobBean{

    public JobDetail getJobDetail() {
        return JobBuilder.newJob(this.getClass()).storeDurably(true).withIdentity(getJobName(), getJobGroup()).build();
    }


    public Set<Trigger> getTriggers() {
        HashSet<Trigger> triggers = new HashSet<>(1);
        triggers.add(TriggerBuilder.newTrigger()
                .withIdentity(getJobName(), getJobGroup())
                .withSchedule(CronScheduleBuilder.cronSchedule(getCron())).build());
        return triggers;
    }

    /**
     * 任务名称
     *
     * @return
     */
    protected abstract String getJobName();

    /**
     * 任务组
     *
     * @return
     */
    protected abstract String getJobGroup();


    /**
     * 获取Cron表达式
     *
     * @return
     */
    protected abstract String getCron();
}

任务实现这个超类,实现相应的getJobName,getJobGroup,getCron方法就能被QUARTZ调度。比如像下面这样。

@Component
public class Test extends AbstractQuartzJobBean {
    @Override
    protected String getJobName() {
        return "test";
    }

    @Override
    protected String getJobGroup() {
        return "test";
    }

    @Override
    protected String getCron() {
        return "*/5 * * * * ?";
    }

    @Override
    protected void executeInternal(JobExecutionContext jobExecutionContext) throws JobExecutionException {
        System.out.println("test");
    }
}

不过这样操作之后,任务是不会被执行的,因为我们还没有执行scheduler.scheduleJob方法。如果每增加一个任务就自己添加一句scheduler.scheduleJob很容易忘记,也很麻烦。

不知道有没有细心的同学发现我再Test测试Job上加上了@Component注解。前面刚说了任务会被每次重新创建,为什么还自己去添加Spring Bean的注解。这个注解是为了让我们偷个懒不用每增加一个任务就自己添加一句scheduler.scheduleJob

@Component
public class QuartzJobRegister implements ApplicationRunner {
    @Autowired
    private List<AbstractQuartzJobBean> schedulables;

    @Autowired
    private Scheduler scheduler;

    @Override
    public void run(ApplicationArguments args) throws Exception {
        for (Schedulable schedule : schedulables) {
            this.scheduler.scheduleJob(schedule.getJobDetail(), schedule.getTriggers(), true);
        }

    }

}

使用一个Bean实现ApplicationRunner注解,在启动的时候将所有的任务执行scheduler.scheduleJob就不用自己手动的为每个任务添加了。

经过上面的操作后,每次新增任务就只用像Test任务那样实现AbstractQuartzJobBean,重新几个简单的提供信息的方法和任务执行内容方法就可以了。

---------------------------------------------------------------------------------------------------------------------------

源码地址:https://gitee.com/johnny-SW/spring-quartz

----------------------------------------------------------------------------------------------------------------------------

Maven

<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-context-support</artifactId>
		</dependency>
		<dependency>
			<groupId>org.quartz-scheduler</groupId>
			<artifactId>quartz</artifactId>
		</dependency>

 

本文地址:https://blog.csdn.net/LCBUSHIHAHA/article/details/107066577