如何优雅的在Spring Boot中使用Quartz
发布日期:2021-05-06 01:36:19 浏览次数:30 分类:精选文章

本文共 6972 字,大约阅读时间需要 23 分钟。

在网上看到关于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

@Configurationpublic 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
getTriggers() { HashSet
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调度。比如像下面这样。

@Componentpublic 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

@Componentpublic class QuartzJobRegister implements ApplicationRunner {    @Autowired    private List
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,重新几个简单的提供信息的方法和任务执行内容方法就可以了。

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

源码地址:

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

Maven

org.springframework
spring-context-support
org.quartz-scheduler
quartz

 

上一篇:ThreadPoolExecutor异常处理
下一篇:Executor(五):ForkJoinPool详解 jdk1.8

发表评论

最新留言

第一次来,支持一个
[***.219.124.196]2025年03月28日 02时55分51秒