背景:最近手头有一个改造迁移的项目,是spring boot1.4.0,迁移过程中新增了一些自动执行任务,用到了quartz,现整理下供参考。
项目环境:spring boot1.4.0+quartz2.3.0+druid1.0.16。小型集群项目。
引言:quartz2.3.0支持集群,spring对quartz有整合。
正文开始:
1、添加依赖
<dependency>
<groupId>org.quartz-scheduler</groupId>
<artifactId>quartz</artifactId>
<version>2.3.0</version>
</dependency>
2、配置quratz
quartz.properties配置文件:
# Default Properties file for use by StdSchedulerFactory
# to create a Quartz Scheduler Instance, if a different
# properties file is not explicitly specified.
#
org.quartz.scheduler.instanceName: MyQuartzScheduler
#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: 1
#org.quartz.threadPool.threadPriority: 5
#org.quartz.threadPool.threadsInheritContextClassLoaderOfInitializingThread: true
#org.quartz.jobStore.misfireThreshold: 60000
org.quartz.jobStore.class: org.quartz.impl.jdbcjobstore.JobStoreTX
org.quartz.jobStore.driverDelegateClass: org.quartz.impl.jdbcjobstore.PostgreSQLDelegate
#集群配置
org.quartz.jobStore.isClustered: true
org.quartz.jobStore.clusterCheckinInterval: 1000org.quartz.jobStore.dataSource = myDS
#========================================================================= ===
# Configure Datasources
#========================================================================= ===
#自定义DruidPoolingConnectionProvider扩展支持druid
org.quartz.dataSource.myDS.connectionProvider.class:cn.bj.ykct.hbyc.manager.quartz.DruidPoolingConnecti onProvider
org.quartz.dataSource.myDS.driver = org.postgresql.Driverorg.quartz.dataSource.myDS.URL = jdbc:postgresql://192.168.135.136:5432/wxhepsbc
org.quartz.dataSource.myDS.user = postgres
org.quartz.dataSource.myDS.password = postgres
org.quartz.dataSource.myDS.maxConnection = 5
项目用的druid连接池,而quartz2.3.0没有对druid实现,但预留了接口,所以我们自己实现。
DruidPoolingConnectionProvider类
package cn.bj.ykct.hbyc.manager.quartz;
import java.sql.Connection;
import java.sql.SQLException;
import org.quartz.utils.ConnectionProvider;
import com.alibaba.druid.pool.DruidDataSource;
/**
* 扩展quartz支持druid连接池
*
* @author Danger
*
*/
public class DruidPoolingConnectionProvider 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;
private DruidDataSource datasource;
@Override
public Connection getConnection() throws SQLException {
// TODO Auto-generated method stub
return datasource.getConnection();
}
@Override
public void shutdown() throws SQLException {
// TODO Auto-generated method stub
datasource.close();
}
@Override
public void initialize() throws SQLException {
// TODO Auto-generated method stub
if (URL == null) {
throw new SQLException("DBPool could not be created: DB URL cannot be null");
}
if (driver == null) {
throw new SQLException(
"DBPool '" + URL + "' could not be created: " + "DB driver class name cannot be null!");
}
if (maxConnection < 0) {
throw new SQLException(
"DBPool '" + URL + "' could not be created: " + "Max connections must be greater than zero!");
}
datasource = new DruidDataSource();
datasource.setDriverClassName(driver);
datasource.setUrl(URL);
datasource.setUsername(user);
datasource.setPassword(password);
datasource.setMaxActive(maxConnection);
datasource.setMinIdle(1);
datasource.setMaxPoolPreparedStatementPerConnectionSize(DEFAULT_DB_MAX_CONNECTIONS);
if (validationQuery != null) {
datasource.setValidationQuery(validationQuery);
if (!this.validateOnCheckout)
datasource.setTestOnReturn(true);
else
datasource.setTestOnBorrow(true);
datasource.setValidationQueryTimeout(this.idleConnectionValidationSeconds);
}
}
--添加 getter setter方法
}
定义JobFactory,quartz获取到spring的bean.
package cn.bj.ykct.hbyc.manager.quartz;
import org.quartz.spi.TriggerFiredBundle;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.config.AutowireCapableBeanFactory;
import org.springframework.scheduling.quartz.AdaptableJobFactory;
import org.springframework.stereotype.Component;
/**
* quartz注入spring bean
* @author Danger
*
*/
@Component
public class JobFactory extends AdaptableJobFactory{
@Autowired
private AutowireCapableBeanFactory capableBeanFactory;
@Override
protected Object createJobInstance(TriggerFiredBundle bundle) throws Exception {
Object jobInstance = super.createJobInstance(bundle);
capableBeanFactory.autowireBean(jobInstance);
return jobInstance;
}
}
定义QuartzConfig类
package cn.bj.ykct.hbyc.manager.quartz;
import java.beans.PropertyVetoException;
import java.io.IOException;
import java.util.Properties;
import javax.sql.DataSource;
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.quartz.SchedulerFactoryBean;
/**
* quartz配置文件
*
* @author Danger
*
*/
@Configuration
public class QuartzConfig {
@Autowired
DataSource dataSource;
@Autowired
JobFactory jobFactory;
@Bean(name = "jobSchedule")
public SchedulerFactoryBean schedulerFactoryBean() throws IOException, PropertyVetoException {
SchedulerFactoryBean schedulerFactoryBean = new SchedulerFactoryBean();
schedulerFactoryBean.setJobFactory(jobFactory);//上文定义的JobFactory类。
schedulerFactoryBean.setOverwriteExistingJobs(true);//重写已经存在的Job
schedulerFactoryBean.setQuartzProperties(properties());
schedulerFactoryBean.setWaitForJobsToCompleteOnShutdown(true);
schedulerFactoryBean.setDataSource(dataSource);//设置数据源
return schedulerFactoryBean;
}
//获取quartz.properties
public Properties properties() throws IOException {
// Properties prop = new Properties();
// prop.load(new
// ClassPathResource("/quartz.properties").getInputStream());
// return prop;
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();
}*/
}
quartz操作类(添加任务、删除任务、查找所有正在运行的任务、暂停任务、修改任务)
package cn.bj.ykct.hbyc.manager.quartz;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.quartz.CronScheduleBuilder;
import org.quartz.CronTrigger;
import org.quartz.DateBuilder;
import org.quartz.DateBuilder.IntervalUnit;
import org.quartz.Job;
import org.quartz.JobBuilder;
import org.quartz.JobDetail;
import org.quartz.JobExecutionContext;
import org.quartz.JobKey;
import org.quartz.Scheduler;
import org.quartz.SchedulerException;
import org.quartz.SimpleScheduleBuilder;
import org.quartz.Trigger;
import org.quartz.TriggerBuilder;
import org.quartz.TriggerKey;
import org.quartz.impl.matchers.GroupMatcher;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
/**
* quartz操作管理类
*
* @author Danger
*
*/
@Service
public class QuartzManager {
@Autowired
private Scheduler sched;
/**
* 增加一个job
*
* @param jobClass
* 任务实现类
* @param jobName
* 任务名称
* @param jobGroupName
* 任务组名
* @param jobCron
* cron表达式(如:0/5 * * * * ? )
*/
public void addJob(Class<? extends Job> jobClass, String jobName, String jobGroupName, String jobCron) {
try {
JobDetail jobDetail = JobBuilder.newJob(jobClass).withIdentity(jobName, jobGroupName).build();
Trigger trigger = TriggerBuilder.newTrigger().withIdentity(jobName, jobGroupName)
.startAt(DateBuilder.futureDate(1, IntervalUnit.SECOND))
.withSchedule(CronScheduleBuilder.cronSchedule(jobCron)).startNow().build();
sched.scheduleJob(jobDetail, trigger);
if (!sched.isShutdown()) {
sched.start();
}
} catch (SchedulerException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
/**
* 创建or更新任务,存在则更新不存在创建
*
* @param jobClass
* 任务类
* @param jobName
* 任务名称
* @param jobGroupName
* 任务组名称
* @param jobCron
* cron表达式
*/
public void addOrUpdateJob(Class<? extends Job> jobClass, String jobName, String jobGroupName, String jobCron) {
try {
TriggerKey triggerKey = TriggerKey.triggerKey(jobName, jobGroupName);
CronTrigger trigger = (CronTrigger) sched.getTrigger(triggerKey);
if (trigger == null) {
addJob(jobClass, jobName, jobGroupName, jobCron);
} else {
if(trigger.getCronExpression().equals(jobCron)){
return;
}
updateJob(jobName, jobGroupName, jobCron);
}
} catch (SchedulerException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
/**
*
* @param jobClass
* @param jobName
* @param jobGroupName
* @param jobTime
*/
public void addJob(Class<? extends Job> jobClass, String jobName, String jobGroupName, int jobTime) {
addJob(jobClass, jobName, jobGroupName, jobTime, -1);
}
public void addJob(Class<? extends Job> jobClass, String jobName, String jobGroupName, int jobTime, int jobTimes) {
try {
JobDetail jobDetail = JobBuilder.newJob(jobClass).withIdentity(jobName, jobGroupName)// 任务名称和组构成任务key
.build();
// 使用simpleTrigger规则
Trigger trigger = null;
if (jobTimes < 0) {
trigger = TriggerBuilder.newTrigger().withIdentity(jobName, jobGroupName)
.withSchedule(SimpleScheduleBuilder.repeatSecondlyForever(1).withIntervalInSeconds(jobTime))
.startNow().build();
} else {
trigger = TriggerBuilder
.newTrigger().withIdentity(jobName, jobGroupName).withSchedule(SimpleScheduleBuilder
.repeatSecondlyForever(1).withIntervalInSeconds(jobTime).withRepeatCount(jobTimes))
.startNow().build();
}
sched.scheduleJob(jobDetail, trigger);
if (!sched.isShutdown()) {
sched.start();
}
} catch (SchedulerException e) {
e.printStackTrace();
}
}
public void updateJob(String jobName, String jobGroupName, String jobTime) {
try {
TriggerKey triggerKey = TriggerKey.triggerKey(jobName, jobGroupName);
CronTrigger trigger = (CronTrigger) sched.getTrigger(triggerKey);
trigger = trigger.getTriggerBuilder().withIdentity(triggerKey)
.withSchedule(CronScheduleBuilder.cronSchedule(jobTime)).build();
// 重启触发器
sched.rescheduleJob(triggerKey, trigger);
} catch (SchedulerException e) {
e.printStackTrace();
}
}
/**
* 删除任务一个job
*
* @param jobName
* 任务名称
* @param jobGroupName
* 任务组名
*/
public void deleteJob(String jobName, String jobGroupName) {
try {
sched.deleteJob(new JobKey(jobName, jobGroupName));
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 暂停一个job
*
* @param jobName
* @param jobGroupName
*/
public void pauseJob(String jobName, String jobGroupName) {
try {
JobKey jobKey = JobKey.jobKey(jobName, jobGroupName);
sched.pauseJob(jobKey);
} catch (SchedulerException e) {
e.printStackTrace();
}
}
/**
* 恢复一个job
*
* @param jobName
* @param jobGroupName
*/
public void resumeJob(String jobName, String jobGroupName) {
try {
JobKey jobKey = JobKey.jobKey(jobName, jobGroupName);
sched.resumeJob(jobKey);
} catch (SchedulerException e) {
e.printStackTrace();
}
}
/**
* 立即执行一个job
*
* @param jobName
* @param jobGroupName
*/
public void runAJobNow(String jobName, String jobGroupName) {
try {
JobKey jobKey = JobKey.jobKey(jobName, jobGroupName);
sched.triggerJob(jobKey);
} catch (SchedulerException e) {
e.printStackTrace();
}
}
/**
* 获取所有计划中的任务列表
*
* @return
*/
public List<Map<String, Object>> queryAllJob() {
List<Map<String, Object>> jobList = null;
try {
GroupMatcher<JobKey> matcher = GroupMatcher.anyJobGroup();
Set<JobKey> jobKeys = sched.getJobKeys(matcher);
jobList = new ArrayList<Map<String, Object>>();
for (JobKey jobKey : jobKeys) {
List<? extends Trigger> triggers = sched.getTriggersOfJob(jobKey);
for (Trigger trigger : triggers) {
Map<String, Object> map = new HashMap<>();
map.put("jobName", jobKey.getName());
map.put("jobGroupName", jobKey.getGroup());
map.put("description", "触发器:" + trigger.getKey());
Trigger.TriggerState triggerState = sched.getTriggerState(trigger.getKey());
map.put("jobStatus", triggerState.name());
if (trigger instanceof CronTrigger) {
CronTrigger cronTrigger = (CronTrigger) trigger;
String cronExpression = cronTrigger.getCronExpression();
map.put("jobTime", cronExpression);
}
jobList.add(map);
}
}
} catch (SchedulerException e) {
e.printStackTrace();
}
return jobList;
}
/**
* 获取所有正在运行的job
*
* @return
*/
public List<Map<String, Object>> queryRunJon() {
List<Map<String, Object>> jobList = null;
try {
List<JobExecutionContext> executingJobs = sched.getCurrentlyExecutingJobs();
jobList = new ArrayList<Map<String, Object>>(executingJobs.size());
for (JobExecutionContext executingJob : executingJobs) {
Map<String, Object> map = new HashMap<String, Object>();
JobDetail jobDetail = executingJob.getJobDetail();
JobKey jobKey = jobDetail.getKey();
Trigger trigger = executingJob.getTrigger();
map.put("jobName", jobKey.getName());
map.put("jobGroupName", jobKey.getGroup());
map.put("description", "触发器:" + trigger.getKey());
Trigger.TriggerState triggerState = sched.getTriggerState(trigger.getKey());
map.put("jobStatus", triggerState.name());
if (trigger instanceof CronTrigger) {
CronTrigger cronTrigger = (CronTrigger) trigger;
String cronExpression = cronTrigger.getCronExpression();
map.put("jobTime", cronExpression);
}
jobList.add(map);
}
} catch (SchedulerException e) {
e.printStackTrace();
}
return jobList;
}
}
3、编写业务
package cn.bj.ykct.hbyc.manager.quartz;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.quartz.QuartzJobBean;
public class TestQuartz extends QuartzJobBean{
@Override
protected void executeInternal(JobExecutionContext context) throws JobExecutionException {
// TODO Auto-generated method stub
//业务代码
}}
4、测试。
创建一个job观察运行情况。
TestQuartz .java
package cn.bj.ykct.hbyc.manager.quartz;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.quartz.QuartzJobBean;
public class TestQuartz extends QuartzJobBean{
@Autowired
TestService testService;
@Override
protected void executeInternal(JobExecutionContext context) throws JobExecutionException {
// TODO Auto-generated method stub
testService.service1();
}
}
TestService.java
package cn.bj.ykct.hbyc.manager.quartz;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.springframework.stereotype.Service;
@Service
public class TestService {
private final static Logger LOGGER = LogManager.getLogger(TestService.class);
public void service1(){
LOGGER.info("test service1");
}
}
项目启动后添加任务并启动任务,本例中是implements ApplicationRunner 。
TestQuartzTrigger.java
package cn.bj.ykct.hbyc.manager.quartz;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.stereotype.Component;
@Component
public class TestQuartzTrigger implements ApplicationRunner {
private final static Logger LOGGER = LogManager.getLogger(TestQuartzTrigger.class);
@Autowired
QuartzManager quartzManager;
@Override
public void run(ApplicationArguments args){
// TODO Auto-generated method stub
LOGGER.info("启动测试testQuartz");
try {
//20秒执行一次
} catch (Exception e) {
// TODO Auto-generated catch block
LOGGER.error("启动测试testQuartz异常、异常信息:{}",e.getMessage());
e.printStackTrace();
}
}
}
启动项目观察job运行情况:
2018-04-18 10:28:44.705 INFO 5244 --- [ main] o.s.s.q.SchedulerFactoryBean : Starting Quartz Scheduler now
2018-04-18 10:28:45.158 INFO 5244 --- [ main] o.s.s.q.LocalDataSourceJobStore : ClusterManager: detected 1 failed or restarted instances.
2018-04-18 10:28:45.158 INFO 5244 --- [ main] o.s.s.q.LocalDataSourceJobStore : ClusterManager: Scanning for instance "NON_CLUSTERED"'s failed in-progress jobs.
2018-04-18 10:28:45.170 INFO 5244 --- [ main] o.s.s.q.LocalDataSourceJobStore : ClusterManager: ......Freed 1 acquired trigger(s).
2018-04-18 10:28:45.174 INFO 5244 --- [ main] o.q.c.QuartzScheduler : Scheduler jobSchedule_$_NON_CLUSTERED started.
2018-04-18 10:28:45.189 INFO 5244 --- [_MisfireHandler] o.s.s.q.LocalDataSourceJobStore : Handling 1 trigger(s) that missed their scheduled fire-time.
2018-04-18 10:28:45.251 INFO 5244 --- [ main] b.c.e.u.UndertowEmbeddedServletContainer : Undertow started on port(s) 9012 (http)
2018-04-18 10:28:45.256 INFO 5244 --- [ main] c.b.y.h.m.q.CheckEtcUserJobTrigger : 项目启动后检查checkEtcUserjob1
2018-04-18 10:28:45.257 INFO 5244 --- [ main] c.b.y.h.m.q.CheckEtcUserJobTrigger : checkEtcUserjob1 开始启动:2018-04-18 10-28-45
2018-04-18 10:28:45.261 INFO 5244 --- [ main] c.b.y.h.m.q.TestQuartzTrigger : 启动测试testQuartz
2018-04-18 10:28:45.267 INFO 5244 --- [ main] c.b.y.h.m.ManagerApplication : Started ManagerApplication in 12.097 seconds (JVM running for 12.989)
2018-04-18 10:28:45.285 INFO 5244 --- [hedule_Worker-1] c.b.y.h.m.q.TestService : test service1
2018-04-18 10:29:00.016 INFO 5244 --- [hedule_Worker-1] c.b.y.h.m.q.TestService : test service1
2018-04-18 10:29:20.040 INFO 5244 --- [hedule_Worker-1] c.b.y.h.m.q.TestService : test service1
2018-04-18 10:29:40.025 INFO 5244 --- [hedule_Worker-1] c.b.y.h.m.q.TestService : test service1
正常运行。
总结:
quartz本身支持集群,它是通过数据库中的表来选择触发任务,从而保证不会多个机器执行同一个任务。
spring boot2.0中完美的提供了quartz的支持,你只需引入
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-quartz</artifactId>
</dependency>
设置是运行在数据库中,application.properties中添加spring.quartz.job-store-type=jdbc
任务类继承QuartzJobBean即可,spring boot会自动查找所有继承QuartzJobBean、实现Job的类。
spring boot2.0整合了quartz,详见下一篇文章。
https://blog.youkuaiyun.com/jieyanqulaopo123/article/details/79991781
正文结束。