在开发中有很多定时任务都不是写死的而是可以人为配置并且写到数据库中的,下面简单的分享一个SpringBoot整合QUARTZ并嵌入可视化界面的demo。
Step1. 在数据库中建立有关quartz的表,我用的是 mySql 数据库,建表语句如下,如果是其他的数据库可以自己去网上下载:
#
# Quartz seems to work best with the driver mm.mysql-2.0.7-bin.jar
#
# PLEASE consider using mysql with innodb tables to avoid locking issues
#
# In your Quartz properties file, you'll need to set
# org.quartz.jobStore.driverDelegateClass = org.quartz.impl.jdbcjobstore.StdJDBCDelegate
#
DROP TABLE IF EXISTS QRTZ_FIRED_TRIGGERS;
DROP TABLE IF EXISTS QRTZ_PAUSED_TRIGGER_GRPS;
DROP TABLE IF EXISTS QRTZ_SCHEDULER_STATE;
DROP TABLE IF EXISTS QRTZ_LOCKS;
DROP TABLE IF EXISTS QRTZ_SIMPLE_TRIGGERS;
DROP TABLE IF EXISTS QRTZ_SIMPROP_TRIGGERS;
DROP TABLE IF EXISTS QRTZ_CRON_TRIGGERS;
DROP TABLE IF EXISTS QRTZ_BLOB_TRIGGERS;
DROP TABLE IF EXISTS QRTZ_TRIGGERS;
DROP TABLE IF EXISTS QRTZ_JOB_DETAILS;
DROP TABLE IF EXISTS QRTZ_CALENDARS;
CREATE TABLE QRTZ_JOB_DETAILS
(
SCHED_NAME VARCHAR(120) NOT NULL,
JOB_NAME VARCHAR(200) NOT NULL,
JOB_GROUP VARCHAR(200) NOT NULL,
DESCRIPTION VARCHAR(250) NULL,
JOB_CLASS_NAME VARCHAR(250) NOT NULL,
IS_DURABLE VARCHAR(1) NOT NULL,
IS_NONCONCURRENT VARCHAR(1) NOT NULL,
IS_UPDATE_DATA VARCHAR(1) NOT NULL,
REQUESTS_RECOVERY VARCHAR(1) NOT NULL,
JOB_DATA BLOB NULL,
PRIMARY KEY (SCHED_NAME,JOB_NAME,JOB_GROUP)
);
CREATE TABLE QRTZ_TRIGGERS
(
SCHED_NAME VARCHAR(120) NOT NULL,
TRIGGER_NAME VARCHAR(200) NOT NULL,
TRIGGER_GROUP VARCHAR(200) NOT NULL,
JOB_NAME VARCHAR(200) NOT NULL,
JOB_GROUP VARCHAR(200) NOT NULL,
DESCRIPTION VARCHAR(250) NULL,
NEXT_FIRE_TIME BIGINT(13) NULL,
PREV_FIRE_TIME BIGINT(13) NULL,
PRIORITY INTEGER NULL,
TRIGGER_STATE VARCHAR(16) NOT NULL,
TRIGGER_TYPE VARCHAR(8) NOT NULL,
START_TIME BIGINT(13) NOT NULL,
END_TIME BIGINT(13) NULL,
CALENDAR_NAME VARCHAR(200) NULL,
MISFIRE_INSTR SMALLINT(2) NULL,
JOB_DATA BLOB NULL,
PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP),
FOREIGN KEY (SCHED_NAME,JOB_NAME,JOB_GROUP)
REFERENCES QRTZ_JOB_DETAILS(SCHED_NAME,JOB_NAME,JOB_GROUP)
);
CREATE TABLE QRTZ_SIMPLE_TRIGGERS
(
SCHED_NAME VARCHAR(120) NOT NULL,
TRIGGER_NAME VARCHAR(200) NOT NULL,
TRIGGER_GROUP VARCHAR(200) NOT NULL,
REPEAT_COUNT BIGINT(7) NOT NULL,
REPEAT_INTERVAL BIGINT(12) NOT NULL,
TIMES_TRIGGERED BIGINT(10) NOT NULL,
PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP),
FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
REFERENCES QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
);
CREATE TABLE QRTZ_CRON_TRIGGERS
(
SCHED_NAME VARCHAR(120) NOT NULL,
TRIGGER_NAME VARCHAR(200) NOT NULL,
TRIGGER_GROUP VARCHAR(200) NOT NULL,
CRON_EXPRESSION VARCHAR(200) NOT NULL,
TIME_ZONE_ID VARCHAR(80),
PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP),
FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
REFERENCES QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
);
CREATE TABLE QRTZ_SIMPROP_TRIGGERS
(
SCHED_NAME VARCHAR(120) NOT NULL,
TRIGGER_NAME VARCHAR(200) NOT NULL,
TRIGGER_GROUP VARCHAR(200) NOT NULL,
STR_PROP_1 VARCHAR(512) NULL,
STR_PROP_2 VARCHAR(512) NULL,
STR_PROP_3 VARCHAR(512) NULL,
INT_PROP_1 INT NULL,
INT_PROP_2 INT NULL,
LONG_PROP_1 BIGINT NULL,
LONG_PROP_2 BIGINT NULL,
DEC_PROP_1 NUMERIC(13,4) NULL,
DEC_PROP_2 NUMERIC(13,4) NULL,
BOOL_PROP_1 VARCHAR(1) NULL,
BOOL_PROP_2 VARCHAR(1) NULL,
PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP),
FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
REFERENCES QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
);
CREATE TABLE QRTZ_BLOB_TRIGGERS
(
SCHED_NAME VARCHAR(120) NOT NULL,
TRIGGER_NAME VARCHAR(200) NOT NULL,
TRIGGER_GROUP VARCHAR(200) NOT NULL,
BLOB_DATA BLOB NULL,
PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP),
FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
REFERENCES QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
);
CREATE TABLE QRTZ_CALENDARS
(
SCHED_NAME VARCHAR(120) NOT NULL,
CALENDAR_NAME VARCHAR(200) NOT NULL,
CALENDAR BLOB NOT NULL,
PRIMARY KEY (SCHED_NAME,CALENDAR_NAME)
);
CREATE TABLE QRTZ_PAUSED_TRIGGER_GRPS
(
SCHED_NAME VARCHAR(120) NOT NULL,
TRIGGER_GROUP VARCHAR(200) NOT NULL,
PRIMARY KEY (SCHED_NAME,TRIGGER_GROUP)
);
CREATE TABLE QRTZ_FIRED_TRIGGERS
(
SCHED_NAME VARCHAR(120) NOT NULL,
ENTRY_ID VARCHAR(95) NOT NULL,
TRIGGER_NAME VARCHAR(200) NOT NULL,
TRIGGER_GROUP VARCHAR(200) NOT NULL,
INSTANCE_NAME VARCHAR(200) NOT NULL,
FIRED_TIME BIGINT(13) NOT NULL,
SCHED_TIME BIGINT(13) NOT NULL,
PRIORITY INTEGER NOT NULL,
STATE VARCHAR(16) NOT NULL,
JOB_NAME VARCHAR(200) NULL,
JOB_GROUP VARCHAR(200) NULL,
IS_NONCONCURRENT VARCHAR(1) NULL,
REQUESTS_RECOVERY VARCHAR(1) NULL,
PRIMARY KEY (SCHED_NAME,ENTRY_ID)
);
CREATE TABLE QRTZ_SCHEDULER_STATE
(
SCHED_NAME VARCHAR(120) NOT NULL,
INSTANCE_NAME VARCHAR(200) NOT NULL,
LAST_CHECKIN_TIME BIGINT(13) NOT NULL,
CHECKIN_INTERVAL BIGINT(13) NOT NULL,
PRIMARY KEY (SCHED_NAME,INSTANCE_NAME)
);
CREATE TABLE QRTZ_LOCKS
(
SCHED_NAME VARCHAR(120) NOT NULL,
LOCK_NAME VARCHAR(40) NOT NULL,
PRIMARY KEY (SCHED_NAME,LOCK_NAME)
);
commit;
Step2.新建一个springBoot项目并导入依赖
pom.xml 文件的内容如下:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.example.demo</groupId>
<artifactId>QuartzDemo</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>war</packaging>
<name>demo-quartz</name>
<description>Demo project for quartz</description>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.0.3.RELEASE</version> <!-- lookup parent from repository -->
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-logging</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-quartz</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>1.3.2</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-log4j2</artifactId>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId>
</dependency>
<dependency>
<groupId>com.lmax</groupId>
<artifactId>disruptor</artifactId>
<version>3.3.4</version>
</dependency>
<dependency>
<groupId>javax.mail</groupId>
<artifactId>mail</artifactId>
<version>1.4.7</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.9</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.9</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>com.ds.tech</groupId>
<artifactId>dstech3-utility</artifactId>
<version>3.1.7</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
<groupId>com.aliyun</groupId>
<artifactId>aliyun-java-sdk-dysmsapi</artifactId>
<version>1.1.0</version>
</dependency>
<dependency>
<groupId>com.aliyun</groupId>
<artifactId>aliyun-java-sdk-core</artifactId>
<version>3.2.8</version>
</dependency>
<dependency>
<groupId>com.aliyun.oss</groupId>
<artifactId>aliyun-sdk-oss</artifactId>
<version>2.2.1</version>
</dependency>
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper</artifactId>
<version>5.0.0</version>
</dependency>
</dependencies>
<build>
<resources>
<resource>
<directory>src/main/java</directory>
<includes>
<include>**/*.*</include>
</includes>
<excludes>
<exclude>**/*.java</exclude>
</excludes>
</resource>
<resource>
<directory>src/main/resources</directory>
<includes>
<include>**/*.*</include>
</includes>
</resource>
</resources>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
其中下面的这个依赖是Quartz 所必须的
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-quartz</artifactId>
</dependency>
Step3. 在配置文件中进行配置,application.properties 的内容如下:
#服务端口号
server.port=8082
#以Tomcat为web容器时的字符编码
server.tomcat.uri-encoding=UTF-8
spring.http.encoding.force=true
spring.http.encoding.enabled=true
# 数据源配置
spring.datasource.url=jdbc:mysql://localhost:3306/AthenaDB?useUnicode=true&characterEncoding=utf-8
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.username=root
spring.datasource.password=123456
#连接池配置
#spring.datasource.type=org.apache.commons.dbcp2.BasicDataSource
spring.datasource.type=com.alibaba.druid.pool.DruidDataSource
spring.datasource.initialSize=5
spring.datasource.minIdle=5
spring.datasource.maxActive=20
spring.datasource.maxWait=60000
spring.datasource.timeBetweenEvictionRunsMillis=60000
spring.datasource.minEvictableIdleTimeMillis=300000
spring.datasource.validationQuery=SELECT 1 FROM DUAL
spring.datasource.testWhileIdle=true
spring.datasource.testOnBorrow=false
spring.datasource.testOnReturn=false
spring.datasource.poolPreparedStatements=true
spring.datasource.maxPoolPreparedStatementPerConnectionSize=20
spring.datasource.connectionProperties=characterEncoding=utf8;druid.stat.mergeSql=true;druid.stat.slowSqlMillis=5000
# 合并多个DruidDataSource的监控数据
#spring.datasource.useGlobalDataSourceStat=true
#spring.datasource.filters=stat,wall,log4j
#mybaits mapper位置设置
mybatis.mapper-locations=classpath*:com/example/demo/mapper/rmdb/*.xml
#mybatis
mybatis.type-aliases-package=com.example.demo.entity
#访问日志路径
#server.tomcat.accesslog.directory=
#出现错误时, 直接抛出异常,自定义异常页面使用
spring.mvc.throw-exception-if-no-handler-found=true
#不要为我们工程中的资源文件建立映射,自定义异常页面使用
spring.resources.add-mappings=false
#应用名称,一般就是项目名称,这个名称在SpringCloud中比较关键
spring.application.name=dscms
#http请求的字符编码
spring.http.encoding.charset=UTF-8
#配置在使用Thymeleaf做页面模板时的前缀,即页面所在路径
spring.thymeleaf.prefix=classpath:/templates
#设置在使用Thymeleaf做页面模板时的后缀,其实默认就是html
spring.thymeleaf.suffix=.html
#spring.thymeleaf.mode=LEGACYHTML5
spring.thymeleaf.mode=HTML
#设置在使用Thymeleaf做页面模板时是否启用缓存
spring.thymeleaf.cache=false
#设置编码格式
spring.thymeleaf.encoding=UTF-8
#设置静态资源的请求路径
#spring.mvc.static-path-pattern=/**
#指定静态资源的路径
#spring.resources.static-locations=classpath:/static/,classpath:/public/
#禁止对外提供Spring MBeans
spring.jmx.enabled=false
Step4. 在resources文件夹下新建一个 quartz.properties 的配置文件,这样就不会走它自带的quartz.properties的配置文件,配置文件的内容如下:
#作业实例名称
org.quartz.scheduler.instanceName: DefaultQuartzScheduler
#如果使用集群,instanceId必须唯一,设置成AUTO
org.quartz.scheduler.instanceId = AUTO
org.quartz.scheduler.rmi.export: false
org.quartz.scheduler.rmi.proxy: false
org.quartz.scheduler.wrapJobExecutionInUserTransaction: false
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
#============================================================================
# Configure JobStore
#============================================================================
#计划任务存储在内存中
#org.quartz.jobStore.class: org.quartz.simpl.RAMJobStore
#存储方式使用JobStoreTX(持久化到数据库中)
org.quartz.jobStore.class:org.quartz.impl.jdbcjobstore.JobStoreTX
org.quartz.jobStore.driverDelegateClass:org.quartz.impl.jdbcjobstore.StdJDBCDelegate
#使用自己的配置文件
org.quartz.jobStore.useProperties:true
#数据库中quartz表的表名前缀
org.quartz.jobStore.tablePrefix:QRTZ_
org.quartz.jobStore.dataSource:qzDS
#是否使用集群(如果项目只部署到 一台服务器,就不用了)
org.quartz.jobStore.isClustered = false
#============================================================================
# Configure Datasources
#============================================================================
#配置数据源
org.quartz.dataSource.qzDS.connectionProvider.class:com.example.demo.common.duird.DruidConnectionProvider
org.quartz.dataSource.qzDS.driver = com.mysql.jdbc.Driver
org.quartz.dataSource.qzDS.URL = jdbc:mysql://localhost:3306/AthenaDB?characterEncoding=utf-8
org.quartz.dataSource.qzDS.user:root
org.quartz.dataSource.qzDS.password:123456
org.quartz.dataSource.qzDS.maxConnection:10
Step5. 自定义数据源,因为要持久化到数据库,所以要用ConnectionProvider自定以数据源,代码如下
package com.example.demo.common.duird;
import java.sql.Connection;
import java.sql.SQLException;
import org.quartz.SchedulerException;
import org.quartz.utils.ConnectionProvider;
import com.alibaba.druid.pool.DruidDataSource;
public class DruidConnectionProvider implements ConnectionProvider {
//JDBC驱动
public String driver;
//JDBC连接串
public String URL;
//数据库用户名
public String user;
//数据库用户密码
public String password;
//数据库最大连接数
public int maxConnection;
//数据库SQL查询每次连接返回执行到连接池,以确保它仍然是有效的。
public String validationQuery;
private boolean validateOnCheckout;
private int idleConnectionValidationSeconds;
public String maxCachedStatementsPerConnection;
private String discardIdleConnectionsSeconds;
public static final int DEFAULT_DB_MAX_CONNECTIONS = 10;
public static final int DEFAULT_DB_MAX_CACHED_STATEMENTS_PER_CONNECTION = 120;
//Druid连接池
private DruidDataSource datasource;
/*
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*
* 接口实现
*
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*/
public Connection getConnection() throws SQLException {
return datasource.getConnection();
}
public void shutdown() throws SQLException {
datasource.close();
}
public void initialize() throws SQLException{
if (this.URL == null) {
throw new SQLException("DBPool could not be created: DB URL cannot be null");
}
if (this.driver == null) {
throw new SQLException("DBPool driver could not be created: DB driver class name cannot be null!");
}
if (this.maxConnection < 0) {
throw new SQLException("DBPool maxConnectins could not be created: Max connections must be greater than zero!");
}
datasource = new DruidDataSource();
try{
datasource.setDriverClassName(this.driver);
} catch (Exception e) {
try {
throw new SchedulerException("Problem setting driver class name on datasource: " + e.getMessage(), e);
} catch (SchedulerException e1) {
}
}
datasource.setUrl(this.URL);
datasource.setUsername(this.user);
datasource.setPassword(this.password);
datasource.setMaxActive(this.maxConnection);
datasource.setMinIdle(1);
datasource.setMaxWait(0);
datasource.setMaxPoolPreparedStatementPerConnectionSize(DEFAULT_DB_MAX_CONNECTIONS);
if (this.validationQuery != null) {
datasource.setValidationQuery(this.validationQuery);
if(!this.validateOnCheckout)
datasource.setTestOnReturn(true);
else
datasource.setTestOnBorrow(true);
datasource.setValidationQueryTimeout(this.idleConnectionValidationSeconds);
}
}
/*
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*
* 提供get set方法
*
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*/
public String getDriver() {
return driver;
}
public void setDriver(String driver) {
this.driver = driver;
}
public String getURL() {
return URL;
}
public void setURL(String URL) {
this.URL = URL;
}
public String getUser() {
return user;
}
public void setUser(String user) {
this.user = user;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public int getMaxConnection() {
return maxConnection;
}
public void setMaxConnection(int maxConnection) {
this.maxConnection = maxConnection;
}
public String getValidationQuery() {
return validationQuery;
}
public void setValidationQuery(String validationQuery) {
this.validationQuery = validationQuery;
}
public boolean isValidateOnCheckout() {
return validateOnCheckout;
}
public void setValidateOnCheckout(boolean validateOnCheckout) {
this.validateOnCheckout = validateOnCheckout;
}
public int getIdleConnectionValidationSeconds() {
return idleConnectionValidationSeconds;
}
public void setIdleConnectionValidationSeconds(int idleConnectionValidationSeconds) {
this.idleConnectionValidationSeconds = idleConnectionValidationSeconds;
}
public DruidDataSource getDatasource() {
return datasource;
}
public void setDatasource(DruidDataSource datasource) {
this.datasource = datasource;
}
public String getDiscardIdleConnectionsSeconds() {
return discardIdleConnectionsSeconds;
}
public void setDiscardIdleConnectionsSeconds(String discardIdleConnectionsSeconds) {
this.discardIdleConnectionsSeconds = discardIdleConnectionsSeconds;
}
}
注意:上边的这个数据源的内容是和quartz.properties中的数据源的配置互相配合的
Step6. 自定义一个jobfactory ,其目的是在具体的作业中 需要Spring 注入一些Service,所以要自定义一个jobfactory, 让其在具体job 类实例化时 使用Spring 的API 来进行依赖注入
@Component
public class JobFactory extends AdaptableJobFactory {
/**
* 让不受spring管理的类具有spring自动注入
*/
@Autowired
private AutowireCapableBeanFactory capableBeanFactory;
@Override
protected Object createJobInstance(TriggerFiredBundle bundle) throws Exception {
// 调用父类的方法
Object jobInstance = super.createJobInstance(bundle);
// 进行注入
capableBeanFactory.autowireBean(jobInstance);
return jobInstance;
}
}
Step7.创建调度器
package com.example.demo.common.config;
import java.io.IOException;
import java.util.Properties;
import org.quartz.Scheduler;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.config.PropertiesFactoryBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.ClassPathResource;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.scheduling.quartz.SchedulerFactoryBean;
import com.example.demo.common.factory.JobFactory;
@Configuration
@EnableScheduling
public class SchedulerConfig {
@Autowired
JobFactory jobFactory;
@Bean(name="SchedulerFactory")
public SchedulerFactoryBean schedulerFactoryBean() throws IOException {
SchedulerFactoryBean factory = new SchedulerFactoryBean();
//用于quartz集群,QuartzScheduler 启动时更新己存在的Job,这样就不用每次修改targetObject后删除qrtz_job_details表对应记录了
//factory.setOverwriteExistingJobs(true);
//QuartzScheduler 延时启动,应用启动完10秒后 QuartzScheduler 再启动
//factory.setStartupDelay(10);
factory.setQuartzProperties(quartzProperties()); //作业及数据源配置信息
// 自定义Job Factory,用于Spring注入 service,bin等
factory.setJobFactory(jobFactory);
return factory;
}
@Bean
public Properties quartzProperties() throws IOException {
PropertiesFactoryBean propertiesFactoryBean = new PropertiesFactoryBean();
propertiesFactoryBean.setLocation(new ClassPathResource("/quartz.properties"));
//在quartz.properties中的属性被读取并注入后再初始化对象
propertiesFactoryBean.afterPropertiesSet();
return propertiesFactoryBean.getObject();
}
/*
* quartz初始化监听器
*/
/* @Bean
public QuartzInitializerListener executorListener() {
return new QuartzInitializerListener();
}*/
/*
* 通过SchedulerFactoryBean获取Scheduler的实例
*/
@Bean(name="Scheduler")
public Scheduler scheduler() throws IOException {
return schedulerFactoryBean().getScheduler();
}
}
Step8. 创建一个job_info_config.properties的配置文件,写关于作业的信息
#作业信息配置
demoquartz1_limit=2
demoquartz1_id=1000001
demoquartz1_name="样例作业1";
Step9. 新建一个类读取上面的配置文件中的信息
public class JobInfoConfig {
/**
* 样例作业1的队列查询上限
* @return
*/
public static Integer getDemoQuartz1Limit() {
return ConvertUtil.toInteger(ConfigUtil.getSettings("demoquartz1_limit"));
}
/**
* 样例作业1的id
* @return
*/
public static String getDemoQuartz1Id() {
return ConvertUtil.toString(ConfigUtil.getSettings("demoquartz1_id"));
}
/**
* 样例作业1的名称
* @return
*/
public static String getDemoQuartz1Name() {
return ConvertUtil.toString(ConfigUtil.getSettings("demoquartz1_name"));
}
}
Step10, 新建一个job类
package com.example.demo.job;
import org.quartz.DisallowConcurrentExecution;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import com.example.demo.service.DemoService;
@DisallowConcurrentExecution
public class DemoJob implements IBaseJob {
private final Logger logger = LoggerFactory.getLogger(DemoJob.class);
@Autowired
private DemoService demoService;
@Override
public void execute(JobExecutionContext context)
throws JobExecutionException {
// TODO Auto-generated method stub
try{
demoService.run();
}catch(Exception e){
logger.error(e.toString());
}
}
}
package com.example.demo.job;
import org.quartz.Job;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
public interface IBaseJob extends Job {
public void execute(JobExecutionContext context) throws JobExecutionException;
}
Step11.在service 中写要执行的作业
/**
* 读取作业信息
* @author Administrator
*
*/
public class BaseService {
/**
* 作业编号
*/
protected String jobId;
/**
* 作业名称
*/
protected String jobName;
/**
* 执行成功数量
*/
protected int success=0;
/**
* 执行失败数量
*/
protected int fail = 0;
/**
* 待处理记录总数
*/
protected int total = 0;
/**
* 设置job编号
* @param jid
*/
protected void setJobId(String jId) {
jobId = jId;
}
/**
* 设置job名称
* @param jName
*/
protected void setJobName(String jName) {
jobName = jName;
}
/**
* 开始作业方法
*/
protected void startJob() {
success = 0;
fail=0;
total = 0;
LogWriter.writeJobStartLog(jobId, jobName);
}
/**
* 设置作业待处理总数
* @param tl
*/
protected void setJobTotal(int tl) {
total = tl;
LogWriter.writeJobStartLog(jobId, jobName,total);
}
/**
* 当前项处理状态
* @param currentKey
*/
protected void setSuccessJob(String currentKey) {
success++;
String msg = String.format("jobsrun - jobid:%s jobname:%s key:%s status:success", jobId,jobName,currentKey);
LogWriter.writeWorkLog(msg);
}
/**
* 失败处理
* @param currentKey
*/
protected void setFailJob(String currentKey) {
fail++;
String msg = String.format("jobsrun - jobid:%s jobname:%s key:%s status:fail", jobId,jobName,currentKey);
LogWriter.writeWorkLog(msg);
}
/**
* 结束作业
*/
protected void endJob() {
LogWriter.writeJobEndLog(jobId, jobName, total, success, fail);
}
}
package com.example.demo.service;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.List;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.example.demo.common.config.JobInfoConfig;
import com.example.demo.dao.UserDao;
import com.example.demo.entity.User;
import com.example.demo.entity.UserExample;
@Service
public class DemoService extends BaseService{
private final Logger logger = LoggerFactory.getLogger(DemoService.class);
@Autowired
private UserDao userDao;
public void run(){
//设置作业编号
setJobId(JobInfoConfig.getDemoQuartz1Id());
System.out.println("作业编号:"+JobInfoConfig.getDemoQuartz1Name());
//设置作业名称
setJobName(JobInfoConfig.getDemoQuartz1Name());
System.out.println("作业名称为:"+JobInfoConfig.getDemoQuartz1Name());
//开始作业
startJob();
//获取待处理队列
List<User> list = getQueueList();
if(list.size() == 0){
endJob();
return;
}
//设置待处理的总数
setJobTotal(list.size());
//循环处理内容
for (User user : list) {
queueToDeal(user);
}
//结束作业
endJob();
}
public List<User> getQueueList(){
List<User> list = new ArrayList<User>();
try{
UserExample userExample = new UserExample();
userExample.createCriteria().andBalanceEqualTo(new BigDecimal(0));
userExample.setStart(0);
userExample.setLength(Integer.valueOf(JobInfoConfig.getDemoQuartz1Limit()));
System.out.println("作业上限:"+JobInfoConfig.getDemoQuartz1Limit());
list = userDao.selectByExample(userExample);
}catch(Exception e){
logger.error(jobName,e);
}
return list;
}
public void queueToDeal(User user){
User users = userDao.selectByPrimaryKey(user.getUserId());
try{
if(users != null){
users.setBalance(new BigDecimal(1));
UserExample userExample = new UserExample();
userExample.createCriteria().andUserIdEqualTo(user.getUserId());
if(userDao.updateByExample(users, userExample)<=0){
setFailJob(users.getUserId().toString());
return;
}
setSuccessJob(users.getUserId().toString());
}else{
setFailJob(user.getUserId().toString());
return;
}
}catch(Exception e){
setFailJob(users.getUserId().toString());
logger.error(e.toString());
}
}
}
package com.example.demo.service;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.example.demo.dao.JobAndTriggerDao;
import com.example.demo.entity.JobAndTrigger;
import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;
/**
* 作业配置服务
* 查询所有的作业
*/
@Service
public class JobAndTriggerSevice {
@Autowired
private JobAndTriggerDao jobAndTriggerDao;
/**
* 分页查询
* @param pageNum
* @param pageSize
* @return
*/
public PageInfo<JobAndTrigger> getJobAndTriggerDetails(int pageNum, int pageSize) {
PageHelper.startPage(pageNum, pageSize);
List<JobAndTrigger> list = jobAndTriggerDao.getJobAndTriggerDetails();
PageInfo<JobAndTrigger> page = new PageInfo<JobAndTrigger>(list);
return page;
}
}
Step12. controller 中的代码如下:
package com.example.demo.controller;
import java.util.HashMap;
import java.util.Map;
import org.quartz.CronScheduleBuilder;
import org.quartz.CronTrigger;
import org.quartz.JobBuilder;
import org.quartz.JobDetail;
import org.quartz.JobKey;
import org.quartz.Scheduler;
import org.quartz.SchedulerException;
import org.quartz.TriggerBuilder;
import org.quartz.TriggerKey;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import com.ds.tech.utility.log4j2.LogWriter;
import com.example.demo.entity.JobAndTrigger;
import com.example.demo.job.IBaseJob;
import com.example.demo.service.JobAndTriggerSevice;
import com.github.pagehelper.PageInfo;
@RestController
@RequestMapping(value="/job")
public class JobController {
@Autowired
private JobAndTriggerSevice jobAndTriggerSevice;
//加入Qulifier注解,通过名称注入bean
@Autowired
@Qualifier("Scheduler")
Scheduler scheduler;
public static IBaseJob getClass(String classname) throws Exception
{
Class<?> class1 = Class.forName(classname);
return (IBaseJob)class1.newInstance();
}
@PostMapping(value="/addjob")
public void addJob(@RequestParam(value="jobClassName")String jobClassName,
@RequestParam(value="jobGroupName")String jobGroupName,
@RequestParam(value="cronExpression")String cronExpression,
@RequestParam(value="description")String description) throws Exception {
// 启动调度器
scheduler.start();
//构建job信息
JobDetail jobDetail = JobBuilder.newJob(getClass(jobClassName).getClass()).withIdentity(jobClassName, jobGroupName).withDescription(description).build();
//表达式调度构建器(即任务执行的时间)
CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule(cronExpression);
//按新的cronExpression表达式构建一个新的trigger
CronTrigger trigger = TriggerBuilder.newTrigger().withIdentity(jobClassName, jobGroupName)
.withSchedule(scheduleBuilder).build();
try {
scheduler.scheduleJob(jobDetail, trigger);
} catch (SchedulerException e) {
System.out.println("创建定时任务失败"+e);
//throw new Exception("创建定时任务失败");
}
}
@PostMapping(value="/pausejob")
public void pauseJob(@RequestParam(value="jobClassName")String jobClassName, @RequestParam(value="jobGroupName")String jobGroupName) throws Exception {
try {
scheduler.pauseJob(JobKey.jobKey(jobClassName, jobGroupName));
} catch (SchedulerException e) {
System.out.println("停止定时任务失败"+e);
//throw new Exception("创建定时任务失败");
}
}
@PostMapping(value="/resumejob")
public void resumeJob(@RequestParam(value="jobClassName")String jobClassName, @RequestParam(value="jobGroupName")String jobGroupName) throws Exception {
try {
scheduler.resumeJob(JobKey.jobKey(jobClassName, jobGroupName));
} catch (SchedulerException e) {
System.out.println("继续定时任务失败"+e);
//throw new Exception("创建定时任务失败");
}
}
@PostMapping(value="/deletejob")
public void deleteJob(@RequestParam(value="jobClassName")String jobClassName, @RequestParam(value="jobGroupName")String jobGroupName) throws Exception {
try {
scheduler.pauseTrigger(TriggerKey.triggerKey(jobClassName, jobGroupName));
scheduler.unscheduleJob(TriggerKey.triggerKey(jobClassName, jobGroupName));
scheduler.deleteJob(JobKey.jobKey(jobClassName, jobGroupName));
} catch (SchedulerException e) {
System.out.println("删除定时任务失败"+e);
//throw new Exception("创建定时任务失败");
}
}
/**
* 查询任务列表
* @param pageNum
* @param pageSize
* @return
*/
@GetMapping(value="/queryjob")
public Map<String, Object> queryjob(@RequestParam(value="pageNum")Integer pageNum, @RequestParam(value="pageSize")Integer pageSize)
{
Map<String, Object> map = new HashMap<String, Object>();
try {
PageInfo<JobAndTrigger> jobAndTrigger = jobAndTriggerSevice.getJobAndTriggerDetails(pageNum, pageSize);
map.put("JobAndTrigger", jobAndTrigger);
map.put("number", jobAndTrigger.getTotal());
} catch (Exception e) {
LogWriter.writeErrorLog("查询定时任务列表失败", e);
}
return map;
}
/**
* 修改定时任务
* @param jobClassName
* @param jobGroupName
* @param cronExpression
* @param description
* @throws Exception
*/
@PostMapping(value="/reschedulejob")
public void rescheduleJob(@RequestParam(value="jobClassName")String jobClassName,
@RequestParam(value="jobGroupName")String jobGroupName,
@RequestParam(value="cronExpression")String cronExpression,
@RequestParam(value="description")String description) throws Exception
{
try {
TriggerKey triggerKey = TriggerKey.triggerKey(jobClassName, jobGroupName);
// 表达式调度构建器
CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule(cronExpression);
CronTrigger trigger = (CronTrigger) scheduler.getTrigger(triggerKey);
// 按新的cronExpression表达式重新构建trigger
trigger = trigger.getTriggerBuilder().withIdentity(triggerKey).withSchedule(scheduleBuilder).withDescription(description).build();
// 按新的trigger重新设置job执行
scheduler.rescheduleJob(triggerKey, trigger);
} catch (SchedulerException e) {
LogWriter.writeErrorLog("更新定时任务失败", e);
}
}
}
Step13. 放开页面所在的静态资源,让他可以访问到
package com.example.demo.common.config;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
/**
* Spring mvc 配置
*
*/
@Configuration
@EnableWebMvc
@ComponentScan
public class WebMvcConfig implements WebMvcConfigurer {
/**
* 设置资源
*/
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
//全部静态资源,如果指定全部资源则全局异常捕获无法传递到自定义的error.html页面
//registry.addResourceHandler("/webapp/**").addResourceLocations("classpath:/webapp/");
registry.addResourceHandler("/**").addResourceLocations("classpath:/static/");
WebMvcConfigurer.super.addResourceHandlers(registry);
}
}
Step14. 在static 文件夹下建立一个html,代码如下:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Quartz任务管理</title>
<link rel="stylesheet" href="https://unpkg.com/element-ui@2.0.5/lib/theme-chalk/index.css">
<script src="https://unpkg.com/vue/dist/vue.js"></script>
<script src="https://cdn.bootcss.com/vue-validator/2.1.5/vue-validator.js"></script>
<script src="http://cdn.bootcss.com/vue-resource/1.3.4/vue-resource.js"></script>
<script src="https://unpkg.com/element-ui@2.0.5/lib/index.js"></script>
<style>
#top {
background:#20A0FF;
padding:5px;
overflow:hidden
}
</style>
</head>
<body>
<div id="test">
<div id="top">
<el-button type="text" @click="search" style="color:white">查询</el-button>
<el-button type="text" @click="handleadd" style="color:white">添加</el-button>
</span>
</div>
<br/>
<div style="margin-top:15px">
<el-table
ref="testTable"
:data="tableData"
style="width:100%"
border
>
<el-table-column
prop="job_NAME"
label="任务名称"
sortable
show-overflow-tooltip>
</el-table-column>
<el-table-column
prop="job_GROUP"
label="任务所在组"
width="120"
sortable>
</el-table-column>
<el-table-column
prop="job_CLASS_NAME"
label="任务类名"
sortable>
</el-table-column>
<el-table-column
prop="trigger_NAME"
label="触发器名称"
sortable>
</el-table-column>
<el-table-column
prop="trigger_GROUP"
label="触发器所在组"
width="120"
sortable>
</el-table-column>
<el-table-column
prop="cron_EXPRESSION"
label="表达式"
sortable>
</el-table-column>
<el-table-column
prop="time_ZONE_ID"
label="时区"
sortable>
</el-table-column>
<el-table-column label="操作" width="300">
<template scope="scope">
<el-button
size="small"
type="warning"
@click="handlePause(scope.$index, scope.row)">暂停</el-button>
<el-button
size="small"
type="info"
@click="handleResume(scope.$index, scope.row)">恢复</el-button>
<el-button
size="small"
type="danger"
@click="handleDelete(scope.$index, scope.row)">删除</el-button>
<el-button
size="small"
type="success"
@click="handleUpdate(scope.$index, scope.row)">修改</el-button>
</template>
</el-table-column>
</el-table>
<div align="center">
<el-pagination
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
:current-page="currentPage"
:page-sizes="[10, 20, 30, 40]"
:page-size="pagesize"
layout="total, sizes, prev, pager, next, jumper"
:total="totalCount">
</el-pagination>
</div>
</div>
<el-dialog title="添加任务" :visible.sync="dialogFormVisible" width="500px">
<el-form :model="form">
<el-form-item label="任务名称" label-width="120px" style="width:400px">
<el-input v-model="form.jobName" auto-complete="off"></el-input>
</el-form-item>
<el-form-item label="任务分组" label-width="120px" style="width:400px">
<el-input v-model="form.jobGroup" auto-complete="off"></el-input>
</el-form-item>
<el-form-item label="表达式" label-width="120px" style="width:400px">
<el-input v-model="form.cronExpression" auto-complete="off"></el-input>
</el-form-item>
<el-form-item label="备注信息" label-width="120px" style="width:400px">
<el-input type="textarea" v-model="form.description" auto-complete="off"></el-input>
</el-form-item>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button @click="dialogFormVisible = false">取 消</el-button>
<el-button type="primary" @click="add">确 定</el-button>
</div>
</el-dialog>
<el-dialog title="修改任务" :visible.sync="updateFormVisible" width="500px">
<el-form :model="updateform">
<el-form-item label="表达式" label-width="120px" style="width:400px">
<el-input v-model="updateform.cronExpression" auto-complete="off"></el-input>
</el-form-item>
<el-form-item label="备注信息" label-width="120px" style="width:400px">
<el-input type="textarea" v-model="form.description" auto-complete="off"></el-input>
</el-form-item>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button @click="updateFormVisible = false">取 消</el-button>
<el-button type="primary" @click="update">确 定</el-button>
</div>
</el-dialog>
</div>
<footer align="center">
<p>© Quartz 任务管理</p>
</footer>
<script>
var vue = new Vue({
el:"#test",
data: {
//表格当前页数据
tableData: [],
//请求的URL
url:'job/queryjob',
//默认每页数据量
pagesize: 10,
//当前页码
currentPage: 1,
//查询的页码
start: 1,
//默认数据总数
totalCount: 1000,
//添加对话框默认可见性
dialogFormVisible: false,
//修改对话框默认可见性
updateFormVisible: false,
//提交的表单
form: {
jobName: '',
jobGroup: '',
cronExpression: '',
description:'',
},
updateform: {
jobName: '',
jobGroup: '',
cronExpression: '',
description:'',
},
},
methods: {
//从服务器读取数据
loadData: function(pageNum, pageSize){
this.$http.get('job/queryjob?' + 'pageNum=' + pageNum + '&pageSize=' + pageSize).then(function(res){
console.log(res)
this.tableData = res.body.JobAndTrigger.list;
this.totalCount = res.body.number;
},function(){
console.log('failed');
});
},
//单行删除
handleDelete: function(index, row) {
this.$http.post('job/deletejob',{"jobClassName":row.job_NAME,"jobGroupName":row.job_GROUP},{emulateJSON: true}).then(function(res){
this.loadData( this.currentPage, this.pagesize);
},function(){
console.log('failed');
});
},
//暂停任务
handlePause: function(index, row){
this.$http.post('job/pausejob',{"jobClassName":row.job_NAME,"jobGroupName":row.job_GROUP},{emulateJSON: true}).then(function(res){
this.loadData( this.currentPage, this.pagesize);
},function(){
console.log('failed');
});
},
//恢复任务
handleResume: function(index, row){
this.$http.post('job/resumejob',{"jobClassName":row.job_NAME,"jobGroupName":row.job_GROUP},{emulateJSON: true}).then(function(res){
this.loadData( this.currentPage, this.pagesize);
},function(){
console.log('failed');
});
},
//搜索
search: function(){
this.loadData(this.currentPage, this.pagesize);
},
//弹出对话框
handleadd: function(){
this.dialogFormVisible = true;
},
//添加
add: function(){
this.$http.post('job/addjob',{"jobClassName":this.form.jobName,"jobGroupName":this.form.jobGroup,"cronExpression":this.form.cronExpression,"description":this.form.description},{emulateJSON: true}).then(function(res){
this.loadData(this.currentPage, this.pagesize);
this.dialogFormVisible = false;
},function(){
console.log('failed');
});
this.form.jobName="";
},
//更新
handleUpdate: function(index, row){
console.log(row)
this.updateFormVisible = true;
this.updateform.jobName = row.job_CLASS_NAME;
this.updateform.jobGroup = row.job_GROUP;
},
//更新任务
update: function(){
this.$http.post
('job/reschedulejob',
{"jobClassName":this.updateform.jobName,
"jobGroupName":this.updateform.jobGroup,
"cronExpression":this.updateform.cronExpression,
"description":this.form.description
},{emulateJSON: true}
).then(function(res){
this.loadData(this.currentPage, this.pagesize);
this.updateFormVisible = false;
},function(){
console.log('failed');
});
},
//每页显示数据量变更
handleSizeChange: function(val) {
this.pagesize = val;
this.loadData(this.currentPage, this.pagesize);
},
//页码变更
handleCurrentChange: function(val) {
this.currentPage = val;
this.loadData(this.currentPage, this.pagesize);
},
},
});
//载入数据
vue.loadData(vue.currentPage, vue.pagesize);
</script>
</body>
</html>
这样所有的东西都完成啦,运行一下就好啦,运行效果如下: