• Spring Boot 应用系列 6 -- Spring Boot 2 整合Quartz


    Quartz是实现定时任务的利器,Quartz主要有四个组成部分,分别是:

    1. Job(任务):包含具体的任务逻辑;

    2. JobDetail(任务详情):是对Job的一种详情描述;

    3. Trigger(触发器):负责管理触发JobDetail的机制;

    4. Scheduler(调度器):负责Job的执行。

    有两种方式可以实现Spring Boot与Quartz的整合:

    一、使用Spring提供的工厂类

    spring-context.jar和spring-context-support.jar类库提供了一些org.quartz包的扩展,使用这些扩展并通过注入bean的方式可以实现与Quartz的整合。

    1. pom.xml

    添加依赖:

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

    Spring Boot 2.0.4指定的quartz.jar的版本是2.3.0

    2. Job

    首先定义一个执行器:

    import java.util.Date;
    
    import devutility.internal.text.format.DateFormatUtils;
    
    public class Executor {
        public static void execute(String name, long costMillis) {
            Date startDate = new Date();
            Date endDate = new Date(startDate.getTime() + costMillis);
    
            StringBuilder stringBuilder = new StringBuilder();
            stringBuilder.append(String.format("[%s]: ", DateFormatUtils.format(startDate, "yyyy-MM-dd HH:mm:ss:SSS")));
            stringBuilder.append(String.format("%s executing on %s, ", name, Thread.currentThread().getName()));
            stringBuilder.append(String.format("will finish at %s.", DateFormatUtils.format(endDate, "yyyy-MM-dd HH:mm:ss:SSS")));
            System.out.println(stringBuilder.toString());
    
            try {
                Thread.sleep(costMillis);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    定义Job:

    import org.springframework.scheduling.annotation.EnableScheduling;
    import org.springframework.stereotype.Component;
    
    import devutility.test.app.quartz.common.Executor;
    
    @Component
    @EnableScheduling
    public class SpringJobs {
        public void job1() {
            Executor.execute("Job1", 5000);
        }
    
        public void job2() {
            Executor.execute("Job2", 6000);
        }
    }

    @EnableScheduling注解是必须要有的。

    3. JobDetail

     1 package devutility.test.app.quartz.config;
     2 
     3 import org.quartz.Trigger;
     4 import org.springframework.beans.factory.annotation.Qualifier;
     5 import org.springframework.context.annotation.Bean;
     6 import org.springframework.context.annotation.Configuration;
     7 import org.springframework.scheduling.quartz.CronTriggerFactoryBean;
     8 import org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean;
     9 import org.springframework.scheduling.quartz.SchedulerFactoryBean;
    10 
    11 import devutility.test.app.quartz.jobs.SpringJobs;
    12 
    13 @Configuration
    14 public class SpringJobConfiguration {
    15     @Bean
    16     public MethodInvokingJobDetailFactoryBean jobDetailFactory1(SpringJobs springJobs) {
    17         MethodInvokingJobDetailFactoryBean jobDetailFactory = new MethodInvokingJobDetailFactoryBean();
    18         jobDetailFactory.setName("Spring-Job1");
    19         jobDetailFactory.setGroup("Spring-Group");
    20 
    21         jobDetailFactory.setTargetObject(springJobs);
    22         jobDetailFactory.setTargetMethod("job1");
    23 
    24         jobDetailFactory.setConcurrent(false);
    25         return jobDetailFactory;
    26     }
    27 
    28     @Bean
    29     public MethodInvokingJobDetailFactoryBean jobDetailFactory2(SpringJobs springJobs) {
    30         MethodInvokingJobDetailFactoryBean jobDetailFactory = new MethodInvokingJobDetailFactoryBean();
    31         jobDetailFactory.setName("Spring-Job2");
    32         jobDetailFactory.setGroup("Spring-Group");
    33 
    34         jobDetailFactory.setTargetObject(springJobs);
    35         jobDetailFactory.setTargetMethod("job2");
    36 
    37         jobDetailFactory.setConcurrent(true);
    38         return jobDetailFactory;
    39     }

    MethodInvokingJobDetailFactoryBean是来自spring-context-support.jar的一个工厂类,它实现了FactoryBean<JobDetail>接口,完成了对具体job的封装。

    21行指定具体的任务是我们在2中定义的SpringJobs,22行指定我们使用SpringJobs中的job1方法作为定时任务的具体业务实现。

    注意24和37行,如果一个任务每隔5秒执行一次,但是每次需要执行10秒,那么它有两种执行方式,串行(执行完上一个再开启下一个)和并行(间隔时间到了就开始并行执行下一个),24和37就分别设置成了并行和串行执行。

    4. Trigger

    Quartz支持多种trigger,其中配置最为灵活的一种当属CronTrigger,它属于表达式类型的配置方式,有关cron表达式的配置请参考

     1 package devutility.test.app.quartz.config;
     2 
     3 import org.quartz.Trigger;
     4 import org.springframework.beans.factory.annotation.Qualifier;
     5 import org.springframework.context.annotation.Bean;
     6 import org.springframework.context.annotation.Configuration;
     7 import org.springframework.scheduling.quartz.CronTriggerFactoryBean;
     8 import org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean;
     9 import org.springframework.scheduling.quartz.SchedulerFactoryBean;
    10 
    11 import devutility.test.app.quartz.jobs.SpringJobs;
    12 
    13 @Configuration
    14 public class SpringJobConfiguration {
    15     @Bean
    16     public CronTriggerFactoryBean cronTriggerFactory1(@Qualifier("jobDetailFactory1") MethodInvokingJobDetailFactoryBean jobDetailFactory1) {
    17         CronTriggerFactoryBean cronTriggerFactoryBean = new CronTriggerFactoryBean();
    18         cronTriggerFactoryBean.setName("cronTriggerFactoryForJobDetailFactory1");
    19         cronTriggerFactoryBean.setJobDetail(jobDetailFactory1.getObject());
    20         cronTriggerFactoryBean.setCronExpression("0/3 * * * * ?");
    21         return cronTriggerFactoryBean;
    22     }
    23 
    24     @Bean
    25     public CronTriggerFactoryBean cronTriggerFactory2(@Qualifier("jobDetailFactory2") MethodInvokingJobDetailFactoryBean jobDetailFactory2) {
    26         CronTriggerFactoryBean cronTriggerFactoryBean = new CronTriggerFactoryBean();
    27         cronTriggerFactoryBean.setName("cronTriggerFactoryForJobDetailFactory2");
    28         cronTriggerFactoryBean.setJobDetail(jobDetailFactory2.getObject());
    29         cronTriggerFactoryBean.setCronExpression("0/4 * * * * ?");
    30         return cronTriggerFactoryBean;
    31     }

    本例使用的Cron表达式非常简单,分别是每隔3秒和每隔4秒执行一次。

    5. Scheduler

    1     @Bean
    2     public SchedulerFactoryBean schedulerFactory1(Trigger cronTriggerFactory1, Trigger cronTriggerFactory2) {
    3         SchedulerFactoryBean schedulerFactoryBean = new SchedulerFactoryBean();
    4         schedulerFactoryBean.setStartupDelay(2);
    5         schedulerFactoryBean.setTriggers(cronTriggerFactory1, cronTriggerFactory2);
    6         return schedulerFactoryBean;
    7     }

    行4指明系统启动之后需要延迟2秒执行;

    行5用于注册需要执行的触发器;

    SchedulerFactoryBean还有一个叫autoStartup的属性,用于指明任务在系统启动之后是否立即执行,默认是true。

    6. 测试

    由此可见,Job1是按照我们的预期串行执行的。

    Job2则是并行执行的。

    二、使用org.quartz原生类和方法

    1. pom.xml,只需要添加quartz

    <dependency>
        <groupId>org.quartz-scheduler</groupId>
        <artifactId>quartz</artifactId>
    </dependency>

    2. 定义一个Scheduler类型的Bean

    package devutility.test.app.quartz.config;
    
    import org.quartz.Scheduler;
    import org.quartz.SchedulerException;
    import org.quartz.impl.StdSchedulerFactory;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    
    @Configuration
    public class QuartzConfiguration {
        @Bean
        public Scheduler scheduler() throws SchedulerException {
            return StdSchedulerFactory.getDefaultScheduler();
        }
    }

    3. Job

    package devutility.test.app.quartz.jobs;
    
    import org.quartz.DisallowConcurrentExecution;
    import org.quartz.Job;
    import org.quartz.JobDetail;
    import org.quartz.JobExecutionContext;
    import org.quartz.JobExecutionException;
    
    import devutility.test.app.quartz.common.Executor;
    
    @DisallowConcurrentExecution
    public class ScheduleJob1 implements Job {
        @Override
        public void execute(JobExecutionContext context) throws JobExecutionException {
            Executor.execute("ScheduleJob1", 5000);
    
            JobDetail jobDetail = context.getJobDetail();
            System.out.println(String.format("key: %s", jobDetail.getKey()));
        }
    }

    这种方式每一个Job都需要定义一个类实现org.quartz.Job接口,形式上要比第一种方式更条理。

    3. 我们定义一个创建任意Job的公共方法,来实现Job类的定时执行:

     1 package devutility.test.app.quartz.services;
     2 
     3 import org.quartz.CronScheduleBuilder;
     4 import org.quartz.CronTrigger;
     5 import org.quartz.Job;
     6 import org.quartz.JobBuilder;
     7 import org.quartz.JobDetail;
     8 import org.quartz.JobKey;
     9 import org.quartz.Scheduler;
    10 import org.quartz.SchedulerException;
    11 import org.quartz.TriggerBuilder;
    12 import org.quartz.TriggerKey;
    13 import org.springframework.beans.factory.annotation.Autowired;
    14 import org.springframework.stereotype.Service;
    15 
    16 import devutility.internal.models.OperationResult;
    17 
    18 @Service
    19 public class JobServiceImpl implements JobService {
    20     @Autowired
    21     private Scheduler schedulerFactory1;
    22 
    23     @Autowired
    24     private Scheduler scheduler;
    25 
    26     @Override
    27     public void start(String name, String group, String cronExpression, Class<? extends Job> clazz) throws SchedulerException {
    28         JobDetail jobDetail = JobBuilder.newJob(clazz).withIdentity(name, group).build();
    29 
    30         String triggerName = String.format("trigger_%s", name);
    31         CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule(cronExpression);
    32         CronTrigger trigger = TriggerBuilder.newTrigger().withIdentity(triggerName, group).withSchedule(scheduleBuilder).build();
    33         scheduler.scheduleJob(jobDetail, trigger);
    34 
    35         if (!scheduler.isStarted()) {
    36             scheduler.start();
    37         }
    38     }

    28行创建一个新的JobDetail,并将Job实现类的Class对象赋值给它;

    32行创建了一个新的Trigger;

    33-37行使用注入进来的scheduler来运行一个定时任务。

    三、对Job的CRUD操作

    创建和运行Job上面已经讲过了,下面说一下Job的暂停、中断、删除和更新操作。

    1. 暂停

    scheduler有一个方法public void pauseJob(JobKey jobKey),该方法通过暂停trigger的触发来实现暂停job的功能,调用该方法之后正在运行的job会持续运行到业务逻辑处理完毕,等下一个触发条件满足时不再开启新的job。

        public OperationResult pause(String group, String name) {
            OperationResult result = new OperationResult();
            JobKey jobKey = JobKey.jobKey(name, group);
    
            try {
                scheduler.pauseJob(jobKey);
            } catch (SchedulerException e) {
                e.printStackTrace();
                result.setErrorMessage(String.format("Pause Job with name = "%s" group = "%s" failed, system error!", name, group));
            }
    
            return result;
        }

    2. 中断

    针对需要中断的Job,quartz专门为其定义了一个接口org.quartz.InterruptableJob:

    public interface InterruptableJob extends Job {
    
        /*
         * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
         * 
         * Interface.
         * 
         * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
         */
    
        /**
         * <p>
         * Called by the <code>{@link Scheduler}</code> when a user
         * interrupts the <code>Job</code>.
         * </p>
         * 
         * @throws UnableToInterruptJobException
         *           if there is an exception while interrupting the job.
         */
        void interrupt()
            throws UnableToInterruptJobException;
    }

    所有需要支持中断的Job都需要实现这个接口:

    package devutility.test.app.quartz.jobs;
    
    import org.quartz.DisallowConcurrentExecution;
    import org.quartz.InterruptableJob;
    import org.quartz.JobExecutionContext;
    import org.quartz.JobExecutionException;
    import org.quartz.UnableToInterruptJobException;
    
    import devutility.test.app.quartz.common.Executor;
    
    @DisallowConcurrentExecution
    public class ScheduleJob2 implements InterruptableJob {
        @Override
        public void execute(JobExecutionContext context) throws JobExecutionException {
            Executor.execute(context, 5000);
        }
    
        @Override
        public void interrupt() throws UnableToInterruptJobException {
            System.out.println("ScheduleJob2 is interrupting now。。。");
        }
    }

    然后通过调用Scheduler实例的boolean interrupt(JobKey jobKey)方法,查看StdScheduler的源码,我们发现Scheduler的interrupt方法仅仅是调用了InterruptableJob中的interrupt()方法实现,然后设置了一下自己的interrupted属性就完了,并未做任何其他操作。

    所以,如果要实现可以中断的Job,我们需要在InterruptableJob实现类中增加中断的逻辑:

    package devutility.test.app.quartz.jobs;
    
    import org.quartz.InterruptableJob;
    import org.quartz.JobExecutionContext;
    import org.quartz.JobExecutionException;
    import org.quartz.UnableToInterruptJobException;
    
    import devutility.test.app.quartz.common.Executor;
    
    public class ScheduleJob3 implements InterruptableJob {
        private boolean interrupted = false;
    
        @Override
        public void execute(JobExecutionContext context) throws JobExecutionException {
            Executor.execute("ScheduleJob3 sub-task 1", 1000);
    
            if (interrupted) {
                return;
            }
    
            Executor.execute("ScheduleJob3 sub-task 2", 1000);
    
            if (interrupted) {
                return;
            }
    
            Executor.execute("ScheduleJob3 sub-task 3", 1000);
        }
    
        @Override
        public void interrupt() throws UnableToInterruptJobException {
            interrupted = true;
        }
    }

    3. 删除

        public OperationResult delete(String jobName, String jobGroup) {
            OperationResult result = new OperationResult();
            result.append(String.format("Removing Quartz job: %s", jobName));
    
            try {
                if (!schedulerFactory1.deleteJob(JobKey.jobKey(jobName, jobGroup))) {
                    result.setErrorMessage(String.format("Removing job %s failed!", jobName));
                }
            } catch (SchedulerException e) {
                e.printStackTrace();
                result.setErrorMessage(String.format("Removing job %s failed with error!", jobName));
            }
    
            return result;
        }

    4. 更新

        public OperationResult update(CronTrigger cronTrigger, String cronExpression) {
            OperationResult result = new OperationResult();
            TriggerKey triggerKey = cronTrigger.getKey();
            CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule(cronExpression);
            CronTrigger newTrigger = cronTrigger.getTriggerBuilder().withSchedule(scheduleBuilder).build();
    
            try {
                schedulerFactory1.rescheduleJob(triggerKey, newTrigger);
                result.append(String.format("Update cron trigger %s succeeded!", triggerKey.getName()));
            } catch (SchedulerException e) {
                e.printStackTrace();
                result.setErrorMessage(String.format("Update cron trigger %s failed!", triggerKey.getName()));
            }
    
            return result;
        }

    Demo代码

  • 相关阅读:
    Python读写ini文件
    MySQL 让主键id 从1开始自增
    python 找字符串中所有包含字符的下标
    centos7防火墙命令
    如何将npm升级到最新版本
    将 Npm 的源替换成淘宝的源
    MySQL 时间格式化/ MySQL DATE_FORMAT
    Python中crypto模块进行AES加密和解密
    windows环境下python3安装Crypto
    Nginx+PHPSTORM+Xdebug 配置
  • 原文地址:https://www.cnblogs.com/eagle6688/p/9635183.html
Copyright © 2020-2023  润新知