Maven依赖

  1. <dependency>
  2. <groupId>org.quartz-scheduler</groupId>
  3. <artifactId>quartz</artifactId>
  4. <version>2.3.2</version>
  5. </dependency>

三大组件:Job、Trigger、Scheduler

  • Job——任务
  • Trigger——触发器
  • Scheduler——调度器

任务和触发器进行绑定,然后由调度器调用触发器进行执行任务
关系大概如下
Scheduler与Trigger一对多
Trigger与JobDetail多对一
JobDetail与Job多对一

image.png

第一个quartz程序

public static void main(String[] args) {
    try {
        //调度器
        Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();
        scheduler.start();
        System.out.println(scheduler.getSchedulerName());
        System.out.println("线程个数" + scheduler.getMetaData().getThreadPoolSize());
        scheduler.shutdown();
    } catch (SchedulerException e) {
        e.printStackTrace();
    }
}

scheduler启动任务,scheduler停止任务,没有用到job trigger

注意静态导入

import static org.quartz.CronScheduleBuilder.cronSchedule;
import static org.quartz.JobBuilder.newJob;
import static org.quartz.SimpleScheduleBuilder.simpleSchedule;
import static org.quartz.TriggerBuilder.newTrigger;

使用这些类的方法时不需要在方法名前写类和点,下面的代码都用到了

调度器Scheduler

使用工厂模式创建对象

 Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();

scheduler的一些api

scheduler.start();
//添加jobdetail 将job保存在内存中
scheduler.addJob(job, false);
//使用调度器调度 这样绑定就不需要上面那一行
scheduler.scheduleJob(job, trigger1);
scheduler.scheduleJob(trigger2);
scheduler.shutdown();
//根据jobdetail对象进行一些操作
//删除job
scheduler.deleteJob(detail.getKey());
//继续Job
scheduler.resumeJob(detail.getKey());
//暂停Job
scheduler.pauseJob(detail.getKey());
//取消job
scheduler.unscheduleJob(trigger.getKey());
//结束任务
scheduler.shutdown();

任务 Job和JobDetail

Job接口实现类

要继承Job接口
execute方法里执行具体的任务方法

public class HelloJob implements Job {

    @Override
    public void execute(JobExecutionContext context) throws JobExecutionException {
        Trigger trigger = context.getTrigger();
        JobDataMap jobDataMap = trigger.getJobDataMap();
        System.out.println(trigger.getKey() + " job... "
                + DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")
                .format(LocalDateTime.now()));
    }

}

JobExecutionContext 接口对象可以获取一些信息,比如获取trigger获取存放的数据JobDataMap
具体方法可以点进去看

JobDetail

新建任务将上面实现类绑定,提供给trigger绑定

//创建任务名和任务组
JobKey jobKey = new JobKey("job1", "group1");
//新建任务
JobDetail job = newJob(HelloJob.class)
        .withIdentity(jobKey)
        //没有trigger关联的时候也存起来
        .storeDurably()
        .build();

或者这样写

//新建任务
JobDetail job = newJob(HelloJob.class)
        .withIdentity("job1", "group1")
        //没有trigger关联的时候也存起来
        .storeDurably()
        .build();

可以在创建JobDetail对象是添加JobDataMap信息

//新建任务
JobDetail job = newJob(HelloJob.class)
        .withIdentity("job1", "group1")
        //JobDataMap 键值对
        .usingJobData("key", "value")
        //没有trigger关联的时候也存起来
        .storeDurably()
        .build();

JobDataMap

usingJobData有很多方法重载
可以在新建JobDetail时新增key value,可以无限链式调用

JobDetail job = newJob(HelloJob.class)
                .withIdentity("job1", "group1")
                .usingJobData("key", "123123123")
                //没有trigger关联的时候也存起来
                .storeDurably()
                .build();

也可以在Trigger里设置

Trigger trigger1 = newTrigger()
        .withIdentity("trigger1", "group1")
        .usingJobData("key","trigger trigger trigger")
        .startNow()
        .withSchedule(
                simpleSchedule()
                        .withIntervalInSeconds(1)
                        //执行多少次
                        .withRepeatCount(20)
                        .repeatForever()
        ).build();

在JobExecutionContext里可以获得

public class HelloJob implements Job {

    @Override
    public void execute(JobExecutionContext context) throws JobExecutionException {
        Trigger trigger = context.getTrigger();
        System.out.println("================start=================");
        JobDataMap detailMap = context.getJobDetail().getJobDataMap();
        String key = detailMap.getString("key");

        JobDataMap triggerJobDataMap = trigger.getJobDataMap();
        String triggerKey = triggerJobDataMap.getString("key");
        System.out.println(key);
        System.out.println(triggerKey);

    }
}

在Job接口实现类中
可以进行get set方法设置进行获取

@Getter
@Setter
@Slf4j
public class HelloJob implements Job {

    private String key;
    private String name;

    @Override
    public void execute(JobExecutionContext context) throws JobExecutionException {
        System.out.println("================start=================");
        System.out.println(key);
        System.out.println(name);
    }

}

不管是JobDetail还是Trigger都能获取,通过这俩key同名就会冲突,随机获取一个对应

JobDetail job = newJob(HelloJob.class)
        .withIdentity("job1", "group1")
        .usingJobData("key", "123123123")
        //没有trigger关联的时候也存起来
        .storeDurably()
        .build();
//触发器
Trigger trigger1 = newTrigger()
        .withIdentity("trigger1", "group1")
        .usingJobData("name", "trigger trigger trigger")
        .startNow()
        .withSchedule(
                simpleSchedule()
                        .withIntervalInSeconds(1)
                        //执行多少次
                        .withRepeatCount(20)
                        .repeatForever()
        ).build();

JobDataMap更新

在Job接口实现类中加注解 @PersistJobDataAfterExecution
然后给对应的值put,不加此注解直接put不会生效

@PersistJobDataAfterExecution
@DisallowConcurrentExecution
@Getter
@Setter
@Slf4j
public class HelloJob implements Job {

    private String key;
    private String name;

    @Override
    public void execute(JobExecutionContext context) throws JobExecutionException {
        System.out.println("================start=================");
        System.out.println(key);
        System.out.println(name);
        context.getJobDetail().getJobDataMap().put("key", key + 1);
    }

}

防止定时任务并发执行

image.png
在Job接口实现类上加个注解就行了

@DisallowConcurrentExecution
public class HelloJob implements Job{
    //...
}

触发器 Trigger

和JobDetail任务进行绑定,由Scheduler进行调度,一个Trigger只能和一个JobDetail绑定,多个Trigger执行同一JobDeTail会保存
配置任务执行的策略,可以配置cron表达式

//触发器
Trigger trigger1 = newTrigger()
        .withIdentity("trigger1", "group1")
        //立即执行
        .startNow()
        .endAt(
                //什么时候结束
                Date.from(LocalDateTime.now().plusSeconds(5).atZone(ZoneId.systemDefault()).toInstant())
        )
        .withSchedule(
                // simple触发器
                simpleSchedule()
                        //执行周期 隔一秒
                        .withIntervalInSeconds(1)
                        //执行多少次
                        //.withRepeatCount(1)
                        .repeatForever()
        ).build();

这样没有指定job的名和组就需要scheduler来绑定

scheduler.scheduleJob(job, trigger1);
Trigger trigger2 = newTrigger()
        .withIdentity("trigger2", "group1")
        //指定执行开始时间
        .startAt(
                Date.from(LocalDateTime.now().plusSeconds(5).atZone(ZoneId.systemDefault()).toInstant())
        )
        //指定job的名字和组
        .forJob("job1", "group1")
        .withSchedule(
                //cron触发器
                // cron表达式
                cronSchedule("* * * * * ?")
        ).build();

指定了job的名和组需要scheduler先保存job,然后在通过trigger执行任务

//添加job
scheduler.addJob(job, false);
scheduler.scheduleJob(trigger2);

优先级

当线程数小于Trigger数时且同一时间执行任务时才有效
数字越大优先级越高

Trigger trigger1 = newTrigger()
                .withIdentity("trigger1", "group1")
                .usingJobData("name", "trigger trigger trigger")
                //设置优先级的值 默认是5
                .withPriority(5)
                .startNow()
                .withSchedule(
                        simpleSchedule()
                                .withIntervalInSeconds(1)
                                //执行多少次
                                .withRepeatCount(20)
                                .repeatForever()
                ).build();

Misfire机制

错过机制
触发器不执行,到了时间本应该触发,结果没有
Simple触发器和Cron触发器都有自己的应对策略

产生原因

  1. 线程数少于Trigger数,只能有一部分Trigger执行
  2. 工作时长大于定时任务的时间间隔

完整demo代码

public static void main(String[] args) {
    try {
        //调度器
        Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();
        scheduler.start();
        JobKey jobKey = new JobKey("job1", "group1");
        //新建任务
        JobDetail job = newJob(HelloJob.class)
                .withIdentity("job1", "group1")
                .usingJobData("key", "value")
                //没有trigger关联的时候也存起来
                .storeDurably()
                .build();
        //触发器
        Trigger trigger1 = newTrigger()
                .withIdentity("trigger1", "group1")
                .startNow()
                .endAt(
                        Date.from(LocalDateTime.now().plusSeconds(5).atZone(ZoneId.systemDefault()).toInstant())
                )
                .withSchedule(
                        simpleSchedule()
                                .withIntervalInSeconds(1)
                                //执行多少次
                                //.withRepeatCount(1)
                                .repeatForever()
                ).build();
        Trigger trigger2 = newTrigger()
                .withIdentity("trigger2", "group1")
                .startAt(
                        Date.from(LocalDateTime.now().plusSeconds(5).atZone(ZoneId.systemDefault()).toInstant())
                )
                //指定job
                .forJob("job1", "group1")
                .withSchedule(
                        // cron表达式
                        cronSchedule("* * * * * ?")
                ).build();
        //添加job 
        scheduler.addJob(job, false);
        //使用调度器调度
        scheduler.scheduleJob(job, trigger1);
        scheduler.scheduleJob(trigger2);
        //主线程睡20秒 让定时任务执行20秒
        Thread.sleep(TimeUnit.SECONDS.toMillis(20));
        scheduler.shutdown();
    } catch (SchedulerException | InterruptedException e) {
        e.printStackTrace();
    }
}

quartz.properties 配置文件

如果工程没有 quartz就会去依赖的jar里去找
文件名就是quartz.properties
组成部分

  1. 调度器属性
  2. 线程池属性
  3. 作业存储位置
  4. 插件配置 ```xml

    Default Properties file for use by StdSchedulerFactory

    to create a Quartz Scheduler Instance, if a different

    properties file is not explicitly specified.

    #

区分调度器实例 所有调度器实例中唯一 只有一个的时候可以不配置

org.quartz.scheduler.instanceId: 111

区分调度器实例 名

org.quartz.scheduler.instanceName: DefaultQuartzScheduler org.quartz.scheduler.rmi.export: false org.quartz.scheduler.rmi.proxy: false org.quartz.scheduler.wrapJobExecutionInUserTransaction: false

org.quartz.threadPool.class: org.quartz.simpl.SimpleThreadPool org.quartz.threadPool.threadCount: 10 org.quartz.threadPool.threadPriority: 5 org.quartz.threadPool.threadsInheritContextClassLoaderOfInitializingThread: true

org.quartz.jobStore.misfireThreshold: 60000

基于内存存储job 也可以配置成基于数据库

org.quartz.jobStore.class: org.quartz.simpl.RAMJobStore

复杂一点的
```java
# Default Properties file for use by StdSchedulerFactory
# to create a Quartz Scheduler Instance, if a different
# properties file is not explicitly specified.
#
# ===========================================================================
# Configure Main Scheduler Properties 调度器属性
# ===========================================================================
org.quartz.scheduler.instanceName: DefaultQuartzScheduler
org.quartz.scheduler.instanceid:AUTO
org.quartz.scheduler.rmi.export: false
org.quartz.scheduler.rmi.proxy: false
org.quartz.scheduler.wrapJobExecutionInUserTransaction: false
# ===========================================================================  
# Configure ThreadPool 线程池属性  
# ===========================================================================
#线程池的实现类(一般使用SimpleThreadPool即可满足几乎所有用户的需求)
org.quartz.threadPool.class: org.quartz.simpl.SimpleThreadPool
#指定线程数,至少为1(无默认值)(一般设置为1-100直接的整数合适)
org.quartz.threadPool.threadCount: 10
#设置线程的优先级(最大为java.lang.Thread.MAX_PRIORITY 10,最小为Thread.MIN_PRIORITY 1,默认为5)
org.quartz.threadPool.threadPriority: 5
#设置SimpleThreadPool的一些属性
#设置是否为守护线程
#org.quartz.threadpool.makethreadsdaemons = false
#org.quartz.threadPool.threadsInheritContextClassLoaderOfInitializingThread: true
#org.quartz.threadpool.threadsinheritgroupofinitializingthread=false
#线程前缀默认值是:[Scheduler Name]_Worker
#org.quartz.threadpool.threadnameprefix=swhJobThead;
# 配置全局监听(TriggerListener,JobListener) 则应用程序可以接收和执行 预定的事件通知
# ===========================================================================
# Configuring a Global TriggerListener 配置全局的Trigger监听器
# MyTriggerListenerClass 类必须有一个无参数的构造函数,和 属性的set方法,目前2.2.x只支持原始数据类型的值(包括字符串)
# ===========================================================================
#org.quartz.triggerListener.NAME.class = com.swh.MyTriggerListenerClass
#org.quartz.triggerListener.NAME.propName = propValue
#org.quartz.triggerListener.NAME.prop2Name = prop2Value
# ===========================================================================
# Configuring a Global JobListener 配置全局的Job监听器
# MyJobListenerClass 类必须有一个无参数的构造函数,和 属性的set方法,目前2.2.x只支持原始数据类型的值(包括字符串)
# ===========================================================================
#org.quartz.jobListener.NAME.class = com.swh.MyJobListenerClass
#org.quartz.jobListener.NAME.propName = propValue
#org.quartz.jobListener.NAME.prop2Name = prop2Value
# ===========================================================================  
# Configure JobStore 存储调度信息(工作,触发器和日历等)
# ===========================================================================
# 信息保存时间 默认值60秒
org.quartz.jobStore.misfireThreshold: 60000
#保存job和Trigger的状态信息到内存中的类
org.quartz.jobStore.class: org.quartz.simpl.RAMJobStore
# ===========================================================================  
# Configure SchedulerPlugins 插件属性 配置
# ===========================================================================
# 自定义插件  
#org.quartz.plugin.NAME.class = com.swh.MyPluginClass
#org.quartz.plugin.NAME.propName = propValue
#org.quartz.plugin.NAME.prop2Name = prop2Value
#配置trigger执行历史日志(可以看到类的文档和参数列表)
org.quartz.plugin.triggHistory.class = org.quartz.plugins.history.LoggingTriggerHistoryPlugin  
org.quartz.plugin.triggHistory.triggerFiredMessage = Trigger {1}.{0} fired job {6}.{5} at: {4, date, HH:mm:ss MM/dd/yyyy}  
org.quartz.plugin.triggHistory.triggerCompleteMessage = Trigger {1}.{0} completed firing job {6}.{5} at {4, date, HH:mm:ss MM/dd/yyyy} with resulting trigger instruction code: {9}  
#配置job调度插件  quartz_jobs(jobs and triggers内容)的XML文档  
#加载 Job 和 Trigger 信息的类   (1.8之前用:org.quartz.plugins.xml.JobInitializationPlugin)
org.quartz.plugin.jobInitializer.class = org.quartz.plugins.xml.XMLSchedulingDataProcessorPlugin
#指定存放调度器(Job 和 Trigger)信息的xml文件,默认是classpath下quartz_jobs.xml
org.quartz.plugin.jobInitializer.fileNames = my_quartz_job2.xml  
#org.quartz.plugin.jobInitializer.overWriteExistingJobs = false  
org.quartz.plugin.jobInitializer.failOnFileNotFound = true  
#自动扫描任务单并发现改动的时间间隔,单位为秒
org.quartz.plugin.jobInitializer.scanInterval = 10
#覆盖任务调度器中同名的jobDetail,避免只修改了CronExpression所造成的不能重新生效情况
org.quartz.plugin.jobInitializer.wrapInUserTransaction = false
# ===========================================================================  
# Sample configuration of ShutdownHookPlugin  ShutdownHookPlugin插件的配置样例
# ===========================================================================
#org.quartz.plugin.shutdownhook.class = \org.quartz.plugins.management.ShutdownHookPlugin
#org.quartz.plugin.shutdownhook.cleanShutdown = true
#
# Configure RMI Settings 远程服务调用配置
#
#如果你想quartz-scheduler出口本身通过RMI作为服务器,然后设置“出口”标志true(默认值为false)。
#org.quartz.scheduler.rmi.export = false
#主机上rmi注册表(默认值localhost)
#org.quartz.scheduler.rmi.registryhost = localhost
#注册监听端口号(默认值1099)
#org.quartz.scheduler.rmi.registryport = 1099
#创建rmi注册,false/never:如果你已经有一个在运行或不想进行创建注册
# true/as_needed:第一次尝试使用现有的注册,然后再回来进行创建
# always:先进行创建一个注册,然后再使用回来使用注册
#org.quartz.scheduler.rmi.createregistry = never
#Quartz Scheduler服务端端口,默认是随机分配RMI注册表
#org.quartz.scheduler.rmi.serverport = 1098
#true:链接远程服务调度(客户端),这个也要指定registryhost和registryport,默认为false
# 如果export和proxy同时指定为true,则export的设置将被忽略
#org.quartz.scheduler.rmi.proxy = false