天天看點

Java學習筆記:定時任務排程工具之Quartz

Quartz官網 http://www.quartz-scheduler.org/ 特點

  1. 強大的排程功能
  2. 靈活的應用方式
  3. 分布式和叢集能力

主要用到的設計模式

  1. Builder 模式
  2. Factory 模式
  3. 元件模式
  4. 鍊式寫法

三個核心概念

  1. 排程器
  2. 任務
  3. 觸發器

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
* 所有值 *
? 不指定      

提示

  1. L 和 W 可以組合使用
  2. 周字段不區分大小寫 mon 與 MON 相同
  3. 利用線上生成工具

示例

// 每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 包下有預設配置

組成部分

  1. 排程器屬性
  2. 線程池屬性
  3. 作業存儲位置
  4. 插件配置

SpringMVC 整合 Quartz

建立 maven webapp

webmvc
context
aop
core      

配置 Quartz 的兩種方式:

  1. MethodInvokingJobDetailFactoryBean 适合調用特定 bean 方法時很友善
  2. 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()));
    }
}
      

總結

  1. Timer 優缺點
  2. Quartz 三大要素
  3. Quartz&Spring 融合