【quartz】quartz的基本使用

一、快速开始:

Springboot方式:
  1. pom引入quartz包

            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-quartz</artifactId>
            </dependency>
    
  2. 写一个类,继承QuartzJobBean,并重写executeInternal方法,在这个方法中,写你想要定时任务执行的业务逻辑。

    import org.quartz.JobExecutionContext;
    import org.quartz.JobExecutionException;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.springframework.scheduling.quartz.QuartzJobBean;
    
    public class DemoJob extends QuartzJobBean {
        private static final Logger logger = LoggerFactory.getLogger(DemoJob.class);
        @Override
        protected void executeInternal(JobExecutionContext context) throws JobExecutionException {
            logger.info("开始执行业务逻辑");
        }
    }
    
  3. 写一个类,这个类的作用是,设定定时任务执行的频率

    @Configuration
    public class GlobalSheduler {
        @Bean
        public JobDetail demoJobDetail(){
            return JobBuilder.newJob(DemoJob.class)
                    .storeDurably()
                    .build();
        }
        @Bean
        public Trigger demoJobTrigger(){
            SimpleScheduleBuilder simpleScheduleBuilder
                    = SimpleScheduleBuilder.simpleSchedule().withIntervalInSeconds(2).repeatForever();
            return TriggerBuilder.newTrigger().forJob(demoJobDetail())
                    .withSchedule(simpleScheduleBuilder)
                    .build();
        }
     }
    
  4. 启动application,可以看出,定时任务成功每2秒执行一次了。

    2021-12-11 07:50:38.890 [quartzScheduler_Worker-9] INFO  com.example.jobtest.job.DemoJob - 开始执行业务逻辑
    
    2021-12-11 07:50:40.894 [quartzScheduler_Worker-10] INFO com.example.jobtest.job.DemoJob - 开始执行业务逻辑
    
    2021-12-11 07:50:42.883 [quartzScheduler_Worker-1] INFO  com.example.jobtest.job.DemoJob - 开始执行业务逻辑
    
普通方式:
  1. 写一个实现Job接口的类,放业务逻辑。

    import org.quartz.Job;
    import org.quartz.JobExecutionContext;
    import org.quartz.JobExecutionException;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    
    public class HelloJob implements Job {
        private static final Logger logger = LoggerFactory.getLogger(HelloJob.class);
        @Override
        public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {
            logger.info("开始执行业务逻辑");
        }
    }
    
  2. 写main方法,执行实现逻辑。

    import org.quartz.*;
    import org.quartz.impl.StdSchedulerFactory;
    
    public class TestHelloJob {
        public static void main(String[] args) throws SchedulerException {
            //创建一个scheduler
            Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();
            scheduler.getContext().put("skey", "svalue");
    
            //创建一个Trigger
            Trigger trigger = TriggerBuilder.newTrigger()
                    .withIdentity("trigger1", "group1")
                    .usingJobData("t1", "tv1")
                    .withSchedule(SimpleScheduleBuilder.simpleSchedule().withIntervalInSeconds(3)
                            .repeatForever()).build();
            trigger.getJobDataMap().put("t2", "tv2");
    
            //创建一个job
            JobDetail job = JobBuilder.newJob(HelloJob.class)
                    .usingJobData("j1", "jv1")
                    .withIdentity("myjob", "mygroup").build();
            job.getJobDataMap().put("j2", "jv2");
    
            //注册trigger并启动scheduler
            scheduler.scheduleJob(job,trigger);
            scheduler.start();
        }
    }
    
    
  3. 执行main方法,可以看到跟上次一样。

    2021-12-11 08:43:29.097 [DefaultQuartzScheduler_Worker-4] INFO  com.example.jobtest.job.HelloJob - 开始执行业务逻辑
    2021-12-11 08:43:32.093 [DefaultQuartzScheduler_Worker-5] INFO  com.example.jobtest.job.HelloJob - 开始执行业务逻辑
    2021-12-11 08:43:35.091 [DefaultQuartzScheduler_Worker-6] INFO  com.example.jobtest.job.HelloJob - 开始执行业务逻辑
    

二、Scheduler、Job、Trigger、JobDetail都是干啥的

  1. Scheduler

    可以把这个类理解成一个执行器,它是整个定时任务的总调度,可以看见方法二中job和trigger都加入到它中,并且调用了它的start方法,定时器才能执行,这个可以类比多线程的启动方式,两种方法也可以类比多线程的两种方法。

  2. Job

    这里job的概念很简单,就是你希望执行的业务放的类,其中executeInternal()方法可以类比于Thread的run()方法,这样就好理解了。

    这里重点关注针对job有两个注解:

    关于job的状态数据(即JobDataMap)和并发性,还有一些地方需要注意。在job类上可以加入一些注解,这些注解会影响job的状态和并发性。

    @DisallowConcurrentExecution:将该注解加到job类上,告诉Quartz不要并发地执行同一个job定义(这里指特定的job类)的多个实例。请注意这里的用词。所以该限制是针对JobDetail的,而不是job类的。但是我们认为(在设计Quartz的时候)应该将该注解放在job类上,因为job类的改变经常会导致其行为发生变化。

    @PersistJobDataAfterExecution:将该注解加在job类上,告诉Quartz在成功执行了job类的execute方法后(没有发生任何异常),更新JobDetail中JobDataMap的数据,使得该job(即JobDetail)在下一次执行的时候,JobDataMap中是更新后的数据,而不是更新前的旧数据。和 @DisallowConcurrentExecution注解一样,尽管注解是加在job类上的,但其限制作用是针对job实例的,而不是job类的。由job类来承载注解,是因为job类的内容经常会影响其行为状态(比如,job类的execute方法需要显式地“理解”其”状态“)。

    如果你使用了@PersistJobDataAfterExecution注解,我们强烈建议你同时使用@DisallowConcurrentExecution注解,因为当同一个job(JobDetail)的两个实例被并发执行时,由于竞争,JobDataMap中存储的数据很可能是不确定的。

  3. JobDetail

    这个是要重点说的,先说为什么有job的情况下,还要个detail,它存在的意义是什么:

    因为scheduler在执行job的时候,实际上是每次执行都新new了一个job对象,每个job都是无状态的,执行完每个job的exec方法后,就把这个对象给丢弃了。

    验证每次执行job都是新的对象:

    2021-12-11 09:01:05.673 [DefaultQuartzScheduler_Worker-2] INFO  com.example.jobtest.job.HelloJob - 开始执行业务逻辑com.example.jobtest.job.HelloJob@213e0fad
    
    2021-12-11 09:01:08.666 [DefaultQuartzScheduler_Worker-3] INFO  com.example.jobtest.job.HelloJob - 开始执行业务逻辑com.example.jobtest.job.HelloJob@43b21dc7
    
    2021-12-11 09:01:11.667 [DefaultQuartzScheduler_Worker-4] INFO  com.example.jobtest.job.HelloJob - 开始执行业务逻辑com.example.jobtest.job.HelloJob@7f580c2e
    
    2021-12-11 09:01:14.671 [DefaultQuartzScheduler_Worker-5] INFO  com.example.jobtest.job.HelloJob - 开始执行业务逻辑com.example.jobtest.job.HelloJob@24772307
    
    

    而我们日常使用的时候,同一段逻辑,可能要接受不同参数,并根据请求参数不同执行逻辑不同。

    比如现在我们有个场景,需要根据传入参数不同打印不同日志。

    public class HelloJob implements Job {
        private static final Logger logger = LoggerFactory.getLogger(HelloJob.class);
        private String name;
        private int age;
        @Override
        public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {
            System.out.println("我叫"+getName()+",我今年"+getAge()+"岁了");
            logger.info("开始执行业务逻辑"+this.toString());
        }
    
        public String getName() {
            return name;
        }
    
        public int getAge() {
            return age;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    
        public void setAge(int age) {
            this.age = age;
        }
    }
    
            //创建一个Trigger
            Trigger trigger = TriggerBuilder.newTrigger()
                    .withIdentity("trigger1", "group1")
                    .usingJobData("t1", "tv1")
                    .withSchedule(SimpleScheduleBuilder.simpleSchedule().withIntervalInSeconds(3)
                            .repeatForever()).build();
            trigger.getJobDataMap().put("t2", "tv2");
    
            //创建一个jobDetail
            JobDetail job = JobBuilder.newJob(HelloJob.class)
                    .withIdentity("myjob", "mygroup").build();
            job.getJobDataMap().put("name","王小明");
            job.getJobDataMap().put("age", 12);
    
            //创建一个Trigger
            Trigger trigger2 = TriggerBuilder.newTrigger()
                    .withIdentity("trigger2", "group1")
                    .usingJobData("t1", "tv1")
                    .withSchedule(SimpleScheduleBuilder.simpleSchedule().withIntervalInSeconds(3)
                            .repeatForever()).build();
            trigger2.getJobDataMap().put("t2", "tv2");
    
            //创建一个job
            JobDetail job2 = JobBuilder.newJob(HelloJob.class)
                    .withIdentity("myjob2", "mygroup").build();
            job2.getJobDataMap().put("name","李晓红");
            job2.getJobDataMap().put("age", 15);
            scheduler.scheduleJob(job,trigger);
            scheduler.scheduleJob(job2,trigger2);
            scheduler.start();
    
    我叫王小明,我今年12岁了
    
    我叫李晓红,我今年15岁了
    

    可以看出,只多写jobDetail和trigger即可,不用再写一遍job中的内容了。而这里面主要实现的手段,就是通过jobDetail里的一个map,不管新建和销毁了多少个job,他们都可以共用jobdetail中的map里的值。

  4. Trigger

    这个可以理解成触发器,就是你定义你这个job啥时候执行,执行频率是什么样的,比如说每个月1号执行,每天凌晨执行等等。

    trigger的使用:

    Trigger trigger = newTrigger()
        .withIdentity("myTrigger", "group1")
        .startNow()
        .withSchedule(simpleSchedule()
            .withIntervalInSeconds(40)
            .repeatForever())            
        .build();
    

    withIdentity():指定自己的key和所在组,也可以不指定组,那就默认default组。

    startNow():表示立即执行,跟这个方法相对的,还有startAt(futureDate(5, IntervalUnit.MINUTE)) 或者endAt(dateOf(22, 0, 0)),表示在未来某个时间执行。

    withSchedule():这个就是调度的核心方法了,指定了你任务执行的时间和重复的策略。这个方法可以选择两种不同的参数,这两个的使用场景不同,一种是SimpleTrigger,一种是CronTrigger。

    SimpleTrigger适用于一些简单的重复场景,比如说我每隔一段固定的时间执行任务,或者在一个固定的时间执行一次任务,像我们日常用到的定时同步,或者隔一段时间发送一个消息之类的。

    CronTrigger相较于SimpleTrigger功能更加强大,它基于日历的概念而不是按照SimpleTrigger的精确指定间隔进行重新启动的作业启动计划。使用CronTrigger,你可以指定号时间表,例如“每周五中午”或“每个工作日和上午9:30”,甚至“每周一至周五上午9:00至10点之间每5分钟”和1月份的星期五“。

    这两种Trigger的具体使用,这里不做赘述,看官方文档。

    官方文档:https://www.w3cschool.cn/quartz_doc/

上一篇:Jetpack的ViewModel与LiveData总结


下一篇:开发模式