基于Spring+Quartz 搭建定时任务调度框架

写在前面的话

我始终认为,人与人之间最大的差距不是来自于智力和情商! 智力一般的人,一样可以取得不错的成绩,情商一般的人也一样可以。那么拉开差距的到底是什么? 笔者认为是习惯,努力还有选择!让学习成为一种习惯, 他将让你收益终身。希望大家都能在这里学到东西,在未来的学习中工作中能更加如鱼得水,这样既不辜负大家的付出,也不辜负我的劳动成果。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-lMFedfQx-1577934720169)(1BF988091D4B473CB1E4623F0B4B11E8)]

目录:

Quartz 任务调度框架介绍、入门以及基本使用

基本介绍

Quartz是OpenSymphony开源组织在Job scheduling领域又一个开源项目,是完全由java开发的一个开源的任务日程管理系统,“任务进度管理器”就是一个在预先确定(被纳入日程)的时间到达时,负责执行(或者通知)其他软件组件的系统。

官网:http://www.quartz-scheduler.org/
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-LDT80K9t-1577934720170)(70597385B08E4CB8B305DB3E51E8545A)]

简单总结:

  • Quartz 是一个完全由 Java 编写的开源作业调度框架,为在 Java 应用程序中进行作业调度提供了简单却强大的机制。
  • Quartz 可以与 J2EE 与 J2SE 应用程序相结合也可以单独使用。
  • Quartz 允许程序开发人员根据时间的间隔来调度作业。
  • Quartz 实现了作业和触发器的多对多的关系,还能把多个作业与不同的触发器关联。
Quartz能做什么?
  • 作业调度:调用各种框架的作业脚本,例如shell,hive等
  • 定时任务:在某一预定的时刻,执行你想要执行的任务
为什么要用Quartz?
  • 经过市场验证,开源,免费
  • 动态调度任务,api丰富,满足多种业务场景
  • 支持多线程,分布式,集群
  • 支持持久化存储,运行在内存中,可调用JDBC进行持久化存储
  • 支持故障转移,负载均衡
Quartz入门精讲
1. 下载安装

方式一:官网下载 http://www.quartz-scheduler.org/downloads/ 下载之后可以看到这样一个目录,打开将lib中的quartz-2.*-xx.jar添加到项目中
就可以使用啦

方式二:使用maven

        <!-- 引入quartz -->
        <dependency>
            <groupId>org.quartz-scheduler</groupId>
            <artifactId>quartz</artifactId>
            <version>2.3.0</version>
        </dependency>
        <dependency>
            <groupId>org.quartz-scheduler</groupId>
            <artifactId>quartz-jobs</artifactId>
            <version>2.2.1</version>
        </dependency>

坑点:确保应用中只有一个版本的Quartz,否则多版本容易导致引用不正确的类,从而导致项目无法正常运行。需要手动取排除掉这些jar包!

2.quartz.properties配置文件

a. 该配置文件不是必须的,如果使用最基本的配置,该文件必须位于classpath下。基本配置如下:

org.quartz.scheduler.instanceName = MyScheduler
org.quartz.threadPool.threadCount = 3
org.quartz.jobStore.class = org.quartz.simpl.RAMJobStore

配置解释:调度程序名称为MyScheduler,线程池最多可同时运行3个作业,作业的所有信息都存储到内存中

b. 如果你准备构建一个使用quartz的web应用(以.war包的形式),你应该将quartz.properties文件放到WEB-INF/classes目录下。

c. 详细配置手册,有需求是可查看手册: https://www.w3cschool.cn/quartz_doc/quartz_doc-i7oc2d9l.html

3.第一个程序

来看看官方给的示例:

import org.quartz.Scheduler;
import org.quartz.SchedulerException;
import org.quartz.impl.StdSchedulerFactory;
import static org.quartz.JobBuilder.*;
import static org.quartz.TriggerBuilder.*;
import static org.quartz.SimpleScheduleBuilder.*;

public class QuartzTest {

  public static void main(String[] args) {

      try {
          // Grab the Scheduler instance from the Factory
          Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();

        // define the job and tie it to our HelloJob class
        JobDetail job = new Job(HelloJob.class)
          .withIdentity("job1", "group1")
          .build();
        
        // Trigger the job to run now, and then repeat every 40 seconds
        Trigger trigger = new Trigger()
          .withIdentity("trigger1", "group1")
          .startNow()
                .withSchedule(simpleSchedule()
                  .withIntervalInSeconds(40)
                  .repeatForever())            
          .build();
        
        // Tell quartz to schedule the job using our trigger
        scheduler.scheduleJob(job, trigger);

          // and start it off
        scheduler.start();

        scheduler.shutdown();

      } catch (SchedulerException se) {
          se.printStackTrace();
      }
  }
}

解读:这是一个很精简的例子,包含了一个基本的使用过程

  • 首先创建一个Scheduler示例
  • 然后定义一个作业job, 作业中有需要执行的内容
  • 创建一个触发器,其中包含触发方式和条件
  • 将作业,和定时任务传入scheduler中
  • 启动
  • 关闭
4.Quartz核心概念
  • Scheduler: 与调度程序交互的主要API。
  • Job: 由希望由调度程序执行的组件实现的接口。
  • JobDetail: 用于定义作业的实例。
  • Trigger(即触发器): 定义执行给定作业的计划的组件。
  • JobBuilder: 用于定义/构建JobDetail实例,用于定义作业的实例。
  • TriggerBuilder: 用于定义/构建触发器实例。
5.Job编写
  public class HelloJob implements Job {

    public HelloJob() {
    }

    public void execute(JobExecutionContext context)
      throws JobExecutionException {
      
      System.err.println("Hello!  HelloJob is executing.");
    }
  }

很简单, 我们只需要实现Job接口, 然后实现execute方法即可。在execute中写我们的业务逻辑
即可

那么, 假如我需要在execute中接收参数,进行业务处理怎么办呢? 请看下一点!

6.JobDataMap传递参数
    /**
     * 创建一个定时作业任务并启动
     *
     * @param jobName
     * @param cls
     * @param cron
     * @param command
     */
    public static void addJob(String jobName, Class cls, String cron, String command) {
        try {
            Scheduler sched = gSchedulerFactory.getScheduler();

            JobDetail job = JobBuilder.newJob(cls).withIdentity(jobName, JOB_GROUP_NAME).build();

            JobDataMap jobDataMap = job.getJobDataMap();
            // 传递参数
            jobDataMap.put("command", command);
//            jobDataMap.put("timingExpId", timingExpId);

            // 表达式调度构建器
            CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule(cron);

            // 获取当前系统时间 转成 北京时间
            Date currTime = new Date(System.currentTimeMillis());
            Date date = changeTimeZone(currTime, TimeZone.getDefault(), TimeZone.getTimeZone("Asia/Shanghai"));

            // 按新的cronExpression表达式构建一个新的trigger
            Trigger trigger = TriggerBuilder.newTrigger()
                    .withIdentity(jobName, TRIGGER_GROUP_NAME)
                    .withSchedule(scheduleBuilder)
                    .startAt(date)// 设置开始计时时间
                    .build();



            // 交给scheduler去调度
            sched.scheduleJob(job, trigger);

            // 启动
            if (!sched.isShutdown()) {
                sched.start();
                logger.info("新任务创建完毕,并启动 !jobName:[" + jobName + "]");
            }
        } catch (Exception e) {
            logger.error("新任务创建、并启动 失败", e);
        }
    }
  • 实例化job对象后,获取JobDataMap对象 JobDataMap jobDataMap = job.getJobDataMap();
  • 在jobDataMap中设置需要传递的参数jobDataMap.put("command", command);
  • 在Job的execute中接收参数
    public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {

        JobDataMap dataMap = jobExecutionContext.getMergedJobDataMap();
        String command = (String) dataMap.get("command");   // 接收JobDetail 传递过来的参数
    }
  • 用户只能创建一个Job类,然后创建多个与该job关联的JobDetail实例,每一个实例都有自己的属性集和JobDataMap,最后,将所有的实例都加到scheduler中。
7. Trigger的公共属性

所有类型的trigger都有TriggerKey这个属性,表示trigger的身份;除此之外,trigger还有很多其它的公共属性。这些属性,在构建trigger的时候可以通过TriggerBuilder设置。

trigger的公共属性有:

  • jobKey属性:当trigger触发时被执行的job的身份;
  • startTime属性:设置trigger第一次触发的时间;该属性的值是java.util.Date类型,表示某个指定的时间点;有些类型的trigger,会在设置的startTime时立即触发。

优先级(priority)

  • 设置优先级,正数负数均可

注意:只有同时触发的trigger之间才会比较优先级。10:59触发的trigger总是在11:00触发的trigger之前执行。

注意:如果trigger是可恢复的,在恢复后再调度时,优先级与原trigger是一样的。

错过触发(misfire Instructions)

  • trigger的misfire属性;如果scheduler关闭了,或者Quartz线程池中没有可用的线程来执行job,此时持久性的trigger就会错过(miss)其触发时间,即错过触发(misfire)。

日历示例(calendar)

  • Quartz的Calendar对象(不是java.util.Calendar对象)可以在定义和存储trigger的时候与trigger进行关联。Calendar用于从trigger的调度计划中排除时间段。
8. 注解控制Job状态和并发

这些注解通常加载Job类上:

  • @DisallowConcurrentExecution 通知Quartz不要并发地执行同一个job类的多个实例。
  • @PersistJobDataAfterExecution 成功执行了job类的execute方法后,没有出现异常,则更新JobDetail中JobDataMap的数据
  • @DisallowConcurrentExecution 控制Job实例并发执行,使其有序一个接一个执行
9. Job其它属性

通过JobDetail对象,可以给job实例配置的其它属性:

  • Durability 非持久化,不活跃的trigger与之关联的时候,会被自动地从scheduler中删除
  • RequestsRecovery scheduler发生硬关闭(hard shutdown) 例如关机,则当scheduler重新启动的时候,该job会被重新执行。

使用 Spring + Maven 构建项目

1. Spring 基本介绍

Spring这东西相信大家都很熟悉了,但是为了照顾到所有同学,还是在这里简的概述一下。有基础的同学可以直接跳过

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-OVsxCdJX-1577934720172)(AFBA3890978E4EB8BE40D96A2F3EB28B)]

  • Spring 是最受欢迎的企业级 Java 应用程序开发框架,数以百万的来自世界各地的开发人员使用 Spring 框架来创建性能好、易于测试、可重用的代码。

  • Spring 框架是一个开源的 Java 平台,它最初是由 Rod Johnson 编写的,并且于 2003 年 6 月首次在 Apache 2.0 许可下发布。

  • Spring 是轻量级的框架,其基础版本只有 2 MB 左右的大小。

  • Spring 框架的核心特性是可以用于开发任何 Java 应用程序,但是在 Java EE 平台上构建 web 应用程序是需要扩展的。 Spring 框架的目标是使 J2EE 开发变得更容易使用,通过启用基于 POJO 编程模型来促进良好的编程实践。

  • 详细的DI,AOP这些概念可以自行去了解

  • 官网:https://spring.io/projects/spring-framework

2.Maven基本介绍

有基础的同学可直接跳过
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-GsYWaRVu-1577934720172)(A9FA2A4B537747B3A883373B8B5E4D33)]

  • Maven是一个项目管理和综合工具。Maven提供了开发人员构建一个完整的生命周期框架。开发团队可以自动完成项目的基础工具建设,Maven使用标准的目录结构和默认构建生命周期。

  • 在多个开发团队环境时,Maven可以设置按标准在非常短的时间里完成配置工作。由于大部分项目的设置都很简单,并且可重复使用,Maven让开发人员的工作更轻松,同时创建报表,检查,构建和测试自动化设置。

  • 功能:构建,文档生成,报告,依赖,SCMs,发布,分发,邮件列表

  • 详细可了解:http://maven.apache.org/

3.基于idea使用Spring + Maven构建项目
  • 新建项目

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-00vz7tRP-1577934720172)(99C397ECD389459281FBDF9FAB4D888E)]

  • 使用maven构建

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-YAXqm4fR-1577934720173)(10532C3A0A0D48A9A010B8EF39557679)]

  • 填写信息

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-H1pCiaw3-1577934720173)(3AF123136D7C410AA8A734CD9C81FE79)]

  • 填写项目名称,路径

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-RFZRgi8x-1577934720173)(D02838DACE654D6583780F3DCA364EFF)]

  • 完成后是这样的

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-sMOXnyy7-1577934720173)(0244D22299E64A4DAB49CD5F5E9DA8DA)]

  • 选择自动导入添加的依赖

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-J9LpH1oy-1577934720174)(BC1B8E63C74C429FABE860120AB1DEA7)]

  • 集成Spring

将Spring所需要的依赖粘贴至pom文件中,等待依赖下载完成即可

     <!-- Spring依赖 -->
        <!-- 1.Spring核心依赖 -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-core</artifactId>
            <version>4.3.7.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-beans</artifactId>
            <version>4.3.7.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>4.3.7.RELEASE</version>
        </dependency>
        <!-- 2.Spring dao依赖 -->
        <!-- spring-jdbc包括了一些如jdbcTemplate的工具类 -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-jdbc</artifactId>
            <version>4.3.7.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-tx</artifactId>
            <version>4.3.7.RELEASE</version>
        </dependency>
        <!-- 3.Spring web依赖 -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-web</artifactId>
            <version>4.3.7.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-webmvc</artifactId>
            <version>4.3.7.RELEASE</version>
        </dependency>
        <!-- 4.Spring test依赖:方便做单元测试和集成测试 -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-test</artifactId>
            <version>4.3.7.RELEASE</version>
        </dependency>

集成 Quartz 任务调度框架

1.将Quartz的依赖粘贴至pom文件
        <!-- 引入quartz -->
        <dependency>
            <groupId>org.quartz-scheduler</groupId>
            <artifactId>quartz</artifactId>
            <version>2.3.0</version>
        </dependency>
        <dependency>
            <groupId>org.quartz-scheduler</groupId>
            <artifactId>quartz-jobs</artifactId>
            <version>2.2.1</version>
        </dependency>
2.如果存在冲突,则需要手动排除包,正常像我们这样构建是没问题的

排除依赖使用

    // 排除示例
  <dependency>
        <groupId>org.apache.hadoop</groupId>
        <artifactId>zookeeper</artifactId>
        <version>3.3.1</version>
        <exclusions>
            <exclusion>
                <groupId>jline</groupId>
                <artifactId>jline</artifactId>
            </exclusion>
        </exclusions>
    </dependency>

实战应用开发

这一章节我们将进入实战开发

1.首先编写作业
  • 创建一个job包

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-JnPMH2ND-1577934720174)(FA9303D23D05446AA0B1F8657E97BE47)]

  • 创建一个WorkJob类,并集成Quartz Job类
import org.quartz.*;

import java.io.IOException;

/**
 * @Description 任务作业
 * @Date 2019/12/19 19:26
 * @Created by Jason
 */
// 保证任务一个接着一个执行
@DisallowConcurrentExecution
public class WorkJob implements Job{

    public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {
        System.out.println(" ===== 开始执行作业! ======");

        JobDataMap dataMap = jobExecutionContext.getMergedJobDataMap();
        String command = (String) dataMap.get("command");   // 接收JobDetail 传递过来的参数
        executeWork(command); // 若需要个性化定制任务, 再这里写你的逻辑就行啦
    }

    // 执行具体作业
    private void executeWork(String command) {
        try {
            // 可以执行,windows / linux 命令, 脚本等
            System.out.println(command);
            Process exec = Runtime.getRuntime().exec(command);
            exec.waitFor();
        } catch (IOException e) {
            e.printStackTrace();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

}

我这里写的作业是执行windows或者Linux命令或脚本。需要JDBC操作的,直接在execute方法中写就OK了,数据库连接,sql参数这些可以用参数传递的方式传递过来。

2. 创建配置文件读取类
import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
import java.util.Properties;

/**
 * @Description 配置文件操作类
 * @Date 2019/12/19 20:09
 * @Created by Jason
 */
public class PropUtils {

    private static String propPath;

    private static Properties properties;


    /**
     * 读取配置文件
     * @param path
     * @throws IOException
     */
    private static void read(String path) throws IOException {
        if (path!=null&&!"".equals(path)) {
            if (properties == null) {
                //可以读取任意路径的文件
                properties = new Properties();
                // 使用InPutStream流读取properties文件
                BufferedReader bufferedReader =
                        new BufferedReader(new FileReader(path));

                properties.load(bufferedReader);
            }

        }

        propPath = path;
    }


    /**
     * 获取配置属性
     * @param propPath
     * @param key
     * @return
     */
    public static String getPropValue(String propPath,String key) {
        try {
            read(propPath);
        } catch (IOException e) {
            e.printStackTrace();
            System.out.println(">>>>>>>> 读取配置文件时发生异常!");
        }

        return properties.getProperty(key);
    }


}

创建配置文件读取类是为了,用户可以在任何目录下存放配置文件,程序可以灵活的读取用户的配置,比如用户的定时时间,cron表达式, 执行的命令等。这些不应该写死在程序中

这里我们读取的是properties配置文件

示例配置文件:

prop.properties

cron=*/5 * * * * ?
command=notepad

cron代表时间表达式:每五秒执行一次任务,后面会详细说明

command代表需要执行的命令,notepad代表打开windows的记事本

3.编写Quartz任务管理器

import org.quartz.*;
import org.quartz.impl.StdSchedulerFactory;
import org.quartz.impl.matchers.GroupMatcher;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.List;
import java.util.Set;
import java.util.TimeZone;

/**
 * @Description 时间调度任务管理
 * @Date 2019/12/19 19:52
 * @Created by Jason
 */
public class QuartzManager {

    private final static Logger logger = LoggerFactory.getLogger(QuartzManager.class);

    private static SchedulerFactory gSchedulerFactory = new StdSchedulerFactory();
    private static String JOB_GROUP_NAME = "MY_JOBGROUP_NAME";
    private static String TRIGGER_GROUP_NAME = "MY_TRIGGERGROUP_NAME";
    private static Scheduler scheduler;
    // 北京时间
    private static final String TIME_FORMAT = "yyyy-MM-dd HH:mm:ss";

    /**
     * 创建一个定时任务并启动
     *
     * @param jobName
     * @param cls
     * @param cron
     * @param command
     */
    public static void addJob(String jobName, Class cls, String cron, String command) {
        try {
            Scheduler sched = gSchedulerFactory.getScheduler();

            JobDetail job = JobBuilder.newJob(cls).withIdentity(jobName, JOB_GROUP_NAME).build();

            JobDataMap jobDataMap = job.getJobDataMap();
            // 传递参数
            jobDataMap.put("command", command);
//            jobDataMap.put("timingExpId", timingExpId);

            // 表达式调度构建器
            CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule(cron);

            // 获取当前系统时间 转成 北京时间
            Date currTime = new Date(System.currentTimeMillis());
            Date date = changeTimeZone(currTime, TimeZone.getDefault(), TimeZone.getTimeZone("Asia/Shanghai"));

            // 按新的cronExpression表达式构建一个新的trigger
            Trigger trigger = TriggerBuilder.newTrigger()
                    .withIdentity(jobName, TRIGGER_GROUP_NAME)
                    .withSchedule(scheduleBuilder)
                    .startAt(date)// 设置开始计时时间
                    .build();



            // 交给scheduler去调度
            sched.scheduleJob(job, trigger);

            // 启动
            if (!sched.isShutdown()) {
                sched.start();
                System.out.println("新任务创建完毕,并启动 !jobName:[" + jobName + "]");
                logger.info("新任务创建完毕,并启动 !jobName:[" + jobName + "]");
            }
        } catch (Exception e) {
            logger.error("新任务创建、并启动 失败", e);
        }
    }

    /**
     * 移除一个定时任务
     *
     * @param jobName
     */
    public static void removeJob(String jobName) {

        TriggerKey triggerKey = TriggerKey.triggerKey(jobName, TRIGGER_GROUP_NAME);
        JobKey jobKey = JobKey.jobKey(jobName, JOB_GROUP_NAME);

        try {
            Scheduler sched = gSchedulerFactory.getScheduler();

            Trigger trigger = (Trigger) sched.getTrigger(triggerKey);
            if (trigger == null) {
                return;
            }

            // 停止触发器
            sched.pauseTrigger(triggerKey);
            // 移除触发器
            sched.unscheduleJob(triggerKey);
            // 删除任务
            sched.deleteJob(jobKey);

            logger.info("移除任务,完毕!jobName:[" + jobName + "]");
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * 查到当前任务的状态
     *
     * @param jobName
     * @return NONE 无,
     * NORMAL, 正常
     * PAUSED, 暂停
     * COMPLETE, 完成
     * ERROR, 错误,
     * BLOCKED 受阻;
     */
    public static String getTriggerState(String jobName) {

        TriggerKey triggerKey = TriggerKey.triggerKey(jobName, TRIGGER_GROUP_NAME);

        String name = null;
        try {
            Scheduler sched = gSchedulerFactory.getScheduler();
            Trigger.TriggerState triggerState = sched.getTriggerState(triggerKey);
            name = triggerState.name();
        } catch (Exception e) {
            logger.error("获取任务状态失败!jobName:[" + jobName + "]", e);
        }
        return name;
    }


    /**
     * 暂停一个任务
     *
     * @param jobName
     */
    public static void pauseJob(String jobName) {
        JobKey jobKey = JobKey.jobKey(jobName, JOB_GROUP_NAME);
        try {
            Scheduler sched = gSchedulerFactory.getScheduler();
            sched.pauseJob(jobKey);
        } catch (Exception e) {
            logger.error("暂停任务失败!jobName:[" + jobName + "]", e);
        }
    }

    /**
     * 恢复一个任务
     *
     * @param jobName
     */
    public static void resumeJob(String jobName) {
        JobKey jobKey = JobKey.jobKey(jobName, JOB_GROUP_NAME);
        try {
            Scheduler sched = gSchedulerFactory.getScheduler();
            sched.resumeJob(jobKey);
        } catch (SchedulerException e) {
            logger.error("恢复任务失败!jobName:[" + jobName + "]", e);

        }
    }


    /**
     * 获取定时任务调度中全部任务
     */
    public static void getAllJobs() {
        try {
            Scheduler sched = gSchedulerFactory.getScheduler();
            List<String> triggerGroupNames = sched.getTriggerGroupNames();
            for (String group : triggerGroupNames) {
                Set<TriggerKey> triggerKeys = sched.getTriggerKeys(GroupMatcher.triggerGroupEquals(group));
                for (TriggerKey triggerKey : triggerKeys) {
                    String jobName = triggerKey.getName();
                    String triggerState = getTriggerState(jobName);
                }
            }
        } catch (Exception e) {
            logger.info("获取任务调度管理器中全部任务失败", e);
        }
    }


    /**
     * 开启全部任务
     */
    public static void startJobs() {
        try {
            scheduler.start();
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }


    /**
     * 关闭全部任务
     */
    public void shutdownJobs() {
        try {
            if (!scheduler.isShutdown()) {
                scheduler.shutdown();
            }
        } catch (Exception e) {
            logger.error("关闭全部任务失败", e);
        }
    }


    /**
     * 删除定时任务
     *
     * @param jobName
     * @param jobGroupName
     * @param triggerName
     * @param triggerGroupName
     */
    public void removeJob(String jobName, String jobGroupName, String triggerName, String triggerGroupName) {
        try {
            TriggerKey triggerKey = TriggerKey.triggerKey(triggerName, triggerGroupName);
            // 停止触发器
            scheduler.pauseTrigger(triggerKey);
            // 移除触发器
            scheduler.unscheduleJob(triggerKey);
            // 删除任务
            scheduler.deleteJob(JobKey.jobKey(jobName, jobGroupName));
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }


    public void startJob(String jobName, String jobGroupName, String triggerName, String triggerGroupName) {
        try {
            TriggerKey triggerKey = TriggerKey.triggerKey(triggerName, triggerGroupName);
            Trigger.TriggerState triggerState = scheduler.getTriggerState(triggerKey);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * 创建启动定时任务
     * @param jobName           任务名称
     * @param triggerName       定时任务名称
     * @param triggerGroupName  定时任务组名称
     * @param jobGroupName      任务组名称
     * @param cron              cron表达式
     * @param jobClass          jobClass
     */
    public void createTrgger(String jobName,String triggerName,String triggerGroupName,String jobGroupName,String cron,Class jobClass) {
        try {

            JobDetail jobDetail = JobBuilder.newJob(jobClass).withIdentity(jobName, jobGroupName).build();
            jobDetail.getJobDataMap().put("messageId", "1");
            TriggerBuilder<Trigger> triggerBuilder = TriggerBuilder.newTrigger();
            // 触发器名,触发器组
            triggerBuilder.withIdentity(triggerName, triggerGroupName);
            triggerBuilder.startNow();
            // 触发器时间设定
            triggerBuilder.withSchedule(CronScheduleBuilder.cronSchedule(cron));
            // 创建Trigger对象
            CronTrigger trigger = (CronTrigger) triggerBuilder.build();
            // 调度容器设置JobDetail和Trigger
            scheduler.scheduleJob(jobDetail, trigger);
            // 启动
            if (!scheduler.isShutdown()) {
                scheduler.start();
            }
        } catch (Exception e) {
            logger.error("", e);
        }
    }

    public Scheduler getScheduler() {
        return scheduler;
    }

    public void setScheduler(Scheduler scheduler) {
        this.scheduler = scheduler;
    }

    /**
     * 获取更改时区后的日期
     * @param date 日期
     * @param oldZone 旧时区对象
     * @param newZone 新时区对象
     * @return 日期
     */
    public static Date changeTimeZone(Date date, TimeZone oldZone, TimeZone newZone) {
        Date dateTmp = null;
        if (date != null) {
            int timeOffset = oldZone.getRawOffset() - newZone.getRawOffset();
            dateTmp = new Date(date.getTime() - timeOffset);
        }
        return dateTmp;
    }
}
4.编写启动类
  • 方式1:jar包形式启动类

使用:

将项目打成jar包
命令行启动:java -jar xxx.jar "D:/timer/timer/prop.properties"
import com.sskjdata.job.WorkJob;
import com.sskjdata.manager.QuartzManager;
import com.sskjdata.prop.PropUtils;

/**
 * @Description TODO
 * @Date 2019/12/19 20:53
 * @Created by Jason
 */
public class MainTest {

    public static void main(String[] args) {
        if (args.length>0) {
            // 获取文件配置属性, 参数: 配置文件地址 , 属性键
            String command = PropUtils.getPropValue(args[0], "command");
            String cron = PropUtils.getPropValue(args[0], "cron");

            System.out.println("cron时间表达式:"+cron);
            System.out.println("command执行命令:"+command);

            QuartzManager.addJob("workJob",WorkJob.class,cron,command);
        }else {
            System.out.println("启动缺少参数! 必须参数:配置文件全路径名");
        }

    }
}
  • 方式2:ide测试
import com.sskjdata.job.WorkJob;
import com.sskjdata.manager.QuartzManager;
import com.sskjdata.prop.PropUtils;

/**
 * Created by Administrator on 2020/1/1 0001.
 */
public class MainTest2 {

    public static void main(String[] args) {
        // 获取文件配置属性, 参数: 配置文件地址 , 属性键
        String command = PropUtils.getPropValue("D:\\timer\\timer\\prop.properties", "command");
        String cron = PropUtils.getPropValue("D:\\timer\\timer\\prop.properties", "cron");

        System.out.println("cron时间表达式:"+cron);
        System.out.println("command执行命令:"+command);

        QuartzManager.addJob("workJob",WorkJob.class,cron,command);
    }
}

  • 方式3:打成依赖jar包,加入其他项目中使用

使用方式:

        // 获取文件配置属性, 参数: 配置文件地址 , 属性键
        String command = PropUtils.getPropValue("文件地址", "配置文件属性");
        String cron = PropUtils.getPropValue("文件地址", "配置文件属性");

        System.out.println("cron时间表达式:"+cron);
        System.out.println("command执行命令:"+command);
        // 启动
        QuartzManager.addJob("workJob",WorkJob.class,cron,command);
5.cron表达式详细介绍

Cron表达式是一个字符串,字符串以5或6个空格隔开,分为6或7个域,每一个域代表一个含义,Cron有如下

两种语法格式:

Seconds Minutes Hours DayofMonth Month DayofWeek Year或

Seconds Minutes Hours DayofMonth Month DayofWeek

每一个域可出现的字符如下:

Seconds:可出现", - * /"四个字符,有效范围为0-59的整数
Minutes:可出现", - * /"四个字符,有效范围为0-59的整数
Hours:可出现", - * /"四个字符,有效范围为0-23的整数
DayofMonth:可出现", - * / ? L W C"八个字符,有效范围为0-31的整数
Month:可出现", - * /"四个字符,有效范围为1-12的整数或JAN-DEc
DayofWeek:可出现", - * / ? L C #"四个字符,有效范围为1-7的整数或SUN-SAT两个范围。1表示星期天,2表示星期一, 依次类推

Year:可出现", - * /"四个字符,有效范围为1970-2099年

每一个域都使用数字,但还可以出现如下特殊字符,它们的含义是:

(1)*:表示匹配该域的任意值,假如在Minutes域使用*, 即表示每分钟都会触发事件。
(2)?:只能用在DayofMonth和DayofWeek两个域。它也匹配域的任意值,但实际不会。
因为DayofMonth和DayofWeek会相互影响。例如想在每月的20日触发调度,不管20
日到底是星期几,则只能使用如下写法: 13 13 15 20 * ?, 
其中最后一位只能用?,而不能使用*,如果使用*表示不管星期几都会触发,实际上并不是这样。
(3)-:表示范围,例如在Minutes域使用5-20,表示从5分到20分钟每分钟触发一次
(4)/:表示起始时间开始触发,然后每隔固定时间触发一次,例如在Minutes域使用5/20,则意味着5分钟触发一次,而25,45等分别触发一次.
(5),:表示列出枚举值值。例如:在Minutes域使用5,20,则意味着在5和20分每分钟触发一次。
(6)L:表示最后,只能出现在DayofWeek和DayofMonth域,如果在DayofWeek域使用5L,意味着在最后的一个星期四触发。
(7)W:表示有效工作日(周一到周五),只能出现在DayofMonth域,系统将在离指定日期的最近的有效工作日触发事件。例如:在 DayofMonth使用5W,如果5日是星期六,则将在最近的工作日:星期五,即4日触发。如果5日是星期天,则在6日(周一)触发;如果5日在星期一到星期五中的一天,则就在5日触发。另外一点,W的最近寻找不会跨过月份
(8)LW:这两个字符可以连用,表示在某个月最后一个工作日,即最后一个星期五。
(9)#:用于确定每个月第几个星期几,只能出现在DayofMonth域。例如在4#2,表示某月的第二个星期三。
举几个例子:
0 0 2 1 * ? * 表示在每月的1日的凌晨2点调度任务
0 15 10 ? * MON-FRI 表示周一到周五每天上午10:15执行作业
0 15 10 ? 6L 2002-2006 表示2002-2006年的每个月的最后一个星期五上午10:15执行作
一个cron表达式有至少6个(也可能7个)有空格分隔的时间元素。
按顺序依次为
秒(0~59)
分钟(0~59)
小时(0~23)
天(月)(0~31,但是你需要考虑你月的天数)
月(0~11)
天(星期)(1~7 1=SUN 或 SUN,MON,TUE,WED,THU,FRI,SAT)
年份(1970-2099)
其中每个元素可以是一个值(如6),一个连续区间(9-12),一个间隔时间(8-18/4)(/表示每隔4小时),一个列表(1,3,5),通配符。由于"月份中的日期"和"星期中的日期"这两个元素互斥的,必须要对其中一个设置?
0 0 10,14,16 * * ? 每天上午10点,下午2点,4点
0 0/30 9-17 * * ? 朝九晚五工作时间内每半小时
0 0 12 ? * WED 表示每个星期三中午12点
"0 0 12 * * ?" 每天中午12点触发
"0 15 10 ? * *" 每天上午10:15触发
"0 15 10 * * ?" 每天上午10:15触发
"0 15 10 * * ? *" 每天上午10:15触发
"0 15 10 * * ? 2005" 2005年的每天上午10:15触发
"0 * 14 * * ?" 在每天下午2点到下午2:59期间的每1分钟触发
"0 0/5 14 * * ?" 在每天下午2点到下午2:55期间的每5分钟触发
"0 0/5 14,18 * * ?" 在每天下午2点到2:55期间和下午6点到6:55期间的每5分钟触发
"0 0-5 14 * * ?" 在每天下午2点到下午2:05期间的每1分钟触发
"0 10,44 14 ? 3 WED" 每年三月的星期三的下午2:10和2:44触发
"0 15 10 ? * MON-FRI" 周一至周五的上午10:15触发
"0 15 10 15 * ?" 每月15日上午10:15触发
"0 15 10 L * ?" 每月最后一日的上午10:15触发
"0 15 10 ? * 6L" 每月的最后一个星期五上午10:15触发
"0 15 10 ? * 6L 2002-2005" 2002年至2005年的每月的最后一个星期五上午10:15触发
"0 15 10 ? * 6#3" 每月的第三个星期五上午10:15触发
有些子表达式能包含一些范围或列表
例如:子表达式(天(星期))可以为 “MON-FRI”,“MON,WED,FRI”,“MON-WED,SAT”
“*”字符代表所有可能的值
因此,“*”在子表达式(月)里表示每个月的含义,“*”在子表达式(天(星期))表示星期的每一天
“/”字符用来指定数值的增量
例如:在子表达式(分钟)里的“0/15”表示从第0分钟开始,每15分钟
在子表达式(分钟)里的“3/20”表示从第3分钟开始,每20分钟(它和“3,23,43”)的含义一样

“?”字符仅被用于天(月)和天(星期)两个子表达式,表示不指定值
当2个子表达式其中之一被指定了值以后,为了避免冲突,需要将另一个子表达式的值设为“?”
“L” 字符仅被用于天(月)和天(星期)两个子表达式,它是单词“last”的缩写
但是它在两个子表达式里的含义是不同的。
在天(月)子表达式中,“L”表示一个月的最后一天
在天(星期)自表达式中,“L”表示一个星期的最后一天,也就是SAT
如果在“L”前有具体的内容,它就具有其他的含义了
例如:“6L”表示这个月的倒数第6天,“FRIL”表示这个月的最一个星期五
注意:在使用“L”参数时,不要指定列表或范围,因为这会导致问题
字段 允许值 允许的特殊字符
秒 0-59 , - * /
分 0-59 , - * /
小时 0-23 , - * /
日期 1-31 , - * ? / L W C
月份 1-12 或者 JAN-DEC , - * /
星期 1-7 或者 SUN-SAT , - * ? / L C #
年(可选) 留空, 1970-2099 , - * /

测试验收项目以及项目总结

启动测试:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-HOdp0wGG-1577934720175)(C864BA9FDA7B4D908ACCBD73DFD16B75)]

看到执行并调用notepad成功,则说明成功了

打成可独立运行jar包, 依赖jar包

1
在这里插入图片描述
2
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-28cmdsOc-1577934720175)(404513AD99464783A57029A492D7DB22)]

3
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-2rBCnf7V-1577934720176)(AE52D16F17B7421E99522058C931D92D)]

4
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-rDVQfVab-1577934720176)(1D323415855A400792CD803BAFC4DE4A)]

5
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-2I7Xwvay-1577934720176)(AB1ED0AC3CC444BF9206D712F81EA484)]

6
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-SV1fwpBx-1577934720177)(38A7331069684952860BB67B846856BE)]

7
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-HPjz9B7q-1577934720177)(6ACA991DE06347D49E4EA46765FA61AA)]

8 一定要选src不然运行不了jar包
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-W30cDYz5-1577934720178)(D29FD852484E44929C939AB2D5E14539)]

9
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-J1aV2UJQ-1577934720178)(790134E489B64537B1177BF75A0A737C)]

10
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-2sPJpGve-1577934720178)(A0837472AA344CABB11B3A45C7D04394)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-f3Tpi7WR-1577934720178)(D8E8BAFBACA740F29475597901CCAA73)]

11 这样jar包就打包完成啦

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-yAIdH5P0-1577934720179)(88D576895DD04E3E879BFD693397AA77)]

项目总结
  • 整体篇幅比较长,大家可以分批次阅读,或者目录跳转均可
  • 对于学习一个新的技术,首先要了解其概念,然后了解它是什么?能做什么?优点是什么?缺点是什么?核心是什么?
  • 实战使用,加深印象,在实战中发现问题解决问题,这样提升才是很快的
  • 记录笔记,笔记方便后期遗忘查看,有利于反复记忆,是一笔经验财富

最后希望此文对大家有所助益!

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值