在电商系统中会常常遇到这样一种场景,就是商品的定时上下架功能,总不能每次都手动执行吧,这个时候咱们首先想到的就是利用定时任务来实现这个功能。html
目前实现定时任务主要有如下几种方式:java
JDK自带 :JDK自带的Timer以及JDK1.5+ 新增的ScheduledExecutorService;web
第三方框架 :使用 Quartz、elastic-job、xxl-job 等开源第三方定时任务框架,适合分布式项目应用。该方式的缺点是配置复杂。spring
Spring :使用 Spring 提供的一个注解 @Schedule
,开发简单,使用比较方便。apache
本文博主主要向你们介绍Quartz框架和Spring定时任务的使用。json
Quartz 是一个彻底由 Java 编写的开源做业调度框架,为在 Java 应用程序中进行做业调度提供了简单却强大的机制。框架
Quartz 能够与 J2EE 与 J2SE 应用程序相结合也能够单独使用。maven
Quartz 容许程序开发人员根据时间的间隔来调度做业。分布式
Quartz 实现了做业和触发器的多对多的关系,还能把多个做业与不一样的触发器关联。ide
在正式学习使用Quartz以前,咱们须要了解几个有关Quartz的核心概念,方便咱们后面学习
Job 表示一个工做,要执行的具体内容。此接口中只有一个方法,以下:
void execute(JobExecutionContext context) // context是重要的上下文,能够访问到关联的JobDetail对象和本次触发的Trigger对象,以及在此之上设定的数据。
JobDetail 表示一个具体的可执行的调度程序,Job 是这个可执行程调度程序所要执行的内容,另外 JobDetail 还包含了这个任务调度的方案和策略。
Trigger 表明一个调度参数的配置,何时去调。
Scheduler 表明一个调度容器,一个调度容器中能够注册多个 JobDetail 和 Trigger。当 Trigger 与 JobDetail 组合,就能够被 Scheduler 容器调度了。
1、建立一个SpringBoot项目,pom.xml配置以下
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.songguoliang</groupId> <artifactId>spring-boot-quartz</artifactId> <version>1.0-SNAPSHOT</version> <name>spring-boot-quartz</name> <description>Spring Boot使用Quartz定时任务</description> <!-- Spring Boot启动器父类 --> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.0.1.RELEASE</version> <relativePath/> <!-- lookup parent from repository --> </parent> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> <java.version>1.8</java.version> </properties> <dependencies> <!-- Spring Boot web启动器 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!-- quartz --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-quartz</artifactId> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> </project>
2、建立一个Job(Job里面是要执行的具体内容)
package com.example.quartz; import org.quartz.Job; import org.quartz.JobExecutionContext; import org.quartz.JobExecutionException; import org.quartz.SchedulerException; import java.time.LocalDateTime; public class TestJob implements Job { @Override public void execute(JobExecutionContext context) throws JobExecutionException { // 经过context获取trigger中的数据 Object tv1 = context.getTrigger().getJobDataMap().get("t1"); Object tv2 = context.getTrigger().getJobDataMap().get("t2"); // 经过context获取JobDetail中的数据 Object jv1 = context.getJobDetail().getJobDataMap().get("j1"); Object jv2 = context.getJobDetail().getJobDataMap().get("j2"); Object sv = null; try { sv = context.getScheduler().getContext().get("skey"); } catch (SchedulerException e) { e.printStackTrace(); } System.out.println(tv1+":"+tv2); System.out.println(jv1+":"+jv2); System.out.println(sv); System.out.println("date:"+ LocalDateTime.now()); } }
3、执行Job
package com.example.quartz; import org.quartz.*; import org.quartz.impl.StdSchedulerFactory; public class QuartzTest { public static void main(String[] args) { try { //建立一个scheduler Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler(); //向scheduler中put值 scheduler.getContext().put("skey", "svalue"); //建立一个Trigger Trigger trigger = TriggerBuilder.newTrigger() //给该Trigger起一个id .withIdentity("trigger1") //以Key-Value形式关联数据 .usingJobData("t1", "tv1") //每3秒触发一次,无限循环 .withSchedule(SimpleScheduleBuilder.simpleSchedule().withIntervalInSeconds(3) .repeatForever()).build(); trigger.getJobDataMap().put("t2","tv2"); //建立一个JobDetail JobDetail jobDetail = JobBuilder.newJob(TestJob.class) //给该JobDetail起一个id .withIdentity("myJob", "myGroup") .usingJobData("j1", "jv1") .build(); jobDetail.getJobDataMap().put("j2", "jv2"); //注册trigger并启动scheduler scheduler.scheduleJob(jobDetail, trigger); scheduler.start(); //若是想要中止这个Job,能够调用shutdown方法 //scheduler.shutdown(); } catch (SchedulerException e) { e.printStackTrace(); } } }
控制台输出
10:46:54.075 [main] INFO org.quartz.impl.StdSchedulerFactory - Using default implementation for ThreadExecutor 10:46:54.079 [main] INFO org.quartz.simpl.SimpleThreadPool - Job execution threads will use class loader of thread: main 10:46:54.089 [main] INFO org.quartz.core.SchedulerSignalerImpl - Initialized Scheduler Signaller of type: class org.quartz.core.SchedulerSignalerImpl 10:46:54.089 [main] INFO org.quartz.core.QuartzScheduler - Quartz Scheduler v.2.3.2 created. 10:46:54.090 [main] INFO org.quartz.simpl.RAMJobStore - RAMJobStore initialized. 10:46:54.091 [main] INFO org.quartz.core.QuartzScheduler - Scheduler meta-data: Quartz Scheduler (v2.3.2) 'DefaultQuartzScheduler' with instanceId 'NON_CLUSTERED' Scheduler class: 'org.quartz.core.QuartzScheduler' - running locally. NOT STARTED. Currently in standby mode. Number of jobs executed: 0 Using thread pool 'org.quartz.simpl.SimpleThreadPool' - with 10 threads. Using job-store 'org.quartz.simpl.RAMJobStore' - which does not support persistence. and is not clustered. 10:46:54.091 [main] INFO org.quartz.impl.StdSchedulerFactory - Quartz scheduler 'DefaultQuartzScheduler' initialized from default resource file in Quartz package: 'quartz.properties' 10:46:54.091 [main] INFO org.quartz.impl.StdSchedulerFactory - Quartz scheduler version: 2.3.2 10:46:54.104 [main] INFO org.quartz.core.QuartzScheduler - Scheduler DefaultQuartzScheduler_$_NON_CLUSTERED started. 10:46:54.104 [DefaultQuartzScheduler_QuartzSchedulerThread] DEBUG org.quartz.core.QuartzSchedulerThread - batch acquisition of 1 triggers 10:46:54.106 [DefaultQuartzScheduler_QuartzSchedulerThread] DEBUG org.quartz.simpl.PropertySettingJobFactory - Producing instance of Job 'myGroup.myJob', class=com.example.quartz.TestJob 10:46:54.110 [DefaultQuartzScheduler_QuartzSchedulerThread] DEBUG org.quartz.core.QuartzSchedulerThread - batch acquisition of 1 triggers 10:46:54.110 [DefaultQuartzScheduler_Worker-1] DEBUG org.quartz.core.JobRunShell - Calling execute on job myGroup.myJob tv1:tv2 jv1:jv2 svalue date:2020-12-19T10:46:54.144 10:46:57.092 [DefaultQuartzScheduler_QuartzSchedulerThread] DEBUG org.quartz.simpl.PropertySettingJobFactory - Producing instance of Job 'myGroup.myJob', class=com.example.quartz.TestJob 10:46:57.092 [DefaultQuartzScheduler_QuartzSchedulerThread] DEBUG org.quartz.core.QuartzSchedulerThread - batch acquisition of 1 triggers 10:46:57.092 [DefaultQuartzScheduler_Worker-2] DEBUG org.quartz.core.JobRunShell - Calling execute on job myGroup.myJob tv1:tv2 jv1:jv2 svalue date:2020-12-19T10:46:57.092 10:47:00.101 [DefaultQuartzScheduler_QuartzSchedulerThread] DEBUG org.quartz.simpl.PropertySettingJobFactory - Producing instance of Job 'myGroup.myJob', class=com.example.quartz.TestJob 10:47:00.101 [DefaultQuartzScheduler_QuartzSchedulerThread] DEBUG org.quartz.core.QuartzSchedulerThread - batch acquisition of 1 triggers 10:47:00.101 [DefaultQuartzScheduler_Worker-3] DEBUG org.quartz.core.JobRunShell - Calling execute on job myGroup.myJob tv1:tv2 jv1:jv2 svalue date:2020-12-19T10:47:00.101 10:47:03.096 [DefaultQuartzScheduler_QuartzSchedulerThread] DEBUG org.quartz.simpl.PropertySettingJobFactory - Producing instance of Job 'myGroup.myJob', class=com.example.quartz.TestJob 10:47:03.096 [DefaultQuartzScheduler_QuartzSchedulerThread] DEBUG org.quartz.core.QuartzSchedulerThread - batch acquisition of 1 triggers 10:47:03.096 [DefaultQuartzScheduler_Worker-4] DEBUG org.quartz.core.JobRunShell - Calling execute on job myGroup.myJob tv1:tv2 jv1:jv2 svalue date:2020-12-19T10:47:03.096
从输出结果咱们能够看到此Job每隔3秒执行一次
有关概念
一、Job
job的一个 trigger 被触发后(稍后会讲到),execute() 方法会被 scheduler 的一个工做线程调用;传递给 execute() 方法的 JobExecutionContext 对象中保存着该 job 运行时的一些信息 ,执行 job 的 scheduler 的引用,触发 job 的 trigger 的引用,JobDetail 对象引用,以及一些其它信息。
二、JobDetail :
JobDetail 对象是在将 job 加入 scheduler 时,由客户端程序(你的程序)建立的。它包含 job 的各类属性设置,以及用于存储 job 实例状态信息的 JobDataMap
三、Trigger:
Trigger 用于触发 Job 的执行。当你准备调度一个 job 时,你建立一个 Trigger 的实例,而后设置调度相关的属性。Trigger 也有一个相关联的 JobDataMap,用于给 Job 传递一些触发相关的参数。Quartz 自带了各类不一样类型的 Trigger,最经常使用的主要是 SimpleTrigger 和 CronTrigger。SimpleTrigger 主要用于一次性执行的 Job(只在某个特定的时间点执行一次),或者 Job 在特定的时间点执行,重复执行 N 次,每次执行间隔T个时间单位。CronTrigger 在基于日历的调度上很是有用,如“每一个星期五的正午”,或者“每个月的第十天的上午 10:15”等。
在定义一个Job时,咱们须要实现Job接口,该接口只有一个execute
方法。
从上一节的案例中咱们能够发现,咱们经过Scheduler去执行Job,咱们传给scheduler一个JobDetail实例,由于咱们在建立JobDetail时,将要执行的job的类名传给了JobDetail,因此scheduler就知道了要执行何种类型的job。(这里利用了Java中的反射建立实例对象)每次当scheduler执行job时,在调用其execute(…)方法以前会建立该类的一个新的实例;执行完毕,对该实例的引用就被丢弃了,实例会被垃圾回收;这种执行策略带来的一个后果是,job必须有一个无参的构造函数(当使用默认的JobFactory时);另外一个后果是,在job类中,不该该定义有状态的数据属性,由于在job的屡次执行中,这些属性的值不会保留。
那么咱们该如何给Job配置相关属性呢?答案就是经过JobDetail
JobDataMap实现了Map接口,能够存放键值对数据,在Job执行的时候,咱们就能够经过JobExecutionContext获取到JobDataMap中的数据,以下
JobDetail jobDetail = JobBuilder.newJob(TestJob.class) .withIdentity("myJob", "myGroup") .usingJobData("j1", "jv1") .usingJobData("j2","jv2") .build();
在job的执行过程当中,能够从JobDataMap中取出数据,以下示例:
Object jv1 = context.getJobDetail().getJobDataMap().get("j1");
固然,若是你但愿实现属性的自动注入,那么你可使用下面的方法
package com.example.quartz; import org.quartz.*; import org.quartz.impl.StdSchedulerFactory; public class QuartzTest2 { public static void main(String[] args) { try { Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler(); Trigger trigger = TriggerBuilder.newTrigger() .withIdentity("trigger1") .usingJobData("t1", "tv1") .withSchedule(SimpleScheduleBuilder.simpleSchedule().withIntervalInSeconds(3) .repeatForever()) .build(); JobDetail jobDetail = JobBuilder.newJob(TestJob2.class) .withIdentity("jd") .usingJobData("name", "张三") .usingJobData("age", 12) .build(); scheduler.scheduleJob(jobDetail, trigger); scheduler.start(); } catch (SchedulerException e) { e.printStackTrace(); } } }
package com.example.quartz; import org.quartz.*; public class TestJob2 implements Job { private String name; private Integer age; public String getName() { return name; } public void setName(String name) { this.name = name; } public Integer getAge() { return age; } public void setAge(Integer age) { this.age = age; } @Override public void execute(JobExecutionContext context) throws JobExecutionException { JobKey jobKey = context.getJobDetail().getKey(); JobDataMap jobDataMap = context.getJobDetail().getJobDataMap(); System.out.println("name:" + name + "age:" +age); } }
给Job类加上get和set方法(属性名称要和JobDataMap中的key相同),那么JobDataMap中的值就是自动注入到Job中,不须要手动获取
Trigger 用于触发 Job 的执行。当你准备调度一个 job 时,你建立一个 Trigger 的实例,而后设置调度相关的属性。全部类型的trigger都有TriggerKey这个属性,表示trigger的身份;除此以外,trigger还有不少其它的公共属性。这些属性,在构建trigger的时候能够经过TriggerBuilder设置。
若是你的trigger不少(或者Quartz线程池的工做线程太少),Quartz可能没有足够的资源同时触发全部的trigger;这种状况下,你可能但愿控制哪些trigger优先使用Quartz的工做线程,要达到该目的,能够在trigger上设置priority属性。好比,你有N个trigger须要同时触发,但只有Z个工做线程,优先级最高的Z个trigger会被首先触发。若是没有为trigger设置优先级,trigger使用默认优先级,值为5;priority属性的值能够是任意整数,正数、负数均可以。
注意:只有同时触发的trigger之间才会比较优先级。10:59触发的trigger老是在11:00触发的trigger以前执行。
注意:若是trigger是可恢复的,在恢复后再调度时,优先级与原trigger是同样的。
trigger还有一个重要的属性misfire;若是scheduler关闭了,或者Quartz线程池中没有可用的线程来执行job,此时持久性的trigger就会错过(miss)其触发时间,即错过触发(misfire)。不一样类型的trigger,有不一样的misfire机制。它们默认都使用“智能机制(smart policy)”,即根据trigger的类型和配置动态调整行为
SimpleTrigger简单点说,就是在具体的时间点执行一次,或者在具体的时间点执行,而且以指定的间隔重复执行若干次。相似于闹钟,你定了一个周末早晨7点的闹钟,这个闹钟会在周末早上7点准时响起。闹钟还有个功能就是过5分钟以后再响一次,这对应着指定的间隔重复执行若干次。
一、指定时间开始触发,不重复:
SimpleTrigger trigger = (SimpleTrigger) TriggerBuilder.newTrigger() .withIdentity("st1", "group1") .startAt(new Date()) // 从当前时间开始执行一次,不重复 .build();
二、指定时间触发,每隔2秒执行一次,重复5次:
SimpleTrigger trigger = (SimpleTrigger) TriggerBuilder.newTrigger() .withIdentity("st2", "group1") .startAt(new Date()) .withSchedule(SimpleScheduleBuilder.simpleSchedule() .withIntervalInSeconds(2) // 2秒 .withRepeatCount(5)// 5次 ) .build();
三、1分钟之后开始触发,仅执行一次:
long time = 1 * 60 * 1000; Date now = new Date(); SimpleTrigger trigger = (SimpleTrigger) TriggerBuilder.newTrigger() .withIdentity("st3", "group1") .startAt(new Date(now.getTime() + time)) .build();
四、当即触发,每隔2秒钟执行一次,直到2020-12-19 13:20:00
String dateStr="2020-12-19 13:20:00"; String pattern="yyyy-MM-dd HH:mm:ss"; SimpleDateFormat dateFormat=new SimpleDateFormat(pattern); Date date = dateFormat.parse(dateStr); SimpleTrigger trigger = (SimpleTrigger) TriggerBuilder.newTrigger() .withIdentity("st4", "group1") .withSchedule(SimpleScheduleBuilder.simpleSchedule() .withIntervalInSeconds(2) .repeatForever()) .endAt(date) .build();
五、在13:00触发,而后每2小时重复一次:
String dateStr="2020-12-19 13:00:00"; String pattern="yyyy-MM-dd HH:mm:ss"; SimpleDateFormat dateFormat=new SimpleDateFormat(pattern); Date date = dateFormat.parse(dateStr); SimpleTrigger trigger = (SimpleTrigger) TriggerBuilder.newTrigger() .withIdentity("st2", "group1") .withSchedule(SimpleScheduleBuilder.simpleSchedule() .withIntervalInHours(2) .repeatForever()) .build();
misfire:被错过的执行任务策略
SimpleTrigger trigger = (SimpleTrigger) TriggerBuilder.newTrigger() .withIdentity("st6") .withSchedule( SimpleScheduleBuilder.simpleSchedule() .withIntervalInMinutes(5) .repeatForever() .withMisfireHandlingInstructionNextWithExistingCount() ) .build();
CronTrigger一般比Simple Trigger更有用,若是你须要在指定日期执行某项任务,使用CronTrigger就很是方便,好比若是你想在每个月的15号给会员发放优惠券,或者每周五中午12点统计用户本周使用产品时长。
cron
表达式是一个字符串,该字符串由 6 个空格分为 7 个域,每个域表明一个时间含义。 一般定义 “年” 的部分能够省略,实际经常使用的 Cron 表达式由前 6 部分组成。格式以下
[秒] [分] [时] [日] [月] [周] [年] Seconds Minutes Hours Day-of-Month Month Day-of-Week Year (optional field)
域 | 是否必填 | 值以及范围 | 通配符 |
---|---|---|---|
秒 | 是 | 0-59 | , - * / |
分 | 是 | 0-59 | , - * / |
时 | 是 | 0-23 | , - * / |
日 | 是 | 1-31 | , - * ? / L W |
月 | 是 | 1-12 或 JAN-DEC | , - * / |
周 | 是 | 1-7 或 SUN-SAT | , - * ? / L # |
年 | 否 | 1970-2099 | , - * / |
须要说明的是,Cron 表达式中,“周” 是从周日开始计算的。“周” 域上的 1
表示的是周日,7
表示周六。
天天晚上12点触发任务:
0 0 0 * * ?
每隔 1 分钟执行一次:
0 */1 * * * ?
每个月 1 号凌晨 1 点执行一次:
0 0 1 1 * ?
每个月最后一天 23 点执行一次:
0 0 23 L * ?
每周周六凌晨 3 点实行一次:
0 0 3 ? * L
在24分,30分执行一次:
0 24,30 * * * ?
是否是有点没看懂,不要紧,咱们可使用Cron表达式生成器帮助咱们生成Cron表达式
SimpleTrigger trigger = (SimpleTrigger) TriggerBuilder.newTrigger() .withSchedule(CronScheduleBuilder.cronSchedule("0 0/2 8-17 * * ?")) .build();
不少时候咱们都须要为系统创建一个定时任务来帮咱们作一些事情,SpringBoot 已经帮咱们实现好了一个,咱们只须要直接使用便可
1、引入依赖
<dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter</artifactId> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> </dependencies>
2、开启注解
在 SpringBoot 中咱们只须要在启动类上加上@EnableScheduling
即可以启动定时任务了。
@SpringBootApplication @EnableScheduling public class TaskApplication { public static void main(String[] args) { SpringApplication.run(DemoApplication.class, args); } }
3、建立scheduled task
import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Component; import java.text.SimpleDateFormat; import java.util.Date; import java.util.concurrent.TimeUnit; /** * @author wugongzi */ @Component public class ScheduledTasks { private static final Logger log = LoggerFactory.getLogger(ScheduledTasks.class); private static final SimpleDateFormat dateFormat = new SimpleDateFormat("HH:mm:ss"); /** * fixedRate:固定速率执行。每5秒执行一次。 */ @Scheduled(fixedRate = 5000) public void reportCurrentTimeWithFixedRate() { log.info("Current Thread : {}", Thread.currentThread().getName()); log.info("Fixed Rate Task : The time is now {}", dateFormat.format(new Date())); } /** * fixedDelay:固定延迟执行。距离上一次调用成功后2秒才执。 */ @Scheduled(fixedDelay = 2000) public void reportCurrentTimeWithFixedDelay() { try { TimeUnit.SECONDS.sleep(3); log.info("Fixed Delay Task : The time is now {}", dateFormat.format(new Date())); } catch (InterruptedException e) { e.printStackTrace(); } } /** * initialDelay:初始延迟。任务的第一次执行将延迟5秒,而后将以5秒的固定间隔执行。 */ @Scheduled(initialDelay = 5000, fixedRate = 5000) public void reportCurrentTimeWithInitialDelay() { log.info("Fixed Rate Task with Initial Delay : The time is now {}", dateFormat.format(new Date())); } /** * cron:使用Cron表达式。 每分钟的1,2秒运行 */ @Scheduled(cron = "1-2 * * * * ? ") public void reportCurrentTimeWithCronExpression() { log.info("Cron Expression: The time is now {}", dateFormat.format(new Date())); } }
启动项目即可以看到效果。