定时任务调度-quartz

Quartz

image-20190403152100120

image-20190403152115254

Job

​ Job是一个接口,只有一个方法

void execute(JobExecutionContext ctx) throws JobExecutionException;

​ 开发者实现该接口定义需要执行的任务。这个方法的参数JobExecutionContext ctx提供了调度上下文中的信息。

​ 下面的demo片段如下:

public class HelloJob implements Job {

    /**
     * 编写对应的业务逻辑
     */
    @Override
    public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {
        System.out.println();
        Date date = new Date();
        SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        System.out.println("Current Exec Time Is: " + simpleDateFormat.format(date));
        System.out.println("hello World");

    }
}
public class HelloSchedule {
    public static void main(String[] args) throws SchedulerException {
        //jobdetail 绑定信息job
        JobDetail jobDetail = JobBuilder.newJob(HelloJob.class).withIdentity("myJob", "group1").build();
        //创建一个Trigger实例,定义该Job立即执行,并且每隔两秒钟重复执行一次,知道永远
        Trigger trigger = TriggerBuilder.newTrigger().
            withIdentity("myTrigger", "group1").
            startNow()
            .withSchedule(SimpleScheduleBuilder.simpleSchedule().withIntervalInSeconds(2).repeatForever()
                         ).build();
        //创建Scheduler实例,通过factory的模式创建
        StdSchedulerFactory stdSchedulerFactory = new StdSchedulerFactory();
        Scheduler scheduler = stdSchedulerFactory.getScheduler();
        scheduler.scheduleJob(jobDetail, trigger);
        scheduler.start();
    }
}

生命周期

​ 每次调度器执行job时,他在调用execute方法前会创建一个新的job实例,当调用完成后,关联的job对象实例会被释放,释放的实例会被垃圾回收机制回收

​ JobDetail为Job实例提供了许多设置属性,以及JobDataMap成员变量属性,它用来存储特定Job实例的状态信息,调度器需要借助JobDetail对象来添加Job实例。

JobDetail

​ Quartz在每次执行job时,都重新创建一个job实例,所以,quartz不是接受一个job实例,而是一个Job实现类,在运行时,通过newInstance()的反射调用机制实例化Job。因此,需要通过一个对象来描述Job的实现类及一些静态信息,如Job名称,组名,关联监听器等信息。

  • name:任务属性
  • group:组
  • jobClass:任务的实现类
  • jobDataMap:用来传参的作用

我们需要在job运行的时候,动态的判断,并且写日志,记录哪一个job执行,通过jobDetail来获取

JOBDataMap

  • 在进行任务调度时,jobDataMap存储在JobExecutionContext中,非常方便获取
  • JobDataMap可以用来装载任何可序列化的数据对象,当job实例对象被执行时,这些参数对象会传递给它。
  • jobDataMap实现了JDK的Map接口,并且添加了一些非常方便的方法用来存取基本数据类型。
public static void main(String[] args) throws SchedulerException {
    //jobdetail 绑定信息job
    JobDetail jobDetail = JobBuilder.newJob(HelloJob.class).withIdentity("myJob", "group1").usingJobData("message", "helloMyJob1").
            usingJobData("helloJob", "wow").build();
    System.out.println(jobDetail.getKey());
    //创建一个Trigger实例,定义该Job立即执行,并且每隔两秒钟重复执行一次,知道永远
    Trigger trigger = TriggerBuilder.newTrigger().
            withIdentity("myTrigger", "group1").
            startNow()
            .withSchedule(SimpleScheduleBuilder.simpleSchedule().withIntervalInSeconds(2).repeatForever()
            ).usingJobData("myTrigger", "triggerString").usingJobData("helloTrigger", "wow").build();
    //创建Scheduler实例,通过factory的模式创建
    StdSchedulerFactory stdSchedulerFactory = new StdSchedulerFactory();
    Scheduler scheduler = stdSchedulerFactory.getScheduler();
    scheduler.scheduleJob(jobDetail, trigger);
    scheduler.start();

}

从Map中直接获取

public class HelloJob implements Job {
    @Override
    public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {
        System.out.println();
        Date date = new Date();
        SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        System.out.println("Current Exec Time Is: " + simpleDateFormat.format(date));
        JobKey jobKey = jobExecutionContext.getJobDetail().getKey();
        System.out.println(jobKey.getName() + jobKey.getGroup());
        JobDataMap jobDataMap = jobExecutionContext.getJobDetail().getJobDataMap();
        for (String s : jobDataMap.keySet()) {
            System.out.println(s);
            System.out.println(jobDataMap.get(s));
        }
        TriggerKey triggerKey = jobExecutionContext.getTrigger().getKey();
        System.out.println(triggerKey.getName() + triggerKey.getGroup());
    }
}

获取所有信息

for (String s : mergedJobDataMap.keySet()) {
    System.out.println(s);
    System.out.println(mergedJobDataMap.get(s));
}

​ 但是上述方式,如果trigger中的key和job的key冲突,那么trigger中的key会覆盖job中的key

通过Setter获取

​ Job实现类中添加Setter方法,对应JobDataMap的键值(Quartz框架默认的JobFactory实现类在初始化job实例对象时会自动的调用这些setter方法)

  • public class HelloJob implements Job {
        private String message;
    
        public String getMessage() {
            return message;
        }
    
        public void setMessage(String message) {
            this.message = message;
        }
    
        /**
     * 编写对应的业务逻辑
     */
        @Override
        public void execute(JobExecutionContext jobExecutionContext) {
            System.out.println(getMessage());
        }
    }
    

Trigger

​ Trigger描述触发job执行的时间规则。有SimpleTrigger和CronTrigger这两个子类。SimpleTrigger主要用于触发一次或者固定频率触发;CronTrigger则可以通过Cron表达式定义各种复杂的触发时间,如每周一上午10点执行、每月的最后一个星期日执行。这个跟Linux中的cron定时任务是一样的,具体的定义规则,自己google吧。

image-20190403172449328

JobKey

​ 表示job实例的标识,触发器触发时,该指定的job实例会被执行

StartTime

​ 表示触发器首次被触发的时间,比如说五一才开始执行

endTime

​ 表示触发器不再被执行的时间

Trigger trigger = TriggerBuilder.newTrigger().
        withIdentity("myTrigger", "group1").
        //当前时间开始
        //startNow()
        //开始时间
                startAt(new Date()).
        //结束时间
                endAt(calendar.getTime())
        .withSchedule(SimpleScheduleBuilder.simpleSchedule().withIntervalInSeconds(2).repeatForever()
        ).usingJobData("myTrigger", "triggerString").usingJobData("helloTrigger", "wow").build();

SimpleTrigger

​ SimpleTrigger可以满足的调度需求是:在具体的时间点执行一次,或者在具体的时间点执行,并且以指定的间隔重复执行若干次。比如,你有一个trigger,你可以设置它在2015年1月13日的上午11:23:54准时触发,或者在这个时间点触发,并且每隔2秒触发一次,一共重复5次。

Calendar

​ 有时每月的最后一个星期日执行任务,但是如果遇到中秋节、春节就不执行,这种需求就用到了Calendar。也就是说,从tigger中排除若干个特殊日期,使用的就是Calendar。一个trigger可以和多Calendar配合使用。

CronTrigger

​ 能够实现更复杂的业务逻辑,比如说每周五执行任务

表达式

​ CronTrigger配置完整格式为:

序号说明是否必填允许范围允许的通配符
10-59,- * /
20-23, - * /
31-31, - * /
41-12, - * ? / L W
51-12 or JAN-DEC, - * /
61-12 or SUN-SAT, - * ? / L #
7empty 或 1970-2099, - * /

通配符说明

  • *
    • 表示所有值. 例如:在分的字段上设置 “*”,表示每一分钟都会触发。
  • ?
    • 表示不指定值。使用的场景为不需要关心当前设置这个字段的值。例如:要在每月的10号触发一个操作,但不关心是周几,所以需要周位置的那个字段设置为"?" 具体设置为 0 0 0 10 * ?- 表示区间。例如 在小时上设置 “10-12”,表示 10,11,12点都会触发。
  • ,
    • 表示指定多个值,例如在周字段上设置 “MON,WED,FRI” 表示周一,周三和周五触发
  • /
    • 用于递增触发。如在秒上面设置"5/15" 表示从5秒开始,每增15秒触发(5,20,35,50)。 在月字段上设置’1/3’所示每月1号开始,每隔三天触发一次。
  • L
    • 表示最后的意思。在日字段设置上,表示当月的最后一天(依据当前月份,如果是二月还会依据是否是润年[leap]), 在周字段上表示星期六,相当于"7"或"SAT"。如果在"L"前加上数字,则表示该数据的最后一个。例如在周字段上设置"6L"这样的格式,则表示“本月最后一个星期五"
  • W
    • 表示离指定日期的最近那个工作日(周一至周五). 例如在日字段上设置"15W",表示离每月15号最近的那个工作日触发。如果15号正好是周六,则找最近的周五(14号)触发, 如果15号是周未,则找最近的下周一(16号)触发.如果15号正好在工作日(周一至周五),则就在该天触发。如果指定格式为 “1W”,它则表示每月1号往后最近的工作日触发。如果1号正是周六,则将在3号下周一触发。(注,“W"前只能设置具体的数字,不允许区间”-").
  • #
    • 序号(表示每月的第几个周几),例如在周字段上设置"6#3"表示在每月的第三个周六.注意如果指定"#5",正好第五周没有周六,则不会触发该配置(用在母亲节和父亲节再合适不过了) ;
  • 小提示:
    • LW可以一组合使用。如果在日字段上设置LW,则表示在本月的最后一个工作日触发;
    • 周字段的设置,若使用英文字母是不区分大小写的,即MONmon相同;

举例

​ 0 0 12 * * ? 每天12点触发

​ 0 15 10 ? * * 每天10点15分触发

​ 0 15 10 * * ? 每天10点15分触发

​ 0 15 10 * * ? * 每天10点15分触发

​ 0 15 10 * * ? 2005 2005年每天10点15分触发

​ 0 * 14 * * ? 每天下午的 2点到2点59分每分触发

​ 0 0/5 14 * * ? 每天下午的 2点到2点59分(整点开始,每隔5分触发)

​ 0 0/5 14,18 * * ? 每天下午的 2点到2点59分、18点到18点59分(整点开始,每隔5分触发)

​ 0 0-5 14 * * ? 每天下午的 2点到2点05分每分触发

​ 0 10,44 14 ? 3 WED 3月分每周三下午的 2点10分和2点44分触发

​ 0 15 10 ? * MON-FRI 从周一到周五每天上午的10点15分触发

​ 0 15 10 15 * ? 每月15号上午10点15分触发

​ 0 15 10 L * ? 每月最后一天的10点15分触发

​ 0 15 10 ? * 6L 每月最后一周的星期五的10点15分触发

​ 0 15 10 ? * 6L 2002-2005 从2002年到2005年每月最后一周的星期五的10点15分触发

​ 0 15 10 ? * 6#3 每月的第三周的星期五开始触发

​ 0 0 12 1/5 * ? 每月的第一个中午开始每隔5天触发一次

​ 0 11 11 11 11 ? 每年的11月11号 11点11分触发(光棍节)

Scheduler调度器

​ Scheduler代表一个Quartz独立运行的容器,Trigger和JobDetail可以注册到Scheduler中,两者在Scheduler中拥有各自的组和名称, 组和名称是在Scheduler中定位一个对象的依据。

Scheduler Factory

image-20190409140553426

StdSchedulerFactory

  • 使用一组参数(java.util.Properties)来创建和初始化Quartz调度器

  • 配置参数一般存储在quartz.properties

  • 调用getScheduler方法就能创建和初始化调度器对象

API

最近一次执行的时间

Date date = scheduler.scheduleJob(build, cronTrigger);
System.out.println(date);

挂起

scheduler.standby();

挂起之后执行

scheduler.start()

停止

​ 这种方式停止之后scheduler再次启动是无法启动的,boolean 参数如果是true则等待当前持有的任务运行完成之后再停止

scheduler.shutdown(boolean b )

判断是否停止

boolean b = scheduler.isShutDown();

QuartzProperties

文档的位置和顺序

默认加载的是工厂目录下的QuartzProperties如果没有则读取jar包中的

quartz.properties

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-delMscNv-1574768763273)(https://ws4.sinaimg.cn/large/006tNc79gy1g1wfcic0clj31880n6q8w.jpg)]

组成部分

调度器属性

org.quartz.scheduler.instanceName :属性用来区分特定的调度器实例,可以按照功能用途来给调度器命名。

org.quartz.scheduler.instanceId:属性和前者一样,也运行任何字符串,但是这个值必须是在所有调度器实例中唯一,尤其在一个集群当中,作为集群的唯一key,如果我们想quartz帮我们生成这个值的话,可以设置auto

xml配置方式添加Quartz

MethodInvokingJobDetailFactoryBean

调用 myBean的printMessage方法

@Component("myBean")
public class MyBean {
    public void printMessage() {
        System.out.println("myBean executes");
    }
}
<bean id="simpleJobDetail"
      class="org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean">
    <property name="targetObject" ref="myBean"/>
    <property name="targetMethod" value="printMessage"/>
</bean>

JobDetailFactoryBean

需要给作业传递数据,想要更加灵活的方式使用这种方式

  <bean id="firstComplexJobDetail"
          class="org.springframework.scheduling.quartz.JobDetailFactoryBean">
        <property name="jobClass"
                  value="com.zzjson.scheduler.FirstScheduleJob"/>
        <property name="jobDataMap">
            <map>
                <entry key="anotherBean" value-ref="anotherBean"/>
            </map>
        </property>
        <property name="Durability" value="true"/>
    </bean>
<bean id="firstTimer" class="com.gupaoedu.timer.XmlTimer"></bean>

<bean id="firstTask"
      class="org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean">
    <property name="targetObject">
        <ref bean="firstTimer" />
    </property>
    <property name="targetMethod">
        <value>execute</value>
    </property>
</bean>

<bean id="firstTrigger" class="org.springframework.scheduling.quartz.CronTriggerFactoryBean">
    <property name="jobDetail">
        <ref bean="firstTask" />
    </property>
    <property name="cronExpression">
        <value>0/5 * * * * ?</value>
    </property>
</bean>

<bean id="startQuertz" lazy-init="false" autowire="no"
      class="org.springframework.scheduling.quartz.SchedulerFactoryBean">
    <property name="triggers">
        <list>
            <ref bean="firstTrigger" />
        </list>
    </property>
</bean>

Annotation方式添加

利用反射机制实现定时任务的动态调度

实现 ApplicationContextAware

扫描类

定时任务重复启动的问题

1、定时任务重复启动的问题,避免注解和xml方式重复使用

2、同一时间触发多个任务,有些任务不执行的问题,使用多线程异步解决 @Async

quartz.properties

# 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

Git 地址:https://gitee.com/zzy0_0/learning/tree/master/quartz-parent

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值