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();

注意:

  1. 重複次數可以爲 0、正整數、SimpleTrigger.REPEAT_INDEFINITELY
  2. 重複執行間隔必須爲 0 或長整數
  3. 一旦執行了 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 融合
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章