Quartz提供两种基本作业存储类型:RAMJobStore、JDBC作业存储。
本博客使用jdbc作业存储的方式,同时实现restFul请求对任务启停实现控制,当前应用springboot版本2.4.4。
<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.0modelVersion>
<groupId>org.examplegroupId>
<artifactId>quartz-jobartifactId>
<version>1.0-SNAPSHOTversion>
<properties>
<maven.compiler.source>8maven.compiler.source>
<maven.compiler.target>8maven.compiler.target>
properties>
<parent>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-parentartifactId>
<version>2.4.4version>
<relativePath/>
parent>
<dependencies>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-batchartifactId>
dependency>
<dependency>
<groupId>mysqlgroupId>
<artifactId>mysql-connector-javaartifactId>
<version>8.0.16version>
dependency>
<dependency>
<groupId>com.alibabagroupId>
<artifactId>druidartifactId>
<version>1.1.10version>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-data-redisartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-quartzartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
<dependency>
<groupId>org.mybatis.spring.bootgroupId>
<artifactId>mybatis-spring-boot-starterartifactId>
<version>2.1.4version>
dependency>
<dependency>
<groupId>com.github.pagehelpergroupId>
<artifactId>pagehelperartifactId>
<version>4.1.0version>
dependency>
<dependency>
<groupId>org.projectlombokgroupId>
<artifactId>lombokartifactId>
<optional>trueoptional>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-testartifactId>
<scope>testscope>
dependency>
<dependency>
<groupId>org.springframework.batchgroupId>
<artifactId>spring-batch-testartifactId>
<scope>testscope>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-cacheartifactId>
dependency>
<dependency>
<groupId>org.aspectjgroupId>
<artifactId>aspectjrtartifactId>
dependency>
<dependency>
<groupId>org.aspectjgroupId>
<artifactId>aspectjweaverartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-aopartifactId>
dependency>
<dependency>
<groupId>org.xerialgroupId>
<artifactId>sqlite-jdbcartifactId>
<version>3.32.3.2version>
dependency>
<dependency>
<groupId>cn.hutoolgroupId>
<artifactId>hutool-allartifactId>
<version>5.7.13version>
dependency>
dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-maven-pluginartifactId>
<configuration>
<excludes>
<exclude>
<groupId>org.projectlombokgroupId>
<artifactId>lombokartifactId>
exclude>
excludes>
configuration>
plugin>
plugins>
build>
project>
application.yml
server:
port: 8080
spring:
profiles:
active: dev
#数据库配置mysql数据库
syx:
database:
url: jdbc:mysql://localhost:3306/mydb?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=UTC&allowPublicKeyRetrieval=true
driver: com.mysql.cj.jdbc.Driver
userName: xxxx
passwd: xxx
validationQuery: select 1 from dual
initSize: 3
minIdle: 3
maxActive: 10
checkTable: false
maxWaitTime: 60000
evictionRunTime: 60000
minEvictionIdleTime: 300000
keepAlive: true
testWhileIdle: true
testOnBorrow: true
testOnReturn: false
poolPreparedStatement: true
maxPoolPreparedStatementSizePerConnection: 20
validateQueryTimeOut: 3
application-dev.yml
mybatis:
mapper-locations: classpath:mapper/*Mapper.xml #mapper文件的路径
#showSql
logging:
level:
com:
syx:
dao: debug
package com.syx.microapplication.base;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
/**
* @date: 2022-03-10 23:04
* 数据源配置参数
*/
@Component
@ConfigurationProperties(prefix = "syx.database")
public class DataSourceConfigProperty {
private String url;
private String driver;
private String userName;
private String passwd;
private String validationQuery = "select 1 from dual";
//连接池初始大小
private String initSize = "3";
//最小连接数
private String minIdle = "3";
//最大连接数
private String maxActive ="10";
//是否开启表的初始化检查
private boolean checkTable = false;
//获取连接的最大等待时间单位为毫秒
private long maxWaitTime;
//空闲检测时间单位为毫秒
private long evictionRunTime;
//连接在池中最小生存时间单位毫秒
private long minEvictionIdleTime;
//
private boolean keepAlive;
private boolean testWhileIdle;
//获取连接时是否检测连接有效
private boolean testOnBorrow;
//连接归还时是否检测连接有效
private boolean testOnReturn;
//是否缓存PrepareStatement
private boolean poolPreparedStatement;
//每个连接的 PrepareStatement缓存大小
private int maxPoolPreparedStatementSizePerConnection;
//连接检测超时设置
private int validateQueryTimeOut;
//getters and setters....
}
package com.syx.microapplication.base;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
/**
* @date: 2022-03-11 00:15
*
*/
@Configuration
@EnableAspectJAutoProxy
@ComponentScan(AppConfig.COMPONENT_SCAN)
@MapperScan(AppConfig.MAPPER_SCAN)
public class AppConfig {
//包扫描路径
protected static final String COMPONENT_SCAN = "com.syx";
//mybatis的dao路径
protected static final String MAPPER_SCAN = "com.syx.**.dao";
}
package com.syx.microapplication.base;
import com.alibaba.druid.pool.DruidDataSource;
import com.alibaba.druid.support.http.StatViewServlet;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.web.servlet.ServletRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.transaction.annotation.EnableTransactionManagement;
import javax.sql.DataSource;
import java.util.ArrayList;
import java.util.List;
/**
* @date: 2022-03-10 23:01
* 数据源配置
*/
@Configuration
@EnableTransactionManagement
@ConditionalOnClass(DataSource.class)
public class DataSourceConfig {
@Autowired
private DataSourceConfigProperty dataSourceConfigProperty;
@Bean
@Primary
public DruidDataSource dataSource(){
DruidDataSource druidDataSource = new DruidDataSource();
druidDataSource.setUrl(dataSourceConfigProperty.getUrl());
druidDataSource.setDriverClassName(dataSourceConfigProperty.getDriver());
druidDataSource.setUsername(dataSourceConfigProperty.getUserName());
druidDataSource.setPassword(dataSourceConfigProperty.getPasswd());
druidDataSource.setInitialSize(Integer.parseInt(dataSourceConfigProperty.getInitSize()));
druidDataSource.setMinIdle(Integer.parseInt(dataSourceConfigProperty.getMinIdle()));
druidDataSource.setMaxActive(Integer.parseInt(dataSourceConfigProperty.getMaxActive()));
druidDataSource.setMaxWait(dataSourceConfigProperty.getMaxWaitTime());
druidDataSource.setTimeBetweenEvictionRunsMillis(dataSourceConfigProperty.getEvictionRunTime());
druidDataSource.setMinEvictableIdleTimeMillis(dataSourceConfigProperty.getMinEvictionIdleTime());
druidDataSource.setTestWhileIdle(dataSourceConfigProperty.isTestWhileIdle());
druidDataSource.setTestOnBorrow(dataSourceConfigProperty.isTestOnBorrow());
druidDataSource.setTestOnReturn(dataSourceConfigProperty.isTestOnReturn());
druidDataSource.setValidationQuery(dataSourceConfigProperty.getValidationQuery());
return druidDataSource;
}
/**
* druid监控平台
* @return
*/
@Bean
public ServletRegistrationBean<StatViewServlet> servletRegistrationBean(){
ServletRegistrationBean<StatViewServlet> registrationBean = new ServletRegistrationBean<>();
StatViewServlet servlet = new StatViewServlet();
registrationBean.setServlet(servlet);
registrationBean.setName("druidView");
registrationBean.setLoadOnStartup(2);
List<String> urlMapping = new ArrayList<>();
urlMapping.add("/druid/*");
registrationBean.setUrlMappings(urlMapping);
return registrationBean;
}
}
package com.syx.microapplication.base;
import org.quartz.*;
import org.quartz.impl.matchers.GroupMatcher;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.ImportResource;
import org.springframework.context.annotation.PropertySource;
import org.springframework.core.io.ClassPathResource;
import org.springframework.scheduling.quartz.SchedulerFactoryBean;
import javax.sql.DataSource;
import java.util.List;
import java.util.Set;
/**
* @date: 2022-03-10 22:28
*
* *
*/
@Configuration
@AutoConfigureAfter(DataSource.class)
@ImportResource(value = {
"classpath*:configJobs/*ApplicationContext.xml"
})
@PropertySource(value = {
"classpath:quartz.properties"
})
public class QuartzConfig {
private static final Logger LOGGER = LoggerFactory.getLogger(QuartzConfig.class);
@Bean
public SchedulerFactoryBean schedulerFactoryBean(DataSource dataSource,QuartzTriggers quartzTriggers){
SchedulerFactoryBean schedulerFactoryBean = new SchedulerFactoryBean(){
@Override
public void afterPropertiesSet() throws Exception {
super.afterPropertiesSet();
//项目启动时打印任务信息
triggerMsgPrint(super.getScheduler());
}
};
//配置数据源
schedulerFactoryBean.setDataSource(dataSource);
//设置quartz.properties
schedulerFactoryBean.setConfigLocation(new ClassPathResource("quartz.properties"));
//设置任务自启动
schedulerFactoryBean.setAutoStartup(true);
//设置调度器名称
schedulerFactoryBean.setSchedulerName("SYX_SCHEDULER");
//triggers维护在configJobs/*ApplicationContext.xml的文件中
schedulerFactoryBean.setTriggers(quartzTriggers.getTriggers());
schedulerFactoryBean.setOverwriteExistingJobs(true);
return schedulerFactoryBean;
}
/**
* 打印trigger信息
* @param scheduler
*/
private void triggerMsgPrint(Scheduler scheduler) throws SchedulerException {
List<String> groupNames = scheduler.getTriggerGroupNames();
for (String groupName : groupNames) {
Set<TriggerKey> triggerKeys = scheduler.getTriggerKeys(GroupMatcher.triggerGroupEquals(groupName));
for (TriggerKey triggerKey : triggerKeys) {
Trigger trigger = scheduler.getTrigger(triggerKey);
JobDetail jobDetail = scheduler.getJobDetail(trigger.getJobKey());
//在使用java配置job时需要指定key为JobName
LOGGER.info("任务名->{}",jobDetail.getJobDataMap().getString("JobName"));
LOGGER.info("任务描述->{}",jobDetail.getDescription());
//需要指定 默认指定为0
LOGGER.info("重复执行次数->{}",jobDetail.getJobDataMap().getString("RepeatTime"));
//需要指定 默认为10ms 维护在JobDataMap
LOGGER.info("重跑时间间隔->{}",jobDetail.getJobDataMap().getString("RepeatInterval"));
if(trigger instanceof CronTrigger){
CronTrigger temp = (CronTrigger) trigger;
LOGGER.info("cron表达式->{}",temp.getCronExpression());
LOGGER.info("开始执行时间->{}",temp.getStartTime());
}
if(trigger instanceof SimpleTrigger){
SimpleTrigger temp = (SimpleTrigger) trigger;
LOGGER.info("触发时间->{}",temp.getFinalFireTime());
LOGGER.info("开始执行时间->{}",temp.getStartTime());
}
LOGGER.info("下次执行时间->{}",trigger.getNextFireTime());
}
}
}
}
package com.syx.microapplication.base;
import org.quartz.Trigger;
import java.util.List;
/**
* @date: 2022-03-11 20:49
* 维护triggers
*/
public class QuartzTriggers {
private List<Trigger> triggers;
public Trigger[] getTriggers(){
Trigger[] arr = new Trigger[this.triggers.size()];
for (int i = 0; i < this.triggers.size(); i++) {
arr[i] = this.triggers.get(i);
}
return arr;
}
public void setTriggers(List<Trigger> triggers) {
this.triggers = triggers;
}
}
<import resource="classpath*:configJobs/jobs/*ApplicationContext.xml"/>
<bean id="triggerCollection" class="com.syx.microapplication.base.QuartzTriggers">
<property name="triggers">
<list>
<ref bean="TestJobTrigger"/>
list>
property>
bean>
beans>
package com.syx.microapplication;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.quartz.QuartzAutoConfiguration;
/**
- @date: 2022-03-10 23:49
-
*/
@SpringBootApplication(exclude = {QuartzAutoConfiguration.class})
public class MainApp {
public static void main(String[] args) {
SpringApplication.run(MainApp.class,args);
}
}
package com.syx.demon.job;
import org.quartz.Job;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
/**
* @date: 2022-03-11 21:53
*
*/
public class TestJob implements Job {
@Override
public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {
System.out.println("Hello world...");
}
}
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="TestJob" class="com.syx.demon.job.TestJob"/>
<bean id="TestJobName" class="org.springframework.scheduling.quartz.JobDetailFactoryBean">
<property name="description" value="TestJob测试"/>
<property name="group" value="TestJobGroup"/>
<property name="jobClass" value="com.syx.demon.job.TestJob"/>
<property name="durability" value="true"/>
<property name="jobDataAsMap">
<map>
<entry key="JobName" value="TestJob"/>
<entry key="RepeatTime" value="0"/>
<entry key="RepeatInterval" value="10"/>
map>
property>
bean>
<bean id="TestJobTrigger" class="org.springframework.scheduling.quartz.CronTriggerFactoryBean">
<property name="jobDetail" ref="TestJobName"/>
<property name="cronExpression" value="*/30 * * * * ?"/>
<property name="misfireInstruction" value="2"/>
bean>
beans>
控制台打印了任务信息,同时任务也执行并输出了hello world…
package com.syx.microapplication.system.service;
import com.syx.microapplication.system.bean.QuartzJobDetail;
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.stereotype.Service;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
/**
* @date: 2022-03-12 16:20
*
*/
@Service
public class QuartzSchedulerService {
@Autowired
private Scheduler scheduler;
private static final Logger LOGGER = LoggerFactory.getLogger(QuartzSchedulerService.class);
/**
* 查询所有任务
* @return
* @throws SchedulerException
*/
public List<QuartzJobDetail> getJobDetail() throws SchedulerException {
List<QuartzJobDetail> result = new ArrayList<>();
List<String> groupNames = scheduler.getJobGroupNames();
for (String groupName : groupNames) {
Set<JobKey> jobKeys = scheduler.getJobKeys(GroupMatcher.jobGroupEquals(groupName));
for (JobKey jobKey : jobKeys) {
JobDetail jobDetail = scheduler.getJobDetail(jobKey);
QuartzJobDetail jobInfo = new QuartzJobDetail();
jobInfo.setSchedulerName(scheduler.getSchedulerName());
jobInfo.setSchedulerInstanceId(scheduler.getSchedulerInstanceId());
jobInfo.setJobName(jobKey.getName());
jobInfo.setDescription(jobDetail.getDescription());
jobInfo.setDurability(jobDetail.isDurable());
jobInfo.setRecover(jobDetail.requestsRecovery());
jobInfo.setGroup(jobKey.getGroup());
jobInfo.setJobClass(jobDetail.getJobClass().getName());
List<? extends Trigger> triggers = scheduler.getTriggersOfJob(jobKey);
if(triggers!=null && !triggers.isEmpty()){
for (Trigger trigger : triggers) {
jobInfo.setState(scheduler.getTriggerState(trigger.getKey()).name());
jobInfo.setTriggersNum(triggers.size());
jobInfo.setNextFireTime(trigger.getNextFireTime());
}
}else{
jobInfo.setState("NONE");
}
result.add(jobInfo);
}
}
return result;
}
/**
* 删除job
* @param jobName
* @param jobGroup
* @throws SchedulerException
*/
public void deleteJob(String jobName,String jobGroup) throws SchedulerException {
scheduler.deleteJob(JobKey.jobKey(jobName,jobGroup));
}
/**
* 重启job
* @param jobName
* @param jobGroup
* @throws SchedulerException
*/
public void resumeJob(String jobName,String jobGroup) throws SchedulerException {
scheduler.resumeJob(JobKey.jobKey(jobName,jobGroup));
}
/**
* 暂停job
* @param jobName
* @param jobGroup
* @throws SchedulerException
*/
public void pause(String jobName,String jobGroup) throws SchedulerException {
scheduler.pauseJob(JobKey.jobKey(jobName,jobGroup));
}
/**
* 立即执行
* @param jobName
* @param jobGroup
* @throws SchedulerException
*/
public void triggerNow(String jobName,String jobGroup) throws SchedulerException {
scheduler.triggerJob(JobKey.jobKey(jobName,jobGroup));
}
}
package com.syx.microapplication.system.bean;
import com.syx.utils.IDUtil;
import java.util.Date;
import java.util.Map;
/**
* @date: 2022-03-12 15:40
* quartzJob的描述信息
*/
public class QuartzJobDetail {
private String description;
private boolean durability;
private String group;
private String jobClass;
private Map<String,Object> jobDataMap;
private String jobName;
private Date nextFireTime;
private int triggersNum;
private String quartzInstanceId;
private String schedulerInstanceId;
private String schedulerName;
private boolean recover;
private String state;
private String uuid=IDUtil.generateId();
//getters and setters
}
package com.syx.microapplication.system.controller;
import com.syx.microapplication.system.bean.QuartzJobDetail;
import com.syx.microapplication.system.service.QuartzSchedulerService;
import org.quartz.SchedulerException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.util.List;
/**
* @date: 2022-03-12 15:31
*
*/
@RestController
@RequestMapping("/quartzJob")
public class QuartzJobController {
@Autowired
private QuartzSchedulerService quartzSchedulerService;
private static final Logger LOGGER = LoggerFactory.getLogger(QuartzJobController.class);
@GetMapping
public List<QuartzJobDetail> getJobDetail() throws SchedulerException {
return quartzSchedulerService.getJobDetail();
}
/**
* 删除job
* @param jobName
* @param jobGroup
* @throws SchedulerException
*/
@DeleteMapping
public void deleteJob(String jobName,String jobGroup) throws SchedulerException {
quartzSchedulerService.deleteJob(jobName,jobGroup);
LOGGER.info("删除任务{}成功",jobName);
}
/**
* 重启job
* @param jobName
* @param jobGroup
* @throws SchedulerException
*/
@GetMapping(path = "{jobName}/{jobGroup}/resume")
public void resumeJob(@PathVariable(value = "jobName") String jobName,
@PathVariable(value = "jobGroup") String jobGroup) throws SchedulerException {
quartzSchedulerService.resumeJob(jobName,jobGroup);
LOGGER.info("重启任务{}成功",jobName);
}
/**
* 暂停job
* @param jobName
* @param jobGroup
* @throws SchedulerException
*/
@GetMapping(path = "{jobName}/{jobGroup}/pause")
public void pause(@PathVariable(value = "jobName") String jobName,
@PathVariable(value = "jobGroup") String jobGroup) throws SchedulerException {
quartzSchedulerService.pause(jobName,jobGroup);
LOGGER.info("暂停任务{}成功",jobName);
}
/**
* 立即执行
* @param jobName
* @param jobGroup
* @throws SchedulerException
*/
@GetMapping(path = "{jobName}/{jobGroup}/triggerNow")
public void triggerNow(@PathVariable(value = "jobName") String jobName,
@PathVariable(value = "jobGroup") String jobGroup) throws SchedulerException {
quartzSchedulerService.triggerNow(jobName,jobGroup);
LOGGER.info("任务{}启动成功",jobName);
}
}