任务调度框架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引用,这样是可以并发执行。

发布了10 篇原创文章 · 获赞 1 · 访问量 385

猜你喜欢

转载自blog.csdn.net/m0_46193073/article/details/104034546