任務調度框架Quartz詳解+具體代碼應用+Spring整合+SpringMVC框架Demo

Quartz是啥?

quartz是一個由java編寫的任務調度庫,由OpenSymphony組織開源出來。絕大多數公司都會用到任務調度這個功能, 比如公司需要定期執行任務調度生成報表, 或者比如博客什麼的定時更新之類的,都可以靠Quartz來完成。

任務調度:現在有N個任務(程序),要求在指定時間執行,比如每週二3點執行任務A、每天相隔5s執行任務B等等,這種多任務擁有多種執行策略就是任務調度。

而quartz的核心作用,是使任務調度變得豐富、高效、安全,開發者只需要調幾個quartz接口並做簡單配置,即可實現上述需求。

quartz號稱能夠同時對上萬個任務進行調度,擁有豐富的功能特性,包括任務調度、任務持久化、可集羣化、插件等。

關於Quartz的結構,重點內容都集結在了下面的圖中

Quartz解析圖

概念解析

1、Job:是一個接口,只有一個方法void execute(JobExecutionContext context),開發者實現該接口定義運行任務,JobExecutionContext類提供了調度上下文的各種信息。Job運行時的信息保存在JobDataMap實例中;

2、JobDetail:Quartz在每次執行Job時,都重新創建一個Job實例,所以它不直接接受一個Job的實例,相反它接收一個Job實現類,以便運行時通過newInstance()的反射機制實例化Job。因此需要通過一個類來描述Job的實現類及其它相關的靜態信息,如Job名字、描述、關聯監聽器等信息,JobDetail承擔了這一角色。

3、Trigger:是一個類,描述觸發Job執行的時間觸發規則。主要有SimpleTrigger和CronTrigger這兩個子類。當僅需觸發一次或者以固定時間間隔週期執行,SimpleTrigger是最適合的選擇;而CronTrigger則可以通過Cron表達式定義出各種複雜時間規則的調度方案:如每早晨9:00執行,週一、週三、週五下午5:00執行等等;
4、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分別針對每年、每月和每週進行定義;

5、Scheduler: 代表一個Quartz的獨立運行容器, Trigger和JobDetail可以註冊到Scheduler中, 兩者在Scheduler中擁有各自的組及名稱, 組及名稱是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實例;

6、ThreadPool:Scheduler使用一個線程池作爲任務運行的基礎設施,任務通過共享線程池中的線程提高運行效率。

代碼示範

1. 簡單使用Quartz(單機版)

三大要素(JobDetail、Trigger、Scheduler)我們使用以下工廠進行實現:
  • MethodInvokingJobDetailFactoryBean
    屬性:name、group、targetObject(spring bean)、targetMethod
    特點:不需要任務類實現任何接口
  • CronTriggerFactoryBean
    屬性:name、group、jobDetail、cronExpression
  • SchedulerFactoryBean

- Maven依賴

  <dependencies>
        <dependency>
            <groupId>org.quartz-scheduler</groupId>
            <artifactId>quartz</artifactId>
            <version>2.3.0</version>
        </dependency>
    </dependencies>

- 定義任務類

package com.golden3young.job;

import org.quartz.Job;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;

import java.text.SimpleDateFormat;
import java.util.Date;

public class HelloJob implements Job {
    @Override
    public void execute(JobExecutionContext context) throws JobExecutionException {
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss SSS");
        System.out.println(sdf.format(new Date()) + " Hello");
    }
}

自定義的任務類需要實現org.quartz包中的Job接口,然後覆蓋execute()方法,在其中給出具體的任務實現代碼。一會這個Job要裝配到JobDetail中,作爲核心部分。

- 簡單測試 開啓任務調度

1.案例一:每隔3秒打印一次Hello
package com.golden3young.simple;

import com.golden3young.job.HelloJob;
import org.quartz.*;
import org.quartz.impl.StdSchedulerFactory;

/**
 * 每隔3秒打印一次Hello
 */
public class HelloJobMain {
    public static void main(String[] args) throws SchedulerException {
        //創建 JobDetail
        JobDetail jobDetail = JobBuilder.newJob(HelloJob.class)
                .withIdentity("hello", "hello")
                .build();

        //創建 Trigger
        SimpleTrigger trigger = TriggerBuilder.newTrigger()
                .withIdentity("hello", "hello")
                .startNow()    //立即執行
                .withSchedule(SimpleScheduleBuilder.simpleSchedule()
                        .withIntervalInSeconds(3)   //每隔3秒執行
                        .repeatForever())   //永遠執行
                .build();   //調度規則

        //創建 Scheduler,並開啓調度
        Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();
        scheduler.scheduleJob(jobDetail, trigger);
        scheduler.start();
    }
}

2.案例二:兩秒後執行任務一次,每三秒打印一次Hello(重複10次)
package com.golden3young.simple;

//靜態導入 - 可以將類中所有的方法導入
import static org.quartz.JobBuilder.*;
import static org.quartz.TriggerBuilder.*;

import com.golden3young.job.HelloJob;
import org.quartz.*;
import org.quartz.impl.StdSchedulerFactory;

import java.text.SimpleDateFormat;
import java.util.Date;

/**
 * 兩秒後執行任務一次,每三秒打印一次Hello(重複10次)
 */
public class SecondMain {
    public static void main(String[] args) throws SchedulerException {
        //創建JobDetail
        JobKey jobKey = new JobKey("hello","hello");
        JobDetail jobDetail = newJob(HelloJob.class)
                .withIdentity(jobKey)
                .build();

        //兩秒後的時間
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        Date date = new Date();
        System.out.println("當前時間:" + sdf.format(date));
        Date twoSecondsAfter = new Date(date.getTime() + 2000L);

        SimpleTrigger trigger = newTrigger()
                .withIdentity(new TriggerKey("hello", "hello"))
                .startAt(twoSecondsAfter)   //傳入一個Date類型的時間。用來指定一個執行時間
                .withSchedule(
                        SimpleScheduleBuilder.simpleSchedule()
                        .withIntervalInSeconds(3)//每隔3秒一執行
                        .withRepeatCount(10)    //重複10次
                ).build();

        Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();
        scheduler.scheduleJob(jobDetail,trigger);
        scheduler.start();
    }
}

3.案例三:兩秒後執行任務一次,每三秒打印一次Hello,15秒結束任務
package com.golden3young.simple;

import com.golden3young.job.HelloJob;
import org.quartz.*;
import org.quartz.impl.StdSchedulerFactory;

import java.text.SimpleDateFormat;
import java.util.Date;

import static org.quartz.JobBuilder.*;
import static org.quartz.TriggerBuilder.*;

public class ThirdMain {
    public static void main(String[] args) throws SchedulerException {

        //創建JobDetail
        JobDetail jobDetail = newJob(HelloJob.class)
                .withIdentity(new JobKey("hello", "hello"))
                .build();

        //兩秒後的時間
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        Date date = new Date();
        System.out.println("當前時間:" + date);
        Date twoSecondsAfter = new Date(date.getTime() + 2000L); //2秒後的時間
        Date endDate = new Date(date.getTime() + 15000L);	//結束時間,也就是15秒後的時間

        //創建Trigger
        SimpleTrigger trigger = newTrigger()
                .withIdentity(new TriggerKey("hello", "hello"))
                .startAt(twoSecondsAfter)   //2秒後開始
                .endAt(endDate) //15秒後停止
                .withSchedule(
                        SimpleScheduleBuilder.simpleSchedule()
                                .withIntervalInSeconds(3)
                        .repeatForever()
                )
                .build();

        Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();
        scheduler.scheduleJob(jobDetail,trigger);
        scheduler.start();
    }
}

2. Spring單獨整合Quartz(註解版)

- Maven 依賴

 	<dependencies>
        <!--quartz-->
        <dependency>
            <groupId>org.quartz-scheduler</groupId>
            <artifactId>quartz</artifactId>
            <version>2.3.0</version>
        </dependency>

        <!--spring-context-support-->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context-support</artifactId>
            <version>5.0.7.RELEASE</version>
        </dependency>
        
    </dependencies>

- 實現任務類

實現的任務比較簡單,就是單純的打印一段文字。

package com.golden3young.job;

public class OrderJob {

    public void execute(){
        System.out.println("刪除訂單");
    }
}

- 配置類的書寫

package com.golden3young.config;

import com.golden3young.job.OrderJob;
import org.quartz.Trigger;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.quartz.CronTriggerFactoryBean;
import org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean;
import org.springframework.scheduling.quartz.SchedulerFactoryBean;


import java.util.ArrayList;
import java.util.List;

@Configuration  //<beans>
public class QuartzConfig {

    // 註冊 任務類
    @Bean
    public OrderJob orderJob(){
        return new OrderJob();
    }

    @Bean
    public MethodInvokingJobDetailFactoryBean jobDetailFactoryBean(){
        MethodInvokingJobDetailFactoryBean factory = new MethodInvokingJobDetailFactoryBean();
        factory.setName("orderJob");
        factory.setGroup("orderGroup");
        factory.setTargetObject(this.orderJob());
        factory.setTargetMethod("execute");
        return factory;
    }

    @Bean
    public CronTriggerFactoryBean cronTriggerFactoryBean(){
        CronTriggerFactoryBean factoryBean = new CronTriggerFactoryBean();
        factoryBean.setName("orderTrigger");
        factoryBean.setGroup("orderTrigger");
        factoryBean.setJobDetail(this.jobDetailFactoryBean().getObject());
        factoryBean.setCronExpression("*/5 * * * * ?");//每5秒執行一次
        return factoryBean;
    }

    @Bean
    public SchedulerFactoryBean scheduler(){
        SchedulerFactoryBean factoryBean = new SchedulerFactoryBean();
        //自動轉成List
        factoryBean.setTriggers(this.cronTriggerFactoryBean().getObject());
        return factoryBean;
    }

}

- 測試代碼

package com.golden3young;

import com.golden3young.config.QuartzConfig;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class Test {
    public static void main(String[] args) {
        AnnotationConfigApplicationContext applicationContext =
                new AnnotationConfigApplicationContext(QuartzConfig.class);
    }
}

3. SpringMVC整合Quartz(xml文件版)

- Maven 依賴

	<!-- spring-webmvc -->
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-webmvc</artifactId>
      <version>5.0.7.RELEASE</version>
    </dependency>
    
    <!-- spring-context-support spring整合quartz的bean都在這個庫中 -->
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-context-support</artifactId>
      <version>5.0.7.RELEASE</version>
    </dependency>
    
     <!-- quartz -->
    <dependency>
      <groupId>org.quartz-scheduler</groupId>
      <artifactId>quartz</artifactId>
      <version>2.3.0</version>
    </dependency>
    
    <!-- thymeleaf -->
    <dependency>
      <groupId>org.thymeleaf</groupId>
      <artifactId>thymeleaf</artifactId>
      <version>3.0.9.RELEASE</version>
    </dependency>
    <!-- thymeleaf-spring5 -->
    <dependency>
      <groupId>org.thymeleaf</groupId>
      <artifactId>thymeleaf-spring5</artifactId>
      <version>3.0.9.RELEASE</version>
    </dependency>

- 配置文件 Web.xml

<!DOCTYPE web-app PUBLIC
 "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
 "http://java.sun.com/dtd/web-app_2_3.dtd" >

<web-app>

  <!--順序:context-praram  filter  filter-mapping listener  servlet  servlet-mapping-->

  <context-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>classpath:spring-root.xml</param-value>
  </context-param>

  <!--POST請求編碼過濾器-->
  <filter>
    <filter-name>encodingFilter</filter-name>
    <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
    <init-param>
      <param-name>encoding</param-name>
      <param-value>UTF-8</param-value>
    </init-param>
    <init-param>
      <param-name>forceEncoding</param-name>
      <param-value>true</param-value>
    </init-param>
  </filter>
  <filter-mapping>
    <filter-name>encodingFilter</filter-name>
    <url-pattern>/</url-pattern>
  </filter-mapping>

  <!--listener  加載root容器-->
  <listener>
    <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
  </listener>

  <!--前端控制器-->
  <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:spring-mvc.xml</param-value>
    </init-param>
    <load-on-startup>1</load-on-startup>
  </servlet>
  <servlet-mapping>
    <servlet-name>dispatcher</servlet-name>
    <url-pattern>/</url-pattern>
  </servlet-mapping>
</web-app>

- 配置Spring-root.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
	http://www.springframework.org/schema/beans/spring-beans-4.2.xsd
	http://www.springframework.org/schema/context
	http://www.springframework.org/schema/context/spring-context-4.2.xsd">

    <!-- 註解掃描 -->
    <context:component-scan base-package="com.etoak">
        <context:include-filter type="annotation" expression="org.springframework.stereotype.Service" />
        <context:include-filter type="annotation" expression="org.springframework.stereotype.Repository" />
        <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller" />
        <context:exclude-filter type="annotation" expression="org.springframework.web.bind.annotation.RestController" />
        <context:exclude-filter type="annotation" expression="org.springframework.web.bind.annotation.ControllerAdvice" />
    </context:component-scan>

    <!--DataSource-->
    <bean id="dataSource" class="com.zaxxer.hikari.HikariDataSource">
        <property name="driverClassName" value="com.mysql.jdbc.Driver" />
        <property name="jdbcUrl" value="jdbc:mysql://127.0.0.1:3306/et1909" />
        <property name="username" value="root" />
        <property name="password" value="etoak" />
    </bean>

    <!--事務管理器:DataSourceTransactionManager-->
    <bean id="tx" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <constructor-arg name="dataSource" ref="dataSource"/>
    </bean>

    <!--JobDetailFactoryBean-->
    <bean id="jobDetail" class="org.springframework.scheduling.quartz.JobDetailFactoryBean">
        <property name="name" value="emailJob"/>
        <property name="group" value="emailJob"/>
        <property name="jobClass" value="com.etoak.job.EmailJob"/>
        <property name="durability" value="true"/>
    </bean>

    <!--CronTriggerFactoryBean-->
    <bean id="trigger" class="org.springframework.scheduling.quartz.CronTriggerFactoryBean">
        <property name="name" value="emailTrigger"/>
        <property name="group" value="emailTrigger"/>
        <property name="jobDetail" ref="jobDetail"/>
        <property name="cronExpression" value="*/5 * * * * ?"/>
    </bean>

    <!--SchedulerFactoryBean-->
    <bean id="scheduler" class="org.springframework.scheduling.quartz.SchedulerFactoryBean">
        <property name="dataSource" ref="dataSource"/>
        <property name="transactionManager" ref="tx"/>
        <property name="configLocation" value="classpath:quartz.properties" />
        <property name="triggers">
            <list>
                <ref bean="trigger" />
            </list>
        </property>
        <!--將Spring的applicationContext容器添加到任務調度器的容器SchedulerContext中,使用key獲取spring容器-->
        <property name="applicationContextSchedulerContextKey" value="spring" />
        <property name="jobFactory" ref="mvcJobFactory"/>
    </bean>
    <bean id="mvcJobFactory" class="com.etoak.factory.MvcJobFactory"/>
</beans>

- 配置Spring-mvc.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:mvc="http://www.springframework.org/schema/mvc"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="
		http://www.springframework.org/schema/beans
		http://www.springframework.org/schema/beans/spring-beans-4.0.xsd
		http://www.springframework.org/schema/mvc
		http://www.springframework.org/schema/mvc/spring-mvc-4.0.xsd
		http://www.springframework.org/schema/context
		http://www.springframework.org/schema/context/spring-context-4.0.xsd">
    <!-- 配置掃描器 -->
    <context:component-scan base-package="com.etoak">
        <!-- include-filter -->
        <!-- 掃描註解ControllerRestController ControllerAdvice -->
        <context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
        <context:include-filter type="annotation" expression="org.springframework.web.bind.annotation.RestController"/>
        <context:include-filter type="annotation" expression="org.springframework.web.bind.annotation.ControllerAdvice"/>

        <!-- exclude-filter -->
        <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Service"/>
        <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Repository"/>
    </context:component-scan>
    <!-- 開始spring mvc配置 -->
    <mvc:annotation-driven></mvc:annotation-driven>

    <!-- 靜態文件相關配置 -->
    <!-- 將靜態文件交給servlet容器處理 -->
    <mvc:default-servlet-handler/>

    <!-- 整合Thymeleaf -->
    <!--  -->
    <bean id="templateResolver" class="org.thymeleaf.spring5.templateresolver.SpringResourceTemplateResolver" >
        <!-- prefix  suffix  templateMode  characterEncoding -->
        <property name="prefix" value="/templates/"></property>
        <property name="suffix" value=".html"></property>
        <property name="characterEncoding" value="UTF-8"></property>
        <property name="templateMode" value="HTML"></property>
        <!-- 是否進行頁面緩存(默認是true),開發階段設置爲false, -->
        <property name="cacheable" value="false"></property>
    </bean>

    <!-- SpringTemplateEngine -->
    <bean id="templateEngine" class="org.thymeleaf.spring5.SpringTemplateEngine">
        <property name="templateResolver" ref="templateResolver"></property>
    </bean>

    <!-- ThymeleafViewResolver -->
    <bean class="org.thymeleaf.spring5.view.ThymeleafViewResolver" >
        <property name="templateEngine" ref="templateEngine"></property>
        <property name="characterEncoding" value="UTF-8"></property>
    </bean>


</beans>

- quartz.properties配置文件

#可以設置爲任意,用在 JDBC JobStore中來唯一標識實例,但是所有集羣節點中必須相同。
org.quartz.scheduler.instanceName = SpringClusterTest
# 屬性爲 AUTO即可,基於主機名和時間戳來產生實例 ID 
org.quartz.scheduler.instanceId = AUTO 

## 線程
org.quartz.threadPool.class = org.quartz.simpl.SimpleThreadPool
org.quartz.threadPool.threadCount = 10
org.quartz.threadPool.threadPriority = 5
org.quartz.threadPool.threadsInheritContextClassLoaderOfInitializingThread = true

## 存儲
org.quartz.jobStore.misfireThreshold = 60000
#JobStoreTX,將任務持久化到數據庫中。因爲集羣中節點依賴於數據庫來傳播 Scheduler實例的狀態,你只能在使用 JDBC JobStore 時應用 Quartz 集羣。這意味着你必須使用 JobStoreTX 或是 JobStoreCMT 作爲 Job 存儲,不能在集羣中使用 RAMJobStore。
org.quartz.jobStore.class = org.quartz.impl.jdbcjobstore.JobStoreTX
org.quartz.jobStore.driverDelegateClass=org.quartz.impl.jdbcjobstore.StdJDBCDelegate
org.quartz.jobStore.tablePrefix = QRTZ_
org.quartz.jobStore.maxMisfiresToHandleAtATime=10
#值 true,表示 Scheduler實例要參與到一個集羣當中。這一屬性會貫穿於調度框架的始終,用於修改集羣環境中操作的默認行爲。
org.quartz.jobStore.isClustered = true 
#定義了Scheduler 實例檢入到數據庫中的頻率(單位:毫秒)。Scheduler 檢查是否其他的實例到了它們應當檢入的時候未檢入;這能指出一個失敗的 Scheduler 實例,且當前 Scheduler 會以此來接管任何執行失敗並可恢復的 Job。通過檢入操作,Scheduler 也會更新自身的狀態記錄。clusterChedkinInterval 越小,Scheduler 節點檢查失敗的 Scheduler 實例就越頻繁。默認值是 15000 (15)。
org.quartz.jobStore.clusterCheckinInterval = 20000 

- 創建服務層

package com.golden3young.service;

import org.springframework.stereotype.Service;

@Service
public class EmailService {

    //所有的業務邏輯都在這裏處理
    public void send(){
        System.out.println("郵件發送服務執行中....");
    }
}

- 創建Job任務類

package com.golden3young.job;

import com.golden3young.service.EmailService;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.quartz.QuartzJobBean;

public class MvcJob extends QuartzJobBean {

    @Autowired
    EmailService emailService;

    @Override
    protected void executeInternal(JobExecutionContext context) throws JobExecutionException {
        System.out.println("mvc Job 執行郵件下發開始...");
        emailService.send();
        System.out.println("mvc Job 執行郵件下發...結束");
    }
}

- 創建任務工廠類

package com.golden3young.factory;

import org.quartz.spi.TriggerFiredBundle;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.config.AutowireCapableBeanFactory;
import org.springframework.scheduling.quartz.SpringBeanJobFactory;

public class MvcJobFactory extends SpringBeanJobFactory{

    //可以將第三方框架創建的實例 加載到spring容器中
    @Autowired
    AutowireCapableBeanFactory springIoc;

    @Override
    protected Object createJobInstance(TriggerFiredBundle bundle) throws Exception {
        // 調用父類的方法創建任務實例
        Object job = super.createJobInstance(bundle);
        System.out.println(job.getClass().getName());

        //加載到spring容器中
        springIoc.autowireBean(job);

        return job;

    }
}

- 創建控制層Controller

包括調度任務的添加、停止、刪除

package com.golden3young.controller;

import com.golden3young.job.MvcJob;
import org.quartz.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;

@Controller
@RequestMapping("/job")
public class MvcController {

    @RequestMapping("/index")
    public String index(){
        return "index";
    }

    @Autowired
    Scheduler scheduler;    //由於在xml中已經配置了SchedulerFactoryBean,這裏可以直接使用

    @RequestMapping(value = "/add", produces = "text/plain;charset=utf-8")
    @ResponseBody
    public String addJob(
            @RequestParam String jobName, @RequestParam String jobGroup,
            @RequestParam String triggerName, @RequestParam String triggerGroup,
            @RequestParam String cron
    ) throws SchedulerException {

        JobKey jobKey = new JobKey(jobName, jobGroup);
        TriggerKey triggerKey = new TriggerKey(triggerName, triggerGroup);
        if (scheduler.checkExists(jobKey) || scheduler.checkExists(triggerKey)){
            return "任務已存在";
        }

        // JobDetail
        JobDetail jobDetail = JobBuilder.newJob(MvcJob.class).withIdentity(jobKey).build();

        //Trigger
        CronTrigger trigger = TriggerBuilder.newTrigger().withSchedule(CronScheduleBuilder.cronSchedule(cron)).build();

        //直接調用scheduler  調度任務
        scheduler.scheduleJob(jobDetail,trigger);

        return "SUCCESS";
    }

    @PostMapping(value = "/pause", produces = "text/plain;charset=UTF-8")
    @ResponseBody
    public String pause(@RequestParam String jobName, @RequestParam String jobGroup) throws SchedulerException {
       JobKey jobKey = new JobKey(jobName, jobGroup);
       if(!scheduler.checkExists(jobKey)){
           return "任務不存在";
       }
       scheduler.pauseJob(jobKey);
       return "SUCCESS";
    }

    @PostMapping(value="/resume", produces = "text/plain;charset=UTF-8")
    @ResponseBody
    public String resume(@RequestParam String jobName, @RequestParam String jobGroup) throws SchedulerException {
        JobKey jobKey = new JobKey(jobName, jobGroup);
        if(!scheduler.checkExists(jobKey)){
            return "任務不存在";
        }

        scheduler.resumeJob(jobKey);
        return "SUCCESS";
    }
}

常見問題整理:

  1. Quartz 中的兩種存儲方式?

RAMJobStore, JobStoreSupport,其中 RAMJobStore 是將 trigger 和 job 存儲在內存中,而 JobStoreSupport 是基於 jdbc 將 trigger 和 job 存儲到數據庫中。RAMJobStore 的存取速度非常快,但是由於其在系統被停止後所有的數據都會丟失,所以在通常應用中,都是使用 JobStoreSupport。

  1. Quartz實現動態配置定時任務?

在我們日常的開發中,很多時候,定時任務都不是寫死的,而是寫到數據庫中,在數據庫中取出需要的數據,從而實現定時任務的動態配置

  1. Quartz中的Job和StatefulJob的區別?

Job:普通的任務,或者說無狀態的任務,在JobDetail執行之後,不會記錄狀態。

StatefulJob:繼承自Job,由於在定義時添加了@PersistJobDataAfterExecution註釋。 對於同一個 trigger 來說,有狀態的 job 不能被並行執行,只有上一次觸發的任務被執行完之後,才能觸發下一次執行。

  1. 除了Quartz以外,其他的定時任務的實現?
    ①Java自帶的類Timer和TimerTask,Timer類是用來記時、定時的類,它接受一個TimerTask做參數;TimerTask裏面可以寫我們的任務。
    ②Spring3.0以後自帶的task,可以將它看成一個輕量級的Quartz。

  2. cron表達式怎麼寫?

Cron Expressions是七個子表達式組成的字符串,用於描述時間的各個細節。這些子表達式 用空格分隔,並表示:
 []     []     [小時]       []     []      []        [] 
 Seconds  Minutes  Hours  Day-of-Month  Month  Day-of-Week  Year (optional field)
時間字段  是否必填      允許值          特殊字符 
秒          是         0-59           ,-*/ 
分          是         0-59           ,-*/ 
時          是         0-23           ,-*/ 
天          是         1-31           ,-*/?LW 
月          是      1-12或者JAN-DEC   ,-*/ 
周          是      1-7或者SUN-SAT    ,-*/?L#周
天用1表示,依次類推 
年          否      空或1970-2099     ,-*/ ,
:表示或的關係 
-:範圍的關係【比如1-21*:每秒、每分、每小時等 
/:每天哪個時間執行 
L:表示爲每月的最後一天,或每個月的最後星期幾如“6L”表示“每月的最後一個星期五” 
W:該字符只能出現在日期字段裏,是對前導日期的修飾,表示離該日期最近的工作日。 例如15W表示離該月15號最近的工作日,如果該月15號是星期六,則匹配14號星期五; 如果15日是星期日,則15W匹配16號星期一; 如果15號是星期二,那15W結果就是15號星期二。 但必須注意關聯的匹配日期不能夠跨月,如你指定1W,如果1號是星期六,結果匹配的是3號星 期一,而非上個月最後的那天。 W字符串只能指定單一日期,而不能指定日期範圍; #:是用來指定每月第n個工作日,例在每週(day-of-week)這個字段中內容爲"6#3" or "FRI#3" 則表示“每月第三個星期五”
?只能用到天和週上 - 如果天用了*,那麼周只能用?

- 如果天用了?,那麼周只能用* 
- 如果日期()確定了,那麼周只能用? 
- 如果周確定了,那麼日期()只能用?
  1. 如何禁止quart的併發操作?

2種方式:

spring中將job的concurrent屬性設置爲false。默認是true 如下:

<bean id="scheduleJob" class="org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean">
    <property name="targetObject" ref="scheduleBusinessObject"/>
    <property name="targetMethod" value="doIt"/>
    <property name="concurrent" value="false"/>
</bean>

job類上加上註解@DisallowConcurrentExecution。

@DisallowConcurrentExecution
public class HelloQuartz implements Job {

    @Override
    public void execute(JobExecutionContext jobExecutionContext) {
        JobDetail detail = jobExecutionContext.getJobDetail();
        String name = detail.getJobDataMap().getString("name");
        System.out.println("my job name is  " + name + " at " + new Date());
        }
    }
}

注意:@DisallowConcurrentExecution是對JobDetail實例生效,如果一個job類被不同的jobdetail引用,這樣是可以併發執行。

發佈了14 篇原創文章 · 獲贊 3 · 訪問量 2091
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章