一、JAVA常见的几种定时任务比较
- Timer:jdk自带的java.util.Timer类,这个类允许你调度一个java.util.TimerTask任务。使用这种方式可以让程序按照某一个频度执行,但不能在指定时间运行,一般很少使用,主要用于非Spring项目简单的任务调度。
- Spring
Task:Spring3.0以后自带的Task,可以将它看成一个轻量级的Quartz,使用起来比Quartz简单很多,在Spring应用中,直接使用@Scheduled注解即可,但对于集群项目比较麻烦,需要避免集群环境下任务被多次调用的情况,而且不能动态维护,任务启动以后不能修改、暂停等。 - Quartz:好用的第三方任务调度工具,可谓是企业级应用系统任务调度工具的老大。可以方便的在集群下使用、可以动态增加、删除、暂停等维护任务,动态定时任务更加灵活。而且,和Spring
Boot集成非常方便。
二、本博客将用Quartz来实现定时任务
Quartz的简单介绍:
核心概念:调度器、任务和触发器
Job
- 是一个接口,有一个方法void execute(),可以通过实现该接口来定义需要执行的任务 JobDetail:Quartz
每次执行job时,都重新创建一个Job实例,会接收一个Job实现类,以便运行的时候通过newInstance()的反射调用机制去实例化
Job.JobDetail
- 是用来描述Job实现类以及相关静态信息,比如任务在scheduler中的组名等信息
Trigger
- 描述触发Job执行的时间触发规则实现类SimpleTrigger和CronTrigger
- 可以通过crom表达式定义出各种复杂的调度方案
Calendar
- 是一些日历特定时间的集合。一个Trigger可以和多个 calendar关联,比如每周一早上10:00执行任务,法定假日不执行, 则可以通过calendar进行定点排除
Scheduler
- 代表一个 Quartz的独立运行容器。Trigger和JobDetail可以注册到Scheduler中。Scheduler可以将Trigger绑定到某一JobDetail上,这样当Trigger被触发时,对应的Job就会执行。一个Job可以对应多个Trigger,但一个Trigger只能对应一个Job.
三、案例实现,代码中有详细注解
- 首先创建springboot项目:
2.创建一个QuartzConfig类,并添加@Configuration注解:
package com.example.demo;
import org.quartz.*;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class QuartzConfig {
@Bean
public JobDetail testQuartz1() {
return JobBuilder.newJob(QuartzTest1.class) //QuartzTest1是我们的业务类
.withIdentity("quartzTest1") //给该JobDetail起一个id
.storeDurably() //即使没有Trigger关联时,也不需要删除该JobDetail
//每个JobDetail内都有一个Map,包含了关联到这个Job的数据,在Job类中可以通过context获取
.usingJobData("msg", "Hello Quartz")//关联键值对
.build();
}
@Bean
public Trigger testQuartzTrigger1() {
//5秒执行一次
SimpleScheduleBuilder scheduleBuilder = SimpleScheduleBuilder.simpleSchedule()
.withIntervalInSeconds(5)
.repeatForever();
return TriggerBuilder.newTrigger()
.forJob(testQuartz1()) //关联上述的JobDetail
.withIdentity("quartzTest1") //给Trigger起个名字
.withSchedule(scheduleBuilder)
.build();
}
@Bean
public JobDetail testQuartz2() {
return JobBuilder.newJob(QuartzTest2.class)
.withIdentity("quartzTest2") //给该JobDetail起一个id
.storeDurably() //即使没有Trigger关联时,也不需要删除该JobDetail
.build();
}
@Bean
public Trigger testQuartzTrigger2() {
//cron方式,每隔5秒执行一次
CronScheduleBuilder cronScheduleBuilder = CronScheduleBuilder.cronSchedule("*/5 * * * * ?");
return TriggerBuilder.newTrigger()
.forJob(testQuartz2()) //关联上述的JobDetail
.withIdentity("quartzTest2") //给Trigger起个名字
.withSchedule(cronScheduleBuilder)
.build();
}
}
3.这里为了展示固定间隔和cron表达式,创建了两个任务
QuartzTest1 :
package com.example.demo;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.springframework.scheduling.quartz.QuartzJobBean;
import java.text.SimpleDateFormat;
import java.util.Date;
public class QuartzTest1 extends QuartzJobBean {
@Override
protected void executeInternal(JobExecutionContext jobExecutionContext) throws JobExecutionException {
SimpleDateFormat sdf=new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
System.out.println("TestQuartz01----" + sdf.format(new Date()));
}
}
QuartzTest2 :
package com.example.demo;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.springframework.scheduling.quartz.QuartzJobBean;
import java.text.SimpleDateFormat;
import java.util.Date;
public class QuartzTest2 extends QuartzJobBean {
@Override
protected void executeInternal(JobExecutionContext jobExecutionContext) throws JobExecutionException {
SimpleDateFormat sdf=new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
System.out.println("TestQuartz02----" + sdf.format(new Date()));
}
}
testTask1使用的固定间隔方式,testTask2使用的是cron表达式方式。
4.测试
四、cron表达式
注:下面内容主要参考Quartz的文档,对于Spring Task基本都适用。
cron表达式是由7个域组成的字符串,它们描述了任务计划的各个细节,这些域用空格分隔,每个域代表的含义如下:
- Seconds(秒)
- Minutes(分)
- Hours(时)
- Day-of-Month(日)
- Month(月)
- Day-of-Week(星期) Year(可选字段)(年)
示例:0 0 10 ? * WED表示每个星期三的10:00:00
说明:下面描述中,XX域则表示cron表达式相应的位置,如秒域表示cron中第1个值,日域则表示cron表达式第4个值等等。
-
月份简称:JAN,FEB,MAR,APR,MAY,JUN,JUL,AUG,SEP,OCT,NOV和DEC。
-
星期简称:SUN,MON,TUE,WED,THU,FRI和SAT。其中,1表示SUN。
-
,:用来分割在域上指定的多个值。如:MON,WED,FRI在星期域里表示星期一、星期三、星期五。
-
/:用于指定增量值。如分钟上使用0/15,表示从零开始,每隔15分钟,等价于0,15,30,45。如分钟上使用3/15,表示从第3分钟开始,每隔
-
15分钟,等价于3,18,33,48,x/y中x表示开始值,y表示步长。
-
:表示匹配该域的任意值。如秒上使用表示每秒触发一次。
-
-:表示指定一个范围,如分钟域上10-13,表示10分、11分、12分、13分。
-
?:表示不关心的域,可用于日和周两个域上,主要用来解决日和周两个域的冲突。和类似,区别在于关心域,只是域的值可以任意,?则表示对该域不关心,不需要看该域的内容,直接忽略。
-
L:表示最后,是单词last的首字母,可用于日和周两个域上,用在日和周上含义不同:
日域上表示月份中日期的最后一天,如一月的第31天、非闰年二月的第28天。
周域上单独使用仅表示7或SAT,即仅表示周六。但是如果跟在其他值后,如6L或FRIL则表示该月中最后一个星期五。
L还可以指定偏移量,如日域指定L-3,表示该月倒数第3天。当使用L时其值尽量不要指定列表或范围,以免令人困惑。 -
W:用于日域,表示距离指定日最近的星期几(周一至周五中的一个),如:日域上值为15W则表示距离本月第15日最近的工作日。
-
#:用于周域,表示该月的第n个星期几。如:周域值为6#3或FRI#3表示该月的第3个星期五。
附上:常用表达式示例
0 0 10,14,16 * * ?每天上午10点、下午两点、下午4点整触发
0 0/30 9-17 * * ? 每天朝九晚五内每隔半小时触发
0 15 10 ? * MON-FRI 周一至周五的上午10:15触发
0 0/5 * * * ?每5分钟触发
10 0/5 * * * ?每隔5分钟的第10秒触发(即10:00:10、10:05:10、10:10:10等)
30 * * * * ? 每半分钟触发
30 10 * * * ? 每小时的10分30秒触发
30 10 1 * * ? 每天1点10分30秒触发
30 10 1 20 * ? 每月20号1点10分30秒触发
30 10 1 20 10 ? * 每年10月20号1点10分30秒触发
30 10 1 20 10 ? 2011 2011年10月20号1点10分30秒触发
30 10 1 ? 10 * 2011 2011年10月每天1点10分30秒触发
30 10 1 ? 10 SUN 2011 2011年10月每周日1点10分30秒触发
15,30,45 * * * * ? 每15秒,30秒,45秒时触发
15-45 * * * * ? 15到45秒内,每秒都触发
15/5 * * * * ? 每分钟的每15秒开始触发,每隔5秒触发一次
15-30/5 * * * * ? 每分钟的15秒到30秒之间开始触发,每隔5秒触发一次
0 0/3 * * * ? 每小时的第0分0秒开始,每三分钟触发一次
0 15 10 ? * MON-FRI 星期一到星期五的10点15分0秒触发
0 15 10 L * ? 每个月最后一天的10点15分0秒触发
0 15 10 LW * ? 每个月最后一个工作日的10点15分0秒触发
0 15 10 ? * 5L 每个月最后一个星期四的10点15分0秒触发
0 15 10 ? * 5#3 每个月第三周的星期四的10点15分0秒触发
参考博客:
https://blog.csdn.net/gnail_oug/article/details/80825302
https://blog.csdn.net/qq_27046951/article/details/82805259