Scheduled 定时任务器: 是 Spring3.0 以后自带的一个定时任务器。
<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>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.5.10.RELEASE</version>
</parent>
<groupId>com.bjsxt</groupId>
<artifactId>25-spring-boot-scheduled</artifactId>
<version>0.0.1-SNAPSHOT</version>
<properties>
<java.version>1.7</java.version>
<thymeleaf.version>3.0.2.RELEASE</thymeleaf.version>
<thymeleaf-layout-dialect.version>2.0.4</thymeleaf-layout-dialect.versi
on>
</properties>
<dependencies>
<!-- springBoot 的启动器 -->
<dependency><groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- springBoot 的启动器 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<!-- 添加 Scheduled 坐标 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context-support</artifactId>
</dependency>
</dependencies>
</project>
@Component
public class ScheduledDemo {
/**
* 定时任务方法
* @Scheduled:设置定时任务
* cron 属性: cron 表达式。 定时任务触发是时间的一个字符串表达形式
*/
@Scheduled(cron="0/2 * * * * ?")
public void scheduledMethod(){
System.out.println("定时器被触发"+new Date());
}
}
Cron 表达式是一个字符串, 分为 6 或 7 个域, 每一个域代表一个含义
Cron 有如下两种语法格式:
(1) Seconds Minutes Hours Day Month Week Year
(2) Seconds Minutes Hours Day Month Week
一、 结构
corn 从左到右(用空格隔开) : 秒 分 小时 月份中的日期 月份 星期中的日期 年份
二、 各字段的含义
位置 | 时间域名 | 允许值 | 允许的特殊字符 |
---|---|---|---|
1 | 秒 | 0-59 | , - * / |
2 | 分钟 | 0-59 | , - * / |
3 | 小时 | 0-23 | , - * / |
4 | 日 | 1-31 | , - * / L W C |
5 | 月 | 1-12 | , - * / |
6 | 星期 | 1-7 | , - * ? / L C # |
7 | 年(可选) | 1970-2099 | , - * / |
Cron 表达式的时间字段除允许设置数值外, 还可使用一些特殊的字符, 提供列表、 范围、 通配符等功能, 细说如下:●星号(*): 可用在所有字段中, 表示对应时间域的每一个时刻, 例如, *在分钟字段时, 表示“每分钟”;
●问号(?) : 该字符只在日期和星期字段中使用, 它通常指定为“无意义的值”, 相当于占位符;
●减号(-): 表达一个范围, 如在小时字段中使用“10-12”, 则表示从 10 到 12 点, 即 10,11,12;
●逗号(,): 表达一个列表值, 如在星期字段中使用“MON,WED,FRI”, 则表示星期一, 星期三和星期
五;
●斜杠(/): x/y 表达一个等步长序列, x 为起始值, y 为增量步长值。 如在分钟字段中使用 0/15, 则
表示为 0,15,30 和 45 秒, 而 5/15 在分钟字段中表示 5,20,35,50, 你也可以使用*/y, 它等同于 0/y;
●L: 该字符只在日期和星期字段中使用, 代表“Last”的意思, 但它在两个字段中意思不同。 L 在日期
字段中, 表示这个月份的最后一天, 如一月的 31 号, 非闰年二月的 28 号; 如果 L 用在星期中, 则表示星
期六, 等同于 7。 但是, 如果 L 出现在星期字段里, 而且在前面有一个数值 X, 则表示“这个月的最后 X 天”,
例如, 6L 表示该月的最后星期五;
●W: 该字符只能出现在日期字段里, 是对前导日期的修饰, 表示离该日期最近的工作日。 例如 15W
表示离该月 15 号最近的工作日, 如果该月 15 号是星期六, 则匹配 14 号星期五; 如果 15 日是星期日,
则匹配 16 号星期一; 如果 15 号是星期二, 那结果就是 15 号星期二。 但必须注意关联的匹配日期不能够
跨月, 如你指定 1W, 如果 1 号是星期六, 结果匹配的是 3 号星期一, 而非上个月最后的那天。 W 字符串
只能指定单一日期, 而不能指定日期范围;
●LW 组合: 在日期字段可以组合使用 LW, 它的意思是当月的最后一个工作日;
●井号(#): 该字符只能在星期字段中使用, 表示当月某个工作日。 如 6#3 表示当月的第三个星期五(6
表示星期五, #3 表示当前的第三个), 而 4#5 表示当月的第五个星期三, 假设当月没有第五个星期三,
忽略不触发;
● C: 该字符只在日期和星期字段中使用, 代表“Calendar”的意思。 它的意思是计划所关联的日期,
如果日期没有被关联, 则相当于日历中所有日期。 例如 5C 在日期字段中就相当于日历 5 日以后的第一天。
1C 在星期字段中相当于星期日后的第一天。
Cron 表达式对特殊字符的大小写不敏感, 对代表星期的缩写英文大小写也不敏感。
例子:
@Scheduled(cron = “0 0 1 1 1 ?”)//每年一月的一号的 1:00:00 执行一次
@Scheduled(cron = “0 0 1 1 1,6 ?”) //一月和六月的一号的 1:00:00 执行一次
@Scheduled(cron = “0 0 1 1 1,4,7,10 ?”) //每个季度的第一个月的一号的 1:00:00 执行一次
@Scheduled(cron = “0 0 1 1 * ?”)//每月一号 1:00:00 执行一次
@Scheduled(cron=“0 0 1 * * *”) //每天凌晨 1 点执行一次
“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秒触发任务
Quartz内部架构
在规模方面,Quartz跟大多数开源框架类似。大约有300个Java类和接口,并被组织到12个包中。这可以和Apache Struts把大约325个类和接口以及组织到11个包中相比。尽管规模几乎不会用来作为衡量框架质量的一个特性,但这里的关键是quarts内含很多功能,这些功能和特性集是否成为、或者应该成为评判一个开源或非开源框架质量的因素。
Quartz调度器
Quartz框架的核心是调度器。调度器负责管理Quartz应用运行时环境。调度器不是靠自己做所有的工作,而是依赖框架内一些非常重要的部件。Quartz不仅仅是线程和线程管理。为确保可伸缩性,Quartz采用了基于多线程的架构。
启动时,框架初始化一套worker线程,这套线程被调度器用来执行预定的作业。这就是Quartz怎样能并发运行多个作业的原理。Quartz依赖一套松耦合的线程池管理部件来管理线程环境。本文中,我们会多次提到线程池管理,但Quartz里面的每个对象是可配置的或者是可定制的。所以,例如,如果你想要插进自己线程池管理设施,我猜你一定能!
作业和触发器
Quartz设计者做了一个设计选择来从调度分离开作业。Quartz中的触发器用来告诉调度程序作业什么时候触发。框架提供了一把触发器类型,但两个最常用的是SimpleTrigger和CronTrigger。SimpleTrigger为需要简单打火调度而设计。
典型地,如果你需要在给定的时间和重复次数或者两次打火之间等待的秒数打火一个作业,那么SimpleTrigger适合你。另一方面,如果你有许多复杂的作业调度,那么或许需要CronTrigger。
错过触发(misfire)
trigger还有一个重要的属性misfire;如果scheduler关闭了,或者Quartz线程池中没有可用的线程来执行job,此时持久性的trigger就会错过(miss)其触发时间,即错过触发(misfire)。不同类型的trigger,有不同的misfire机制。它们默认都使用“智能机制(smart policy)”,即根据trigger的类型和配置动态调整行为。当scheduler启动的时候,查询所有错过触发(misfire)的持久性trigger。然后根据它们各自的misfire机制更新trigger的信息。当你在项目中使用Quartz时,你应该对各种类型的trigger的misfire机制都比较熟悉,这些misfire机制在JavaDoc中有说明。关于misfire机制的细节,会在讲到具体的trigger时作介绍。
Maven 无法下载 Quartz 依赖,去官网下载 http://www.quartz-scheduler.org/downloads/
Quartz 官方手册:https://www.w3cschool.cn/quartz_doc/
需要注意
spring3.1以下的版本必须使用 quartz1.x 系列,3.1以上的版本才支持quartz2.x,不然会出错。
1、原因:spring 对 quartz 的支持实现,org.springframework.scheduling.quartz.CronTriggerBean 继承了 org.quartz.CronTrigger
在 quartz1.x 系列中 org.quartz.CronTrigger 是个类,而在quartz2.x系列中org.quartz.CronTrigger变成了接口,从而造成无法用spring的方式配置quartz的触发器。
1、做个小笔记,Quartz 对任务调度的领域问题进行了高度的抽象
1、提出了调度器、任务、触发器 这3个核心的概念
2、Job:是一个接口,只有一个方法 void execute(JobExecutionContext context),实现该接口定义运行任务,JobExecutionContext类提供了调度上下文的各种信息。
Job运行时的信息保存在JobDatMap 实例中
3、JobDetail:Quartz 在每次执行Job时,都重新创建一个Job实例,所以它不直接接受一个Job的实例,相反它接受一个Job实现类,以便运行时通过
nweInstance() 的反射机制实例化Job。因此需要通过一个类来描述Job的实现类及其他相关的静态信息,如Job名字、描述、关联监听器等信息,
JobDetail 承担了这一角色。
通过该类的构造函数可以更具体的了解它的作用:JobDetail( java.lang.String name , java.lang.String group , java.lang.Class jobClass)
该构造函数要去指定Job的实现类、以及任务在Scheduler 中的组名 和 Job 名称
4、Trigger:是一个类,描述触发了Job执行的时间触发规则。主要有 SimpleTrigger 和 CronTrigger 这两个子类
当仅需 触发 一次 或者 固定时间间隔周期执行,SimpleTrigger 是最合适的选择
CronTrigger 则可以通过 Cron 表达式定义出各种复杂时间规则的调度方案,如 每天早上9:00执行,周一、周三中午12:00执行等。
5、Calendar:org.quartz.Calendar 和 java.util.Calendar 不同,它是一些日历特定时间点的集合
(可以简单的将org.quartz.Calendar 看作 java.util.Calendar 的集合,java.util.Calendar 代表一个日历时间点)
一个 Trigger 可以和多个Calendar 关联,以便排除或包含某些时间点。
假设,我们安排每周星期一早上 10:00 执行任务,但是如果碰到法定的节日,任务则不执行,这时就需要Trigger触发机制
的基础上使用Calendar 进行定点排除,针对不同时间段类型,Quartz在org.quartz.impl.calendar包下提供了若干个Calendar的实现类,
如AnnualCalendar、MonthlyCalendar、WeeklyCalendar分别针对每年、每月和每周进行定义。
6、Scheduler:代表一个Quartz的独立运行容器,Trigger 和 JobDetail 可以注册到 Scheduler 中,两者在Schedule 中拥有各自
的组及名称,组及名称是Scheduler 查找定位容器中某一对象的依据,Trigger的组及名称必须唯一,JobDetail的组合名称也必须唯一(但可以和Trigger的组和名称相同,因为它们是不同类型的)
Scheduler 定义了多个接口方法,允许外部通过组及名称访问和控制容器中Trigger 和 JobDetail。
Scheduler 可以将Trigger绑定到 某一个 JobDetail 中,当Trigger触发时,对应的Job就被执行,一个Job可以对应多个Trigger,但一个Trigger只能对应一个Job。
可以通过SchedulerFactory 创建一个Scheduler实例。Scheduler拥有一个SchedulerContext,它类似于ServletContext,保存着Scheduler上下文信息,Job和Trigger都可以访问SchedulerContext内的信息。SchedulerContext内部通过一个Map,以键值对的方式维护这些上下文数据,SchedulerContext为保存和获取数据提供了多个put() 和 getXxx() 的方法。可以通过Scheduler.getContext() 获取对应的SchedulerContext实例。
7、ThreadPool:Scheduler 使用一个线程池作为任务运行的基础设施,任务通过共享线程池中的线程提高运行效率
8、Job有一个StatefulJob子接口,代表有状态的任务,改接口是一个没有方法的标签接口,其目的是让quartz知道任务的类型,以便采用不同的执行方案
1) job - 任务 - 你要做什么事?
2) Trigger - 触发器 - 你什么时候去做?
3) Scheduler - 任务调度 - 你什么时候需要去做什么事?
基本使用
<!-- Quartz 坐标 --><dependency>
<groupId>org.quartz-scheduler</groupId>
<artifactId>quartz</artifactId>
<version>2.2.1</version>
</dependency>
/**
* 定义任务类
* * *
/
public class QuartzDemo implements Job {
/**
* 任务被触发时所执行的方法
*/
public void execute(JobExecutionContext arg0) throws
JobExecutionException {
System.out.println("Execute...."+new Date());
}
}
public class QuartzMain {
public static void main(String[] args) throws Exception {
// 1.创建 Job 对象: 你要做什么事?
JobDetail job = JobBuilder.newJob(QuartzDemo.class).build();
/**
* 简单的 trigger 触发时间: 通过 Quartz 提供一个方法来完成简单的重复
调用 cron
* Trigger: 按照 Cron 的表达式来给定触发的时间
*/
// 2.创建 Trigger 对象: 在什么时间做?
/*Trigger trigger =TriggerBuilder.newTrigger().withSchedule(SimpleScheduleBuilder.repeatSe
condlyForever())
.build();*/
Trigger trigger =
TriggerBuilder.newTrigger().withSchedule(CronScheduleBuilder.cronSchedu
le("0/2 * * * * ?"))
.build();
// 3.创建 Scheduler 对象: 在什么时间做什么事?
Scheduler scheduler =
StdSchedulerFactory.getDefaultScheduler();
scheduler.scheduleJob(job, trigger);
//启动
scheduler.start();
}
}
<dependencies>
<!-- springBoot 的启动器 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- springBoot 的启动器 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<!-- Quartz 坐标 -->
<dependency>
<groupId>org.quartz-scheduler</groupId>
<artifactId>quartz</artifactId>
<version>2.2.1</version>
<exclusions>
<exclusion>
<artifactId>slf4j-api</artifactId>
<groupId>org.slf4j</groupId>
</exclusion>
</exclusions>
</dependency>
<!-- 添加 Scheduled 坐标 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context-support</artifactId>
</dependency>
<!-- Sprng tx 坐标 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
</dependency>
</dependencies>
/**
* Quartz 配置类
* * *
/
@Configuration
public class QuartzConfig {
/**
* 1.创建 Job 对象
*/
@Bean
public JobDetailFactoryBean jobDetailFactoryBean(){
JobDetailFactoryBean factory = new JobDetailFactoryBean();
//关联我们自己的 Job 类
factory.setJobClass(QuartzDemo.class);
return factory;
} /
**
* 2.创建 Trigger 对象
* 简单的 Trigger
*/
@Bean
public SimpleTriggerFactoryBean
simpleTriggerFactoryBean(JobDetailFactoryBean jobDetailFactoryBean){
SimpleTriggerFactoryBean factory = new
SimpleTriggerFactoryBean();
//关联 JobDetail 对象
factory.setJobDetail(jobDetailFactoryBean.getObject());
//该参数表示一个执行的毫秒数
factory.setRepeatInterval(2000);
//重复次数
factory.setRepeatCount(5);
return factory;
}/**
* 3.创建 Scheduler 对象
*/
@Bean
public SchedulerFactoryBean
schedulerFactoryBean(SimpleTriggerFactoryBean simpleTriggerFactoryBean){
SchedulerFactoryBean factory = new SchedulerFactoryBean();
//关联 trigger
factory.setTriggers(simpleTriggerFactoryBean.getObject());
return factory;
}
}
@Component("myAdaptableJobFactory")
public class MyAdaptableJobFactory extends AdaptableJobFactory {
//AutowireCapableBeanFactory 可以将一个对象添加到 SpringIOC 容器中,
并且完成该对象注入
@Autowired
private AutowireCapableBeanFactory autowireCapableBeanFactory;
/**
* 该方法需要将实例化的任务对象手动的添加到 springIOC 容器中并且完成对
象的注入
*/
@Override
protected Object createJobInstance(TriggerFiredBundle bundle)
throws Exception {
Object obj = super.createJobInstance(bundle);
//将 obj 对象添加 Spring IOC 容器中, 并完成注入
this.autowireCapableBeanFactory.autowireBean(obj);
return obj;
}
}
@Configuration
public class QuartzConfig {
/**
* 1.创建 Job 对象
*/
@Bean
public JobDetailFactoryBean jobDetailFactoryBean(){
JobDetailFactoryBean factory = new JobDetailFactoryBean();//关联我们自己的 Job 类
factory.setJobClass(QuartzDemo.class);
return factory;
} /
**
* 2.创建 Trigger 对象
* 简单的 Trigger
*/
/*@Bean
public SimpleTriggerFactoryBean
simpleTriggerFactoryBean(JobDetailFactoryBean jobDetailFactoryBean){
SimpleTriggerFactoryBean factory = new
SimpleTriggerFactoryBean();
//关联 JobDetail 对象
factory.setJobDetail(jobDetailFactoryBean.getObject());
//该参数表示一个执行的毫秒数
factory.setRepeatInterval(2000);
//重复次数
factory.setRepeatCount(5);
return factory;
}*/
/**
* Cron Trigger
*/
@Bean
public CronTriggerFactoryBean
cronTriggerFactoryBean(JobDetailFactoryBean jobDetailFactoryBean){
CronTriggerFactoryBean factory = new CronTriggerFactoryBean();
factory.setJobDetail(jobDetailFactoryBean.getObject());
//设置触发时间
factory.setCronExpression("0/2 * * * * ?");
return factory;
} /
**
* 3.创建 Scheduler 对象
*/
@Bean
public SchedulerFactoryBean
schedulerFactoryBean(CronTriggerFactoryBean
cronTriggerFactoryBean,MyAdaptableJobFactory myAdaptableJobFactory){
SchedulerFactoryBean factory = new SchedulerFactoryBean();//关联 trigger
factory.setTriggers(cronTriggerFactoryBean.getObject());
factory.setJobFactory(myAdaptableJobFactory);
return factory;
}
}