SpringBoot整合Quartz作为调度中心完整实用例子

因为想要做一个类似于调度中心的东西,定时执行一些Job(通常是一些自定义程序或者可执行的jar包),搭了一个例子,总结了前辈们的相关经验和自己的一些理解,如有雷同或不当之处,望各位大佬见谅和帮忙指正。

由于之前有许多小伙伴问过我如何写个定时任务,里面写上逻辑自己的逻辑,我另做了一个SpringBoot完全整合Quartz的简单例子

可以作为Quartz的入门参考 : https://github.com/EalenXie/springboot-quartz-simple

 

本例子是整合Quartz作为调度中心使用的。

1.首先新建项目SpringBoot-Quartz ,选用的技术栈为 SpringBoot + Quartz + Spring Data Jpa。完整代码可见(本文已非最新代码,最新见Github) : https://github.com/EalenXie/SpringBoot-Quartz

pom.xml依赖 : 

 1 <?xml version="1.0" encoding="UTF-8"?>
 2 <project xmlns="http://maven.apache.org/POM/4.0.0"
 3          xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 4          xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
 5     <modelVersion>4.0.0</modelVersion>
 6     <groupId>name.ealenxie</groupId>
 7     <artifactId>SpringBoot-Quartz</artifactId>
 8     <version>1.0</version>
 9     <parent>
10         <groupId>org.springframework.boot</groupId>
11         <artifactId>spring-boot-starter-parent</artifactId>
12         <version>2.0.1.RELEASE</version>
13     </parent>
14     <dependencies>
15         <dependency>
16             <groupId>org.springframework.boot</groupId>
17             <artifactId>spring-boot-starter-data-jpa</artifactId>
18         </dependency>
19         <dependency>
20             <groupId>org.springframework.boot</groupId>
21             <artifactId>spring-boot-starter-web</artifactId>
22         </dependency>
23         <dependency>
24             <groupId>mysql</groupId>
25             <artifactId>mysql-connector-java</artifactId>
26             <scope>runtime</scope>
27         </dependency>
28         <dependency> <!--quartz依赖-->
29             <groupId>org.quartz-scheduler</groupId>
30             <artifactId>quartz</artifactId>
31             <version>2.2.3</version>
32         </dependency>
33         <dependency>
34             <groupId>org.springframework</groupId>
35             <artifactId>spring-context-support</artifactId>
36         </dependency>
37         <dependency>
38             <groupId>org.quartz-scheduler</groupId>
39             <artifactId>quartz-jobs</artifactId>
40             <version>2.2.3</version>
41         </dependency>
42     </dependencies>
43 </project>

 

2.你需要在数据库中建立Quartz的相关系统表,所以你需要在数据库中执行如下来自Quartz官方的脚本

  1 -- in your Quartz properties file, you'll need to set org.quartz.jobStore.driverDelegateClass = org.quartz.impl.jdbcjobstore.StdJDBCDelegate
  2 -- 你需要在你的quartz.properties文件中设置org.quartz.jobStore.driverDelegateClass = org.quartz.impl.jdbcjobstore.StdJDBCDelegate
  3 -- StdJDBCDelegate说明支持集群,所有的任务信息都会保存到数据库中,可以控制事物,还有就是如果应用服务器关闭或者重启,任务信息都不会丢失,并且可以恢复因服务器关闭或者重启而导致执行失败的任务
  4 -- This is the script from Quartz to create the tables in a MySQL database, modified to use INNODB instead of MYISAM
  5 -- 这是来自quartz的脚本,在MySQL数据库中创建以下的表,修改为使用INNODB而不是MYISAM
  6 -- 你需要在数据库中执行以下的sql脚本
  7 DROP TABLE IF EXISTS QRTZ_FIRED_TRIGGERS;
  8 DROP TABLE IF EXISTS QRTZ_PAUSED_TRIGGER_GRPS;
  9 DROP TABLE IF EXISTS QRTZ_SCHEDULER_STATE;
 10 DROP TABLE IF EXISTS QRTZ_LOCKS;
 11 DROP TABLE IF EXISTS QRTZ_SIMPLE_TRIGGERS;
 12 DROP TABLE IF EXISTS QRTZ_SIMPROP_TRIGGERS;
 13 DROP TABLE IF EXISTS QRTZ_CRON_TRIGGERS;
 14 DROP TABLE IF EXISTS QRTZ_BLOB_TRIGGERS;
 15 DROP TABLE IF EXISTS QRTZ_TRIGGERS;
 16 DROP TABLE IF EXISTS QRTZ_JOB_DETAILS;
 17 DROP TABLE IF EXISTS QRTZ_CALENDARS;
 18 -- 存储每一个已配置的Job的详细信息
 19 CREATE TABLE QRTZ_JOB_DETAILS(
 20 SCHED_NAME VARCHAR(120) NOT NULL,
 21 JOB_NAME VARCHAR(200) NOT NULL,
 22 JOB_GROUP VARCHAR(200) NOT NULL,
 23 DESCRIPTION VARCHAR(250) NULL,
 24 JOB_CLASS_NAME VARCHAR(250) NOT NULL,
 25 IS_DURABLE VARCHAR(1) NOT NULL,
 26 IS_NONCONCURRENT VARCHAR(1) NOT NULL,
 27 IS_UPDATE_DATA VARCHAR(1) NOT NULL,
 28 REQUESTS_RECOVERY VARCHAR(1) NOT NULL,
 29 JOB_DATA BLOB NULL,
 30 PRIMARY KEY (SCHED_NAME,JOB_NAME,JOB_GROUP))
 31 ENGINE=InnoDB;
 32 -- 存储已配置的Trigger的信息
 33 CREATE TABLE QRTZ_TRIGGERS (
 34 SCHED_NAME VARCHAR(120) NOT NULL,
 35 TRIGGER_NAME VARCHAR(200) NOT NULL,
 36 TRIGGER_GROUP VARCHAR(200) NOT NULL,
 37 JOB_NAME VARCHAR(200) NOT NULL,
 38 JOB_GROUP VARCHAR(200) NOT NULL,
 39 DESCRIPTION VARCHAR(250) NULL,
 40 NEXT_FIRE_TIME BIGINT(13) NULL,
 41 PREV_FIRE_TIME BIGINT(13) NULL,
 42 PRIORITY INTEGER NULL,
 43 TRIGGER_STATE VARCHAR(16) NOT NULL,
 44 TRIGGER_TYPE VARCHAR(8) NOT NULL,
 45 START_TIME BIGINT(13) NOT NULL,
 46 END_TIME BIGINT(13) NULL,
 47 CALENDAR_NAME VARCHAR(200) NULL,
 48 MISFIRE_INSTR SMALLINT(2) NULL,
 49 JOB_DATA BLOB NULL,
 50 PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP),
 51 FOREIGN KEY (SCHED_NAME,JOB_NAME,JOB_GROUP)
 52 REFERENCES QRTZ_JOB_DETAILS(SCHED_NAME,JOB_NAME,JOB_GROUP))
 53 ENGINE=InnoDB;
 54 -- 存储已配置的Simple Trigger的信息
 55 CREATE TABLE QRTZ_SIMPLE_TRIGGERS (
 56 SCHED_NAME VARCHAR(120) NOT NULL,
 57 TRIGGER_NAME VARCHAR(200) NOT NULL,
 58 TRIGGER_GROUP VARCHAR(200) NOT NULL,
 59 REPEAT_COUNT BIGINT(7) NOT NULL,
 60 REPEAT_INTERVAL BIGINT(12) NOT NULL,
 61 TIMES_TRIGGERED BIGINT(10) NOT NULL,
 62 PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP),
 63 FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
 64 REFERENCES QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP))
 65 ENGINE=InnoDB;
 66 -- 存储Cron Trigger,包括Cron表达式和时区信息
 67 CREATE TABLE QRTZ_CRON_TRIGGERS (
 68 SCHED_NAME VARCHAR(120) NOT NULL,
 69 TRIGGER_NAME VARCHAR(200) NOT NULL,
 70 TRIGGER_GROUP VARCHAR(200) NOT NULL,
 71 CRON_EXPRESSION VARCHAR(120) NOT NULL,
 72 TIME_ZONE_ID VARCHAR(80),
 73 PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP),
 74 FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
 75 REFERENCES QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP))
 76 ENGINE=InnoDB;
 77 CREATE TABLE QRTZ_SIMPROP_TRIGGERS
 78   (
 79     SCHED_NAME VARCHAR(120) NOT NULL,
 80     TRIGGER_NAME VARCHAR(200) NOT NULL,
 81     TRIGGER_GROUP VARCHAR(200) NOT NULL,
 82     STR_PROP_1 VARCHAR(512) NULL,
 83     STR_PROP_2 VARCHAR(512) NULL,
 84     STR_PROP_3 VARCHAR(512) NULL,
 85     INT_PROP_1 INT NULL,
 86     INT_PROP_2 INT NULL,
 87     LONG_PROP_1 BIGINT NULL,
 88     LONG_PROP_2 BIGINT NULL,
 89     DEC_PROP_1 NUMERIC(13,4) NULL,
 90     DEC_PROP_2 NUMERIC(13,4) NULL,
 91     BOOL_PROP_1 VARCHAR(1) NULL,
 92     BOOL_PROP_2 VARCHAR(1) NULL,
 93     PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP),
 94     FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
 95     REFERENCES QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP))
 96 ENGINE=InnoDB;
 97 --  Trigger作为Blob类型存储(用于Quartz用户用JDBC创建他们自己定制的Trigger类型,JobStore并不知道如何存储实例的时候)
 98 CREATE TABLE QRTZ_BLOB_TRIGGERS (
 99 SCHED_NAME VARCHAR(120) NOT NULL,
100 TRIGGER_NAME VARCHAR(200) NOT NULL,
101 TRIGGER_GROUP VARCHAR(200) NOT NULL,
102 BLOB_DATA BLOB NULL,
103 PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP),
104 INDEX (SCHED_NAME,TRIGGER_NAME, TRIGGER_GROUP),
105 FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
106 REFERENCES QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP))
107 ENGINE=InnoDB;
108 -- 以Blob类型存储Quartz的Calendar日历信息,quartz可配置一个日历来指定一个时间范围
109 CREATE TABLE QRTZ_CALENDARS (
110 SCHED_NAME VARCHAR(120) NOT NULL,
111 CALENDAR_NAME VARCHAR(200) NOT NULL,
112 CALENDAR BLOB NOT NULL,
113 PRIMARY KEY (SCHED_NAME,CALENDAR_NAME))
114 ENGINE=InnoDB;
115 -- 存储已暂停的Trigger组的信息
116 CREATE TABLE QRTZ_PAUSED_TRIGGER_GRPS (
117 SCHED_NAME VARCHAR(120) NOT NULL,
118 TRIGGER_GROUP VARCHAR(200) NOT NULL,
119 PRIMARY KEY (SCHED_NAME,TRIGGER_GROUP))
120 ENGINE=InnoDB;
121 -- 存储与已触发的Trigger相关的状态信息,以及相联Job的执行信息
122 CREATE TABLE QRTZ_FIRED_TRIGGERS (
123 SCHED_NAME VARCHAR(120) NOT NULL,
124 ENTRY_ID VARCHAR(95) NOT NULL,
125 TRIGGER_NAME VARCHAR(200) NOT NULL,
126 TRIGGER_GROUP VARCHAR(200) NOT NULL,
127 INSTANCE_NAME VARCHAR(200) NOT NULL,
128 FIRED_TIME BIGINT(13) NOT NULL,
129 SCHED_TIME BIGINT(13) NOT NULL,
130 PRIORITY INTEGER NOT NULL,
131 STATE VARCHAR(16) NOT NULL,
132 JOB_NAME VARCHAR(200) NULL,
133 JOB_GROUP VARCHAR(200) NULL,
134 IS_NONCONCURRENT VARCHAR(1) NULL,
135 REQUESTS_RECOVERY VARCHAR(1) NULL,
136 PRIMARY KEY (SCHED_NAME,ENTRY_ID))
137 ENGINE=InnoDB;
138 -- 存储少量的有关 Scheduler的状态信息,和别的 Scheduler 实例(假如是用于一个集群中)
139 CREATE TABLE QRTZ_SCHEDULER_STATE (
140 SCHED_NAME VARCHAR(120) NOT NULL,
141 INSTANCE_NAME VARCHAR(200) NOT NULL,
142 LAST_CHECKIN_TIME BIGINT(13) NOT NULL,
143 CHECKIN_INTERVAL BIGINT(13) NOT NULL,
144 PRIMARY KEY (SCHED_NAME,INSTANCE_NAME))
145 ENGINE=InnoDB;
146 -- 存储程序的非观锁的信息(假如使用了悲观锁)
147 CREATE TABLE QRTZ_LOCKS (
148 SCHED_NAME VARCHAR(120) NOT NULL,
149 LOCK_NAME VARCHAR(40) NOT NULL,
150 PRIMARY KEY (SCHED_NAME,LOCK_NAME))
151 ENGINE=InnoDB;
152 CREATE INDEX IDX_QRTZ_J_REQ_RECOVERY ON QRTZ_JOB_DETAILS(SCHED_NAME,REQUESTS_RECOVERY);
153 CREATE INDEX IDX_QRTZ_J_GRP ON QRTZ_JOB_DETAILS(SCHED_NAME,JOB_GROUP);
154 CREATE INDEX IDX_QRTZ_T_J ON QRTZ_TRIGGERS(SCHED_NAME,JOB_NAME,JOB_GROUP);
155 CREATE INDEX IDX_QRTZ_T_JG ON QRTZ_TRIGGERS(SCHED_NAME,JOB_GROUP);
156 CREATE INDEX IDX_QRTZ_T_C ON QRTZ_TRIGGERS(SCHED_NAME,CALENDAR_NAME);
157 CREATE INDEX IDX_QRTZ_T_G ON QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_GROUP);
158 CREATE INDEX IDX_QRTZ_T_STATE ON QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_STATE);
159 CREATE INDEX IDX_QRTZ_T_N_STATE ON QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP,TRIGGER_STATE);
160 CREATE INDEX IDX_QRTZ_T_N_G_STATE ON QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_GROUP,TRIGGER_STATE);
161 CREATE INDEX IDX_QRTZ_T_NEXT_FIRE_TIME ON QRTZ_TRIGGERS(SCHED_NAME,NEXT_FIRE_TIME);
162 CREATE INDEX IDX_QRTZ_T_NFT_ST ON QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_STATE,NEXT_FIRE_TIME);
163 CREATE INDEX IDX_QRTZ_T_NFT_MISFIRE ON QRTZ_TRIGGERS(SCHED_NAME,MISFIRE_INSTR,NEXT_FIRE_TIME);
164 CREATE INDEX IDX_QRTZ_T_NFT_ST_MISFIRE ON QRTZ_TRIGGERS(SCHED_NAME,MISFIRE_INSTR,NEXT_FIRE_TIME,TRIGGER_STATE);
165 CREATE INDEX IDX_QRTZ_T_NFT_ST_MISFIRE_GRP ON QRTZ_TRIGGERS(SCHED_NAME,MISFIRE_INSTR,NEXT_FIRE_TIME,TRIGGER_GROUP,TRIGGER_STATE);
166 CREATE INDEX IDX_QRTZ_FT_TRIG_INST_NAME ON QRTZ_FIRED_TRIGGERS(SCHED_NAME,INSTANCE_NAME);
167 CREATE INDEX IDX_QRTZ_FT_INST_JOB_REQ_RCVRY ON QRTZ_FIRED_TRIGGERS(SCHED_NAME,INSTANCE_NAME,REQUESTS_RECOVERY);
168 CREATE INDEX IDX_QRTZ_FT_J_G ON QRTZ_FIRED_TRIGGERS(SCHED_NAME,JOB_NAME,JOB_GROUP);
169 CREATE INDEX IDX_QRTZ_FT_JG ON QRTZ_FIRED_TRIGGERS(SCHED_NAME,JOB_GROUP);
170 CREATE INDEX IDX_QRTZ_FT_T_G ON QRTZ_FIRED_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP);
171 CREATE INDEX IDX_QRTZ_FT_TG ON QRTZ_FIRED_TRIGGERS(SCHED_NAME,TRIGGER_GROUP);
172 commit;

 


3.新建完Quartz系统表之后,你需要做SpringBoot和Quartz的相关配置
application.yml
 1 quartz:
 2   enabled: true
 3 server:
 4   port: 9090
 5 spring:
 6   datasource:
 7     url: jdbc:mysql://localhost:3306/spring_quartz
 8     username: yourname
 9     password: yourpassword
10     tomcat:
11       initialSize: 20
12       maxActive: 100
13       maxIdle: 100
14       minIdle: 20
15       maxWait: 10000
16       testWhileIdle: true
17       testOnBorrow: false
18       testOnReturn: false

 

quartz.properties文件, (注 : 这里如果使用yml文件格式的配置,里面的配置会失效;spring-boot-starter-quartz已经有全新的支持,感兴趣的道友可以参考文章:https://www.jianshu.com/p/056281e057b3)
 1 #ID设置为自动获取 每一个必须不同 (所有调度器实例中是唯一的)
 2 org.quartz.scheduler.instanceId=AUTO
 3 #指定调度程序的主线程是否应该是守护线程
 4 org.quartz.scheduler.makeSchedulerThreadDaemon=true
 5 #ThreadPool实现的类名
 6 org.quartz.threadPool.class=org.quartz.simpl.SimpleThreadPool
 7 #ThreadPool配置线程守护进程
 8 org.quartz.threadPool.makeThreadsDaemons=true
 9 #线程数量
10 org.quartz.threadPool.threadCount:20
11 #线程优先级
12 org.quartz.threadPool.threadPriority:5
13 #数据保存方式为持久化
14 org.quartz.jobStore.class=org.quartz.impl.jdbcjobstore.JobStoreTX
15 #StdJDBCDelegate说明支持集群
16 org.quartz.jobStore.driverDelegateClass=org.quartz.impl.jdbcjobstore.StdJDBCDelegate
17 #quartz内部表的前缀
18 org.quartz.jobStore.tablePrefix=QRTZ_
19 #是否加入集群
20 org.quartz.jobStore.isClustered=true
21 #容许的最大作业延长时间
22 org.quartz.jobStore.misfireThreshold=25000

 

4.需要调度数据库中的Job实例定义。
JobEntity.class
package com.ealen.entity;
import javax.persistence.*;
import java.io.Serializable;
/**
 * Created by EalenXie on 2018/6/4 14:09
 * 这里个人示例,可自定义相关属性
 */
@Entity
@Table(name = "JOB_ENTITY")
public class JobEntity implements Serializable {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Integer id;
    private String name;          //job名称
    private String group;         //job组名
    private String cron;          //执行的cron
    private String parameter;     //job的参数
    private String description;   //job描述信息
    @Column(name = "vm_param")
    private String vmParam;       //vm参数
    @Column(name = "jar_path")
    private String jarPath;       //job的jar路径,在这里我选择的是定时执行一些可执行的jar包
    private String status;        //job的执行状态,这里我设置为OPEN/CLOSE且只有该值为OPEN才会执行该Job
    public JobEntity() {
    }
    public Integer getId() {
        return id;
    }
    public void setId(Integer id) {
        this.id = id;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public String getGroup() {
        return group;
    }
    public void setGroup(String group) {
        this.group = group;
    }
    public String getCron() {
        return cron;
    }
    public void setCron(String cron) {
        this.cron = cron;
    }
    public String getParameter() {
        return parameter;
    }
    public void setParameter(String parameter) {
        this.parameter = parameter;
    }
    public String getDescription() {
        return description;
    }
    public void setDescription(String description) {
        this.description = description;
    }
    public String getVmParam() {
        return vmParam;
    }
    public void setVmParam(String vmParam) {
        this.vmParam = vmParam;
    }
    public String getJarPath() {
        return jarPath;
    }
    public void setJarPath(String jarPath) {
        this.jarPath = jarPath;
    }
    public String getStatus() {
        return status;
    }
    public void setStatus(String status) {
        this.status = status;
    }
    @Override
    public String toString() {
        return "JobEntity{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", group='" + group + '\'' +
                ", cron='" + cron + '\'' +
                ", parameter='" + parameter + '\'' +
                ", description='" + description + '\'' +
                ", vmParam='" + vmParam + '\'' +
                ", jarPath='" + jarPath + '\'' +
                ", status='" + status + '\'' +
                '}';
    }
    //新增Builder模式,可选,选择设置任意属性初始化对象
    public JobEntity(Builder builder) {
        id = builder.id;
        name = builder.name;
        group = builder.group;
        cron = builder.cron;
        parameter = builder.parameter;
        description = builder.description;
        vmParam = builder.vmParam;
        jarPath = builder.jarPath;
        status = builder.status;
    }
    public static class Builder {
        private Integer id;
        private String name = "";          //job名称
        private String group = "";         //job组名
        private String cron = "";          //执行的cron
        private String parameter = "";     //job的参数
        private String description = "";   //job描述信息
        private String vmParam = "";       //vm参数
        private String jarPath = "";       //job的jar路径
        private String status = "";        //job的执行状态,只有该值为OPEN才会执行该Job
        public Builder withId(Integer i) {
            id = i;
            return this;
        }
        public Builder withName(String n) {
            name = n;
            return this;
        }
        public Builder withGroup(String g) {
            group = g;
            return this;
        }
        public Builder withCron(String c) {
            cron = c;
            return this;
        }
        public Builder withParameter(String p) {
            parameter = p;
            return this;
        }
        public Builder withDescription(String d) {
            description = d;
            return this;
        }
        public Builder withVMParameter(String vm) {
            vmParam = vm;
            return this;
        }
        public Builder withJarPath(String jar) {
            jarPath = jar;
            return this;
        }
        public Builder withStatus(String s) {
            status = s;
            return this;
        }
        public JobEntity newJobEntity() {
            return new JobEntity(this);
        }
    }
}

 

为了方便测试,设计好表之后先插入几条记录,job_entity.sql,相关sql语句如下: 
SET FOREIGN_KEY_CHECKS=0;
DROP TABLE IF EXISTS `job_entity`;
CREATE TABLE `job_entity` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `name` varchar(255) DEFAULT NULL,
  `group` varchar(255) DEFAULT NULL,
  `cron` varchar(255) DEFAULT NULL,
  `parameter` varchar(255) NOT NULL,
  `description` varchar(255) DEFAULT NULL,
  `vm_param` varchar(255) DEFAULT NULL,
  `jar_path` varchar(255) DEFAULT NULL,
  `status` varchar(255) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=7 DEFAULT CHARSET=utf8;
INSERT INTO `job_entity` VALUES ('1',  'first', 'helloworld', '0/2 * * * * ? ', '1', '第一个', '', null, 'OPEN');
INSERT INTO `job_entity` VALUES ('2',  'second', 'helloworld', '0/5 * * * * ? ', '2', '第二个', null, null, 'OPEN');
INSERT INTO `job_entity` VALUES ('3',  'third', 'helloworld', '0/15 * * * * ? ', '3', '第三个', null, null, 'OPEN');
INSERT INTO `job_entity` VALUES ('4',  'four', 'helloworld', '0 0/1 * * * ? *', '4', '第四个', null, null, 'CLOSE');
INSERT INTO `job_entity` VALUES ('5',  'OLAY Job', 'Nomal', '0 0/2 * * * ?', '5', '第五个', null, 'C:\\EalenXie\\Download\\JDE-Order-1.0-SNAPSHOT.jar', 'CLOSE');

 

Quartz的核心配置类 : ConfigureQuartz.class
package com.ealen.config;
import org.quartz.spi.JobFactory;
import org.quartz.spi.TriggerFiredBundle;
import org.springframework.beans.factory.config.AutowireCapableBeanFactory;
import org.springframework.beans.factory.config.PropertiesFactoryBean;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.ClassPathResource;
import org.springframework.scheduling.quartz.SchedulerFactoryBean;
import org.springframework.scheduling.quartz.SpringBeanJobFactory;
import javax.sql.DataSource;
import java.io.IOException;
import java.util.Properties;
/**
 * Created by EalenXie on 2018/6/4 11:02
 * Quartz的核心配置类
 */
@Configuration
public class ConfigureQuartz {
    //配置JobFactory
    @Bean
    public JobFactory jobFactory(ApplicationContext applicationContext) {
        AutowiringSpringBeanJobFactory jobFactory = new AutowiringSpringBeanJobFactory();
        jobFactory.setApplicationContext(applicationContext);
        return jobFactory;
    }
    /**
     * SchedulerFactoryBean这个类的真正作用提供了对org.quartz.Scheduler的创建与配置,并且会管理它的生命周期与Spring同步。
     * org.quartz.Scheduler: 调度器。所有的调度都是由它控制。
     * @param dataSource 为SchedulerFactory配置数据源
     * @param jobFactory 为SchedulerFactory配置JobFactory
     */
    @Bean
    public SchedulerFactoryBean schedulerFactoryBean(DataSource dataSource, JobFactory jobFactory) throws IOException {
        SchedulerFactoryBean factory = new SchedulerFactoryBean();
        //可选,QuartzScheduler启动时更新己存在的Job,这样就不用每次修改targetObject后删除qrtz_job_details表对应记录
        factory.setOverwriteExistingJobs(true);
        factory.setAutoStartup(true); //设置自行启动
        factory.setDataSource(dataSource);
        factory.setJobFactory(jobFactory);
        factory.setQuartzProperties(quartzProperties());
        return factory;
    }
    //从quartz.properties文件中读取Quartz配置属性
    @Bean
    public Properties quartzProperties() throws IOException {
        PropertiesFactoryBean propertiesFactoryBean = new PropertiesFactoryBean();
        propertiesFactoryBean.setLocation(new ClassPathResource("/quartz.properties"));
        propertiesFactoryBean.afterPropertiesSet();
        return propertiesFactoryBean.getObject();
    }
    //配置JobFactory,为quartz作业添加自动连接支持
    public final class AutowiringSpringBeanJobFactory extends SpringBeanJobFactory implements
            ApplicationContextAware {
        private transient AutowireCapableBeanFactory beanFactory;
        @Override
        public void setApplicationContext(final ApplicationContext context) {
            beanFactory = context.getAutowireCapableBeanFactory();
        }
        @Override
        protected Object createJobInstance(final TriggerFiredBundle bundle) throws Exception {
            final Object job = super.createJobInstance(bundle);
            beanFactory.autowireBean(job);
            return job;
        }
    }
}

 

写一个dao访问数据库,JobEntityRepository.class
package com.ealen.dao;
import com.ealen.entity.JobEntity;
import org.springframework.data.repository.CrudRepository;
/**
 * Created by EalenXie on 2018/6/4 14:27
 */
public interface JobEntityRepository extends CrudRepository<JobEntity, Long> {
    JobEntity getById(Integer id);
}

 

5.定义调度中心执行逻辑的Job,这个Job的作用就是按照数据库中的Job的逻辑,规则去执行数据库中的Job。
这里使用了一个自定义的枚举工具类StringUtils , StringUtils.class : 
package com.ealen.util;
import java.util.List;
import java.util.Map;
/**
 * Created by EalenXie on 2018/6/4 14:20
 * 自定义枚举单例对象 StringUtil
 */
public enum StringUtils {
    getStringUtil;
    //是否为空
    public boolean isEmpty(String str) {
        return (str == null) || (str.length() == 0) || (str.equals(""));
    }
    //去空格
    public String trim(String str) {
        return str == null ? null : str.trim();
    }
    //获取Map参数值
    public String getMapString(Map<String, String> map) {
        String result = "";
        for (Map.Entry entry : map.entrySet()) {
            result += entry.getValue() + " ";
        }
        return result;
    }
    //获取List参数值
    public String getListString(List<String> list) {
        String result = "";
        for (String s : list) {
            result += s + " ";
        }
        return result;
    }
}

 

调度中心执行逻辑的Job。DynamicJob.class :  
 1 package com.ealen.job;
 2 import com.ealen.util.StringUtils;
 3 import org.quartz.*;
 4 import org.slf4j.Logger;
 5 import org.slf4j.LoggerFactory;
 6 import org.springframework.stereotype.Component;
 7 import java.io.*;
 8 import java.util.ArrayList;
 9 import java.util.List;
10 /**
11  * Created by EalenXie on 2018/6/4 14:29
12  * :@DisallowConcurrentExecution : 此标记用在实现Job的类上面,意思是不允许并发执行.
13  * :注意org.quartz.threadPool.threadCount线程池中线程的数量至少要多个,否则@DisallowConcurrentExecution不生效
14  * :假如Job的设置时间间隔为3秒,但Job执行时间是5秒,设置@DisallowConcurrentExecution以后程序会等任务执行完毕以后再去执行,否则会在3秒时再启用新的线程执行
15  */
16 @DisallowConcurrentExecution
17 @Component
18 public class DynamicJob implements Job {
19     private Logger logger = LoggerFactory.getLogger(DynamicJob.class);
20     /**
21      * 核心方法,Quartz Job真正的执行逻辑.
22      * @param executorContext executorContext JobExecutionContext中封装有Quartz运行所需要的所有信息
23      * @throws JobExecutionException execute()方法只允许抛出JobExecutionException异常
24      */
25     @Override
26     public void execute(JobExecutionContext executorContext) throws JobExecutionException {
27         //JobDetail中的JobDataMap是共用的,从getMergedJobDataMap获取的JobDataMap是全新的对象
28         JobDataMap map = executorContext.getMergedJobDataMap();
29         String jarPath = map.getString("jarPath");
30         String parameter = map.getString("parameter");
31         String vmParam = map.getString("vmParam");
32         logger.info("Running Job name : {} ", map.getString("name"));
33         logger.info("Running Job description : " + map.getString("JobDescription"));
34         logger.info("Running Job group: {} ", map.getString("group"));
35         logger.info("Running Job cron : " + map.getString("cronExpression"));
36         logger.info("Running Job jar path : {} ", jarPath);
37         logger.info("Running Job parameter : {} ", parameter);
38         logger.info("Running Job vmParam : {} ", vmParam);
39         long startTime = System.currentTimeMillis();
40         if (!StringUtils.getStringUtil.isEmpty(jarPath)) {
41             File jar = new File(jarPath);
42             if (jar.exists()) {
43                 ProcessBuilder processBuilder = new ProcessBuilder();
44                 processBuilder.directory(jar.getParentFile());
45                 List<String> commands = new ArrayList<>();
46                 commands.add("java");
47                 if (!StringUtils.getStringUtil.isEmpty(vmParam)) commands.add(vmParam);
48                 commands.add("-jar");
49                 commands.add(jarPath);
50                 if (!StringUtils.getStringUtil.isEmpty(parameter)) commands.add(parameter);
51                 processBuilder.command(commands);
52                 logger.info("Running Job details as follows >>>>>>>>>>>>>>>>>>>>: ");
53                 logger.info("Running Job commands : {}  ", StringUtils.getStringUtil.getListString(commands));
54                 try {
55                     Process process = processBuilder.start();
56                     logProcess(process.getInputStream(), process.getErrorStream());
57                 } catch (IOException e) {
58                     throw new JobExecutionException(e);
59                 }
60             } else throw new JobExecutionException("Job Jar not found >>  " + jarPath);
61         }
62         long endTime = System.currentTimeMillis();
63         logger.info(">>>>>>>>>>>>> Running Job has been completed , cost time :  " + (endTime - startTime) + "ms\n");
64     }
65     //打印Job执行内容的日志
66     private void logProcess(InputStream inputStream, InputStream errorStream) throws IOException {
67         String inputLine;
68         String errorLine;
69         BufferedReader inputReader = new BufferedReader(new InputStreamReader(inputStream));
70         BufferedReader errorReader = new BufferedReader(new InputStreamReader(errorStream));
71         while ((inputLine = inputReader.readLine()) != null) logger.info(inputLine);
72         while ((errorLine = errorReader.readLine()) != null) logger.error(errorLine);
73     }
74 }

 

6.为了方便控制Job的运行,为调度中心添加相关的业务逻辑 : 

DynamicJobService.class  :

 1 package com.ealen.service;
 2 import com.ealen.dao.JobEntityRepository;
 3 import com.ealen.entity.JobEntity;
 4 import com.ealen.job.DynamicJob;
 5 import org.quartz.*;
 6 import org.springframework.beans.factory.annotation.Autowired;
 7 import org.springframework.stereotype.Service;
 8 import java.util.ArrayList;
 9 import java.util.List;
10 /**
11  * Created by EalenXie on 2018/6/4 14:25
12  */
13 @Service
14 public class DynamicJobService {
15     @Autowired
16     private JobEntityRepository repository;
17     //通过Id获取Job
18     public JobEntity getJobEntityById(Integer id) {
19         return repository.getById(id);
20     }
21     //从数据库中加载获取到所有Job
22     public List<JobEntity> loadJobs() {
23         List<JobEntity> list = new ArrayList<>();
24         repository.findAll().forEach(list::add);
25         return list;
26     }
27     //获取JobDataMap.(Job参数对象)
28     public JobDataMap getJobDataMap(JobEntity job) {
29         JobDataMap map = new JobDataMap();
30         map.put("name", job.getName());
31         map.put("group", job.getGroup());
32         map.put("cronExpression", job.getCron());
33         map.put("parameter", job.getParameter());
34         map.put("JobDescription", job.getDescription());
35         map.put("vmParam", job.getVmParam());
36         map.put("jarPath", job.getJarPath());
37         map.put("status", job.getStatus());
38         return map;
39     }
40     //获取JobDetail,JobDetail是任务的定义,而Job是任务的执行逻辑,JobDetail里会引用一个Job Class来定义
41     public JobDetail geJobDetail(JobKey jobKey, String description, JobDataMap map) {
42         return JobBuilder.newJob(DynamicJob.class)
43                 .withIdentity(jobKey)
44                 .withDescription(description)
45                 .setJobData(map)
46                 .storeDurably()
47                 .build();
48     }
49     //获取Trigger (Job的触发器,执行规则)
50     public Trigger getTrigger(JobEntity job) {
51         return TriggerBuilder.newTrigger()
52                 .withIdentity(job.getName(), job.getGroup())
53                 .withSchedule(CronScheduleBuilder.cronSchedule(job.getCron()))
54                 .build();
55     }
56     //获取JobKey,包含Name和Group
57     public JobKey getJobKey(JobEntity job) {
58         return JobKey.jobKey(job.getName(), job.getGroup());
59     }
60 }

 

JobController.class :
package com.ealen.web;

import com.ealen.entity.JobEntity;
import com.ealen.service.DynamicJobService;
import org.quartz.*;
import org.quartz.impl.matchers.GroupMatcher;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.quartz.SchedulerFactoryBean;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.annotation.PostConstruct;
import java.util.Set;

/**
 * Created by EalenXie on 2018/6/4 16:12
 */
@RestController
public class JobController {

    private static final Logger logger = LoggerFactory.getLogger(JobController.class);
    @Autowired
    private SchedulerFactoryBean schedulerFactoryBean;
    @Autowired
    private DynamicJobService jobService;

    //初始化启动所有的Job
    @PostConstruct
    public void initialize() {
        try {
            reStartAllJobs();
            logger.info("INIT SUCCESS");
        } catch (SchedulerException e) {
            logger.info("INIT EXCEPTION : " + e.getMessage());
            e.printStackTrace();
        }
    }

    //根据ID重启某个Job
    @RequestMapping("/refresh/{id}")
    public String refresh(@PathVariable Integer id) throws SchedulerException {
        String result;
        JobEntity entity = jobService.getJobEntityById(id);
        if (entity == null) return "error: id is not exist ";
        synchronized (logger) {
            JobKey jobKey = jobService.getJobKey(entity);
            Scheduler scheduler = schedulerFactoryBean.getScheduler();
            scheduler.pauseJob(jobKey);
            scheduler.unscheduleJob(TriggerKey.triggerKey(jobKey.getName(), jobKey.getGroup()));
            scheduler.deleteJob(jobKey);
            JobDataMap map = jobService.getJobDataMap(entity);
            JobDetail jobDetail = jobService.geJobDetail(jobKey, entity.getDescription(), map);
            if (entity.getStatus().equals("OPEN")) {
                scheduler.scheduleJob(jobDetail, jobService.getTrigger(entity));
                result = "Refresh Job : " + entity.getName() + "\t jarPath: " + entity.getJarPath() + " success !";
            } else {
                result = "Refresh Job : " + entity.getName() + "\t jarPath: " + entity.getJarPath() + " failed ! , " +
                        "Because the Job status is " + entity.getStatus();
            }
        }
        return result;
    }


    //重启数据库中所有的Job
    @RequestMapping("/refresh/all")
    public String refreshAll() {
        String result;
        try {
            reStartAllJobs();
            result = "SUCCESS";
        } catch (SchedulerException e) {
            result = "EXCEPTION : " + e.getMessage();
        }
        return "refresh all jobs : " + result;
    }

    /**
     * 重新启动所有的job
     */
    private void reStartAllJobs() throws SchedulerException {
        synchronized (logger) {                                                         //只允许一个线程进入操作
            Scheduler scheduler = schedulerFactoryBean.getScheduler();
            Set<JobKey> set = scheduler.getJobKeys(GroupMatcher.anyGroup());
            scheduler.pauseJobs(GroupMatcher.anyGroup());                               //暂停所有JOB
            for (JobKey jobKey : set) {                                                 //删除从数据库中注册的所有JOB
                scheduler.unscheduleJob(TriggerKey.triggerKey(jobKey.getName(), jobKey.getGroup()));
                scheduler.deleteJob(jobKey);
            }
            for (JobEntity job : jobService.loadJobs()) {                               //从数据库中注册的所有JOB
                logger.info("Job register name : {} , group : {} , cron : {}", job.getName(), job.getGroup(), job.getCron());
                JobDataMap map = jobService.getJobDataMap(job);
                JobKey jobKey = jobService.getJobKey(job);
                JobDetail jobDetail = jobService.geJobDetail(jobKey, job.getDescription(), map);
                if (job.getStatus().equals("OPEN")) scheduler.scheduleJob(jobDetail, jobService.getTrigger(job));
                else
                    logger.info("Job jump name : {} , Because {} status is {}", job.getName(), job.getName(), job.getStatus());
            }
        }
    }
}

7.现在已经完事具备,只欠东风了,写一个启动类,跑一下看看效果。
 1 package com.ealen;
 2 import org.springframework.boot.SpringApplication;
 3 import org.springframework.boot.autoconfigure.SpringBootApplication;
 4 /**
 5  * Created by EalenXie on 2018/6/4 11:00
 6  */
 7 @SpringBootApplication
 8 public class QuartzApplication {
 9     public static void main(String[] args) {
10         SpringApplication.run(QuartzApplication.class, args);
11     }
12 }

 

运行效果如下 : 

 看到这里,那么恭喜你,Quartz集群已经搭建成功了。如果部署该项目应用到多个服务器上面,Job会在多个服务器上面执行,但同一个Job只会在某个服务器上面执行,即如果服务器A在某个时间执行了某个Job,则其他服务器如B,C,D在此时间均不会执行此Job。即不会造成该Job被多次执行。

这里可以看到数据库中的Job已经在Quartz注册并初始化成功了。

这里可看到Scheduler已经在工作了,Job也已经按照cron在定时执行了。

执行一个包含jar的Job,看一下效果,图下已经看到JOB在正常运行了  : 

看到Job执行完成了。

 

 

此时如果在数据库中手动修改某个Job的执行cron,并不会马上生效,则可以调用上面写到的业务方法,/refresh/all,则可刷新所有的Job,或/refresh/{id},刷新某个Job。

 


posted @ 2018-06-04 17:44  EalenXie  阅读(74559)  评论(28编辑  收藏  举报