Quartz官網 http://www.quartz-scheduler.org/ 特點
- 強大的排程功能
- 靈活的應用方式
- 分布式和叢集能力
主要用到的設計模式
- Builder 模式
- Factory 模式
- 元件模式
- 鍊式寫法
三個核心概念
- 排程器
- 任務
- 觸發器
Quartz 體系結構
JobDetail
scheduler
trigger
-SimpleTrigger
-CronTrigger
重要組成
Job
JobDetail
JobBuilder
JobStore
Trigger
TriggerBuilder
ThreadPool
Scheduler
Calendar
監聽器
JobListener
TriggerListener
SchedulerListener
Quartz 執行個體
依賴
<dependency>
<groupId>org.quartz-scheduler</groupId>
<artifactId>quartz</artifactId>
<version>2.3.2</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-simple</artifactId>
<version>1.7.25</version>
<scope>compile</scope>
</dependency>
定義任務 MyJob.java
package timer;
import org.quartz.Job;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import java.text.SimpleDateFormat;
import java.util.Date;
public class MyJob implements Job {
@Override
public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
System.out.println(dateFormat.format(new Date()));
}
}
排程任務 QuartzDemo.java
package timer;
import org.quartz.*;
import org.quartz.impl.StdSchedulerFactory;
public class QuartzDemo {
public static void main(String[] args) throws SchedulerException {
// 建立JobDetail
JobDetail jobDetail = JobBuilder
.newJob(MyJob.class)
.withIdentity("myJob", "group1")
.build();
// 每2s執行一次,無限循環
SimpleScheduleBuilder scheduleBuilder = SimpleScheduleBuilder
.simpleSchedule()
.withIntervalInSeconds(2)
.repeatForever();
// 建立Trigger
Trigger trigger = TriggerBuilder.newTrigger()
.withIdentity("myTrigger", "group1")
.startNow()
.withSchedule(scheduleBuilder)
.build();
// 通過工廠方法建立Scheduler執行個體
SchedulerFactory factory = new StdSchedulerFactory();
Scheduler scheduler = factory.getScheduler();
scheduler.start();
scheduler.scheduleJob(jobDetail, trigger);
}
}
Job 和 JobDetail
1、Job 源碼:
package org.quartz;
public interface Job {
void execute(JobExecutionContext context)
throws JobExecutionException;
}
2、Job 的生命周期:
每次排程器執行 Job 時,調用 execute 方法前會建立一個新的 Job 執行個體
調用完成後,關聯的 Job 對象執行個體會被釋放,釋放的執行個體會被垃圾回收機制回收
3、JobDetail:
JobDetail 為 Job 執行個體提供了許多設定屬性,以及 JobDataMap 成員變量屬性,
它用來存儲特定 Job 執行個體的狀态資訊,排程器需要借助 JobDetail 對象來添加 Job 執行個體
4、JobDetail 重要屬性
name
group 預設值DEFAULT
jobClass
jobDataMap
// 建立JobDetail
JobDetail jobDetail = JobBuilder
.newJob(MyJob.class)
.withIdentity("myJob", "group1")
.build();
// 列印jobDetail屬性
System.out.println(jobDetail.getKey().getName()); // myJob
System.out.println(jobDetail.getKey().getGroup()); // group1
System.out.println(jobDetail.getJobClass().getName()); // timer.MyJob
JobExecutionContext & JobDataMap
1、JobExecutionContext:
Scheduler 給 Job 傳遞參數
2、JobDataMap:
可以裝載任何可序列化的資料對象
實作了 Map 接口
設定 JobDataMap 部分代碼
// 建立JobDetail
JobDetail jobDetail = JobBuilder
.newJob(MyJob.class)
.withIdentity("myJob", "group1")
.usingJobData("name", "jobDetail")
.build();
// 每2s執行一次,無限循環
SimpleScheduleBuilder scheduleBuilder = SimpleScheduleBuilder
.simpleSchedule()
.withIntervalInSeconds(2)
.repeatForever();
// 建立Trigger
Trigger trigger = TriggerBuilder.newTrigger()
.withIdentity("myTrigger", "group1")
.startNow()
.usingJobData("name", "trigger")
.withSchedule(scheduleBuilder)
.build();
擷取 JobDataMap
方法一:直接從 JobDataMap 對象中擷取
package timer;
import org.quartz.*;
import java.text.SimpleDateFormat;
import java.util.Date;
public class MyJob implements Job {
@Override
public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {
// 擷取 jobkey
JobKey jobKey = jobExecutionContext.getJobDetail().getKey();
System.out.println(jobKey.getName()); // myJob
System.out.println(jobKey.getGroup()); // group1
// 擷取JobDetail的DataMap
JobDataMap jobDetailDataMap = jobExecutionContext.getJobDetail().getJobDataMap();
System.out.println(jobDetailDataMap.getString("name"));
// jobDetail
// 擷取Trigger的DataMap
JobDataMap triggerDataMap = jobExecutionContext.getTrigger().getJobDataMap();
System.out.println(triggerDataMap.getString("name"));
// trigger
// 擷取合并後的DataMap
JobDataMap dataMap = jobExecutionContext.getMergedJobDataMap();
System.out.println(dataMap.getString("name"));
// trigger
}
}
方法二:定義同名變量擷取
package timer;
import org.quartz.Job;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
public class MyJob implements Job {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {
System.out.println(this.name);
}
}
Trigger
Trigger 是觸發器,用來告訴排程程式作業什麼時候觸發
觸發器通用屬性
JobKey: Job 執行個體的辨別,觸發器被觸發時,指定的 job 執行個體會執行
StartTime:觸發器的時間表首次被觸發的時間,類型是 Java.util.Date
EndTime:觸發器不再被觸發的時間 Java.util.Date
設定部分代碼
// 擷取3秒後的時間
Date startDate = new Date();
startDate.setTime(startDate.getTime() + 3000);
// 擷取6秒後的時間
Date endDate = new Date();
endDate.setTime(endDate.getTime() + 3000);
// 建立Trigger
Trigger trigger = TriggerBuilder.newTrigger()
.withIdentity("myTrigger", "group1")
.startAt(startDate)
.endAt(endDate)
.usingJobData("name", "trigger")
.withSchedule(scheduleBuilder)
.build();
Job 中擷取 Trigger 資料
package timer;
import org.quartz.Job;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.quartz.Trigger;
import java.text.SimpleDateFormat;
public class MyJob implements Job {
@Override
public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
Trigger trigger = jobExecutionContext.getTrigger();
// 擷取開始時間和結束時間
System.out.println(dateFormat.format(trigger.getStartTime()));
System.out.println(dateFormat.format(trigger.getEndTime()));
// 擷取JobKey
System.out.println(trigger.getJobKey().getName());
System.out.println(trigger.getJobKey().getGroup());
}
}
SimpleTrigger
指定時間段内執行一次作業任務
或者在指定的時間間隔内多次執行作業任務
package timer;
import org.quartz.Job;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import java.text.SimpleDateFormat;
import java.util.Date;
public class MyJob implements Job {
@Override
public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
System.out.println(dateFormat.format(new Date()));
}
}
示例 1
// 擷取3秒後的時間
Date startDate = new Date();
startDate.setTime(startDate.getTime() + 3000);
// 3秒鐘之後執行一次
Trigger trigger = TriggerBuilder.newTrigger()
.withIdentity("myTrigger", "group1")
.startAt(startDate)
.build();
示例 2
// 擷取3秒後的時間
Date startDate = new Date();
startDate.setTime(startDate.getTime() + 3000);
// 每2s執行一次,無限循環
SimpleScheduleBuilder scheduleBuilder = SimpleScheduleBuilder
.simpleSchedule()
.withIntervalInSeconds(2)
.withRepeatCount(3);
// 3s之後執行第一次,之後每隔2s執行一次,重複3次
Trigger trigger = TriggerBuilder.newTrigger()
.withIdentity("myTrigger", "group1")
.startAt(startDate)
.withSchedule(scheduleBuilder)
.build();
示例 3
// 擷取3秒後的時間
Date startDate = new Date();
startDate.setTime(startDate.getTime() + 3000L);
// 擷取6秒後的時間
Date endDate = new Date();
endDate.setTime(endDate.getTime() + 6000L);
// 每2s執行一次,無限循環
SimpleScheduleBuilder scheduleBuilder = SimpleScheduleBuilder
.simpleSchedule()
.withIntervalInSeconds(2)
.withRepeatCount(3);
// 3s之後執行第一次,之後每隔2s執行一次,6秒之後結束
Trigger trigger = TriggerBuilder.newTrigger()
.withIdentity("myTrigger", "group1")
.startAt(startDate)
.endAt(endDate)
.withSchedule(scheduleBuilder)
.build();
注意:
重複次數可以為 0、正整數、SimpleTrigger.REPEAT_INDEFINITELY
重複執行間隔必須為 0 或長整數
一旦執行了 endTime 參數,那麼會覆寫重複次數參數的效果
CronTrigger
基于月曆的作業排程器,而不是像 SimpleTrigger 那樣精确指定時間間隔,較為常用
格式:
秒 分 時 日 月 周 年
特殊符号說明
, 或 10,12
- 區間 10-12
/ 每 */5
* 所有值 *
? 不指定
提示
- L 和 W 可以組合使用
- 周字段不區分大小寫 mon 與 MON 相同
- 利用線上生成工具
示例
// 每2秒執行一次
Trigger trigger = TriggerBuilder.newTrigger()
.withIdentity("myTrigger", "group1")
.withSchedule(CronScheduleBuilder.cronSchedule("*/2 * * * * ? *"))
.build();
Scheduler
StdSchedulerFactory
配置參數一般存儲在 quartz.properties
主要函數
// 将job和trigger注冊到scheduler
Date scheduleJob(JobDetail jobDetail, Trigger trigger)
// 啟動
void start()
// 暫停
void standby()
// 關閉
// true 等待所有任務執行完成再關閉
// false 直接關閉
void shutdown()
quartz.properties
文檔位置和加載順序
jar 包下有預設配置
組成部分
- 排程器屬性
- 線程池屬性
- 作業存儲位置
- 插件配置
SpringMVC 整合 Quartz
建立 maven webapp
webmvc
context
aop
core
配置 Quartz 的兩種方式:
- MethodInvokingJobDetailFactoryBean 适合調用特定 bean 方法時很友善
- JobDetailFactoryBean 支援傳入一些參數
項目結構
$ tree -I target
.
├── pom.xml
├── src
│ └── main
│ ├── java
│ │ └── com
│ │ └── mouday
│ │ ├── controller
│ │ │ └── IndexController.java
│ │ └── quartz
│ │ ├── ComplexJob.java
│ │ └── SimpleJob.java
│ ├── resources
│ │ └── dispatcher-servlet.xml
│ └── webapp
│ ├── WEB-INF
│ │ └── web.xml
│ └── index.jsp
1、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>org.example</groupId>
<artifactId>spring-mvc-demo</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<spring.version>5.2.6.RELEASE</spring.version>
</properties>
<build>
<finalName>springquartz</finalName>
<plugins>
<!-- tomcat7插件 maven 指令 tomcat7:run 啟動項目-->
<plugin>
<groupId>org.apache.tomcat.maven</groupId>
<artifactId>tomcat7-maven-plugin</artifactId>
<version>2.2</version>
<configuration>
<port>8080</port>
<path>/</path>
<uriEncoding>UTF-8</uriEncoding>
<!--添加忽略war包檢查标簽,則可以讓tomcat7:run指令正常啟動tomcat-->
<ignorePackaging>true</ignorePackaging>
<contextFile>src/main/webapp/WEB-INF/web.xml</contextFile>
<contextReloadable>true</contextReloadable>
</configuration>
</plugin>
</plugins>
</build>
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-beans</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context-support</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-expression</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
<version>1.2</version>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>4.0.1</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>jstl</groupId>
<artifactId>jstl</artifactId>
<version>1.2</version>
</dependency>
<!-- 需要 context-support tx 的支援-->
<dependency>
<groupId>org.quartz-scheduler</groupId>
<artifactId>quartz</artifactId>
<version>2.3.2</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-simple</artifactId>
<version>1.7.25</version>
<scope>compile</scope>
</dependency>
</dependencies>
</project>
2、src/main/webapp/index.jsp
<html>
<body>
<h2>Hello World!</h2>
</body>
</html>
3、src/main/webapp/WEB-INF/web.xml
<?xml version="1.0" encoding="utf-8" ?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee
http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
version="4.0">
<!-- 配置分發器 預設加載配置檔案:名字-servlet.xml -->
<servlet>
<servlet-name>dispatcher</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<!-- 指定配置檔案 -->
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:dispatcher-servlet.xml</param-value>
</init-param>
<!-- 表示容器再啟動時立即加載servlet -->
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>dispatcher</servlet-name>
<!-- 處理所有URL -->
<url-pattern>/</url-pattern>
</servlet-mapping>
<welcome-file-list>
<welcome-file>index.jsp</welcome-file>
</welcome-file-list>
</web-app>
4、src/main/resources/dispatcher-servlet.xml
<?xml version="1.0" encoding="utf-8" ?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:p="http://www.springframework.org/schema/p"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/mvc
http://www.springframework.org/schema/mvc/spring-mvc.xsd
">
<!--防止中文亂碼-->
<bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter">
<property name="messageConverters">
<list>
<bean class="org.springframework.http.converter.StringHttpMessageConverter">
<property name="supportedMediaTypes">
<list>
<value>text/html; charset=utf-8</value>
</list>
</property>
</bean>
</list>
</property>
</bean>
<!-- 添加注解驅動-->
<mvc:annotation-driven/>
<!-- 預設掃描包路徑-->
<context:component-scan base-package="com.mouday"/>
<!-- view-controller 可以直接不通過controller處理request,轉發到view-->
<mvc:view-controller path="/" view-name="index"/>
<!-- 渲染器-->
<bean id="jspViewResolver" class="org.springframework.web.servlet.view.UrlBasedViewResolver">
<property name="viewClass" value="org.springframework.web.servlet.view.JstlView"/>
<!-- 結果視圖的字首-->
<property name="prefix" value="/"/>
<!-- 結果視圖的字尾-->
<property name="suffix" value=".jsp"/>
</bean>
<!--配置Quartz-->
<bean id="simpleJobDetail" class="org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean">
<property name="targetObject" ref="simpleJob"/>
<property name="targetMethod" value="sayHello"/>
</bean>
<bean id="complexJobDetail" class="org.springframework.scheduling.quartz.JobDetailFactoryBean">
<property name="jobClass" value="com.mouday.quartz.ComplexJob"/>
<property name="jobDataMap">
<map>
<entry key="name" value="Tom"/>
</map>
</property>
<property name="Durability" value="true"/>
</bean>
<!--一s之後執行,每隔2s執行一次-->
<bean id="simpleTrigger" class="org.springframework.scheduling.quartz.SimpleTriggerFactoryBean">
<property name="jobDetail" ref="simpleJobDetail"/>
<property name="startDelay" value="1000"/>
<property name="repeatInterval" value="2000"/>
</bean>
<bean id="cronTrigger" class="org.springframework.scheduling.quartz.CronTriggerFactoryBean">
<property name="jobDetail" ref="complexJobDetail"/>
<property name="cronExpression" value="0/3 * * * * ? *"/>
</bean>
<bean class="org.springframework.scheduling.quartz.SchedulerFactoryBean">
<property name="jobDetails">
<list>
<ref bean="simpleJobDetail"/>
<ref bean="complexJobDetail"/>
</list>
</property>
<property name="triggers">
<list>
<ref bean="simpleTrigger"/>
<ref bean="cronTrigger"/>
</list>
</property>
</bean>
</beans>
5、src/main/java/com/mouday/controller/IndexController.java
package com.mouday.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
/**
* 僅用于SpringMVC服務測試
*/
@Controller
public class IndexController {
@GetMapping("/login")
@ResponseBody
public String login(
@RequestParam(value = "name", required = false) String name,
@RequestParam(value = "password", required = false) String password
) {
return "name:" + name + " password:" + password;
}
}
6、src/main/java/com/mouday/quartz/SimpleJob.java
package com.mouday.quartz;
import org.springframework.stereotype.Component;
import java.text.SimpleDateFormat;
import java.util.Date;
@Component("simpleJob")
public class SimpleJob {
public void sayHello(){
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
System.out.println("SimpleJob "+ dateFormat.format(new Date()) );
}
}
7、src/main/java/com/mouday/quartz/ComplexJob.java
package com.mouday.quartz;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.springframework.scheduling.quartz.QuartzJobBean;
import java.text.SimpleDateFormat;
import java.util.Date;
public class ComplexJob extends QuartzJobBean {
private String name;
public void setName(String name) {
this.name = name;
}
protected void executeInternal(JobExecutionContext jobExecutionContext) throws JobExecutionException {
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
System.out.println("ComplexJob name: " + this.name + " " + dateFormat.format(new Date()));
}
}
總結
- Timer 優缺點
- Quartz 三大要素
- Quartz&Spring 融合