Quartz 是个开源的作业调度框架,为在 Java 应用程序中进行作业调度提供了简单却强大的机制。Quartz 允许开发人员根据时间间隔来调度作业。它实现了作业和触发器的多对多关系,还能把多个作业与不同的触发器关联。整合了 Quartz 的应用程序可以重用来自不同事件的作业,还可以为一个事件组合多个作业。这里主要记录一下Spring与Quartz的整合集成使用。
quartz.properties
#============================================================================
# Configure Scheduler
#============================================================================
# 当多个调度器实例在一个程序里时,就需要为客户端代码区别每个调度器。
# 如果使用集群特性,必须为在集群里的每个实例用一样的名字,实现逻辑上一样的调度器。
org.quartz.scheduler.instanceName = QuartzScheduler
# 如果在一个集群里多个实例是一个逻辑上一样的调度器时,每个实例的这项属性必须唯一。
# 可以设置这项为“AUTO”从而自动收集ID。
org.quartz.scheduler.instanceId = AUTO
# 这个属性设置Scheduler在检测到JobStore到某处的连接(比如到数据库的连接)断开后,
# 再次尝试连接所等待的毫秒数。这个参数在使用RamJobStore无效。
org.quartz.scheduler.dbFailureRetryInterval = 1500
# 如果想要连接到远程的调度器服务,要设置为true,指定一个主机和端口号。
org.quartz.scheduler.rmi.proxy = false
#============================================================================
# Configure ThreadPool
#============================================================================
org.quartz.threadPool.class = org.quartz.simpl.SimpleThreadPool
org.quartz.threadPool.threadCount = 10
org.quartz.threadPool.threadPriority = 5
org.quartz.threadPool.threadsInheritContextClassLoaderOfInitializingThread = true
#============================================================================
# Configure JobStore
#============================================================================
# 将Scheduler相关信息保存在RDB中.有两种实现:JobStoreTX和JobStoreCMT
# 前者为Application自己管理事务,后者为Application Server管理事务,即全局事务JTA
org.quartz.jobStore.class = org.quartz.impl.jdbcjobstore.JobStoreTX
# 类似于Hibernate的Dialect,用于处理DB之间的差异,StdJDBCDelegate能满足大部分的DB
org.quartz.jobStore.driverDelegateClass = org.quartz.impl.jdbcjobstore.StdJDBCDelegate
# 设置数据源,数据源将在应用服务器里被配置和管理
org.quartz.jobStore.dataSource = defaultDS
# 设置属性为true是让Quartz不去在JDBC连接上调用setAutoCommit(false)这个函数
org.quartz.jobStore.dontSetAutoCommitFalse = false
# 在触发器被认为没有触发之前,调度器能承受一个触发器再次触发的一个毫秒级数字。
# 最大能忍受的触发超时时间,如果超过则认为“失误”
org.quartz.jobStore.misfireThreshold = 60000
# 数据库中表名前缀
org.quartz.jobStore.tablePrefix = QRTZ_
# JobStore处理未按时触发的Job的数量
org.quartz.jobStore.maxMisfiresToHandleAtATime = 10
# JobDataMaps是否都为String类型
org.quartz.jobStore.useProperties = true
# 是否是应用在集群中,当应用在集群中时必须设置为TRUE,否则会出错
org.quartz.jobStore.isClustered = true
# 集群检测间隔,Scheduler的Checkin时间,时间长短影响Failure Scheduler的发现速度
org.quartz.jobStore.clusterCheckinInterval = 20000
#============================================================================
# Configure DataSource
#============================================================================
org.quartz.dataSource.defaultDS.driver = com.mysql.jdbc.Driver
org.quartz.dataSource.defaultDS.URL = jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=UTF-8
org.quartz.dataSource.defaultDS.user = root
org.quartz.dataSource.defaultDS.password = 123456
org.quartz.dataSource.defaultDS.maxConnections = 10
org.quartz.dataSource.defaultDS.validationQuery = select 1
org.quartz.dataSource.defaultDS.idleConnectionValidationSeconds = 50
org.quartz.dataSource.defaultDS.validateOnCheckout = false
org.quartz.dataSource.defaultDS.discardIdleConnectionsSeconds = 1000
#============================================================================
# Configure PlugIn
#============================================================================
# Trigger历史日志记录插件
org.quartz.plugin.triggHistory.class = org.quartz.plugins.history.LoggingTriggerHistoryPlugin
org.quartz.plugin.triggHistory.triggerFiredMessage = Trigger \{1\}.\{0\} fired job \{6\}.\{5\} at: \{4, date, HH:mm:ss MM/dd/yyyy}
org.quartz.plugin.triggHistory.triggerCompleteMessage = Trigger \{1\}.\{0\} completed firing job \{6\}.\{5\} at \{4, date, HH:mm:ss MM/dd/yyyy\}.
# Shutdown Hook插件,通过捕捉JVM关闭时的事件,来关闭调度器
org.quartz.plugin.shutdownhook.class = org.quartz.plugins.management.ShutdownHookPlugin
org.quartz.plugin.shutdownhook.cleanShutdown = true
applicationContext-scheduler.xml
SchedulerFactoryExtBean
public class SchedulerFactoryExtBean extends SchedulerFactoryBean {
@Override
public void afterPropertiesSet() throws Exception {
if (isAutoStartup()) {
super.afterPropertiesSet();
}
}
}
Quartz的简单使用
@Service("schedulerBusiness")
public class SchedulerBusinessImpl implements ISchedulerBusiness {
private static final Logger LOG = LoggerFactory.getLogger(SchedulerBusinessImpl.class);
@Resource(name = "schedulerFactory")
private Scheduler scheduler = null;
@SuppressWarnings("unchecked")
@Override
public void insert(String jobGroup, String jobName, String jobClass, String triggerGroup,
String triggerName, String cron) throws BusinessException {
LOG.info("Insert Scheduler {} - {} - {} - {} - {} - {} ", jobGroup, jobName, jobClass,
triggerGroup, triggerName, cron);
checkParamNotNull(jobGroup, "任务组名", jobName, "任务名称", jobClass, "任务实现类", cron, "任务Cron表达式");
try {
JobKey jobKey = new JobKey(jobName, jobGroup);
if (scheduler.checkExists(jobKey)) {
throw new BusinessException(ExceptionCode.PARAM_ERROR, "该任务已经存在");
}
if (StringUtils.isBlank(triggerGroup)) triggerGroup = jobGroup;
if (StringUtils.isBlank(triggerName)) triggerName = jobName;
TriggerKey triggerKey = new TriggerKey(triggerName, triggerGroup);
if (scheduler.checkExists(triggerKey)) {
throw new BusinessException(ExceptionCode.PARAM_ERROR, "该任务Trigger已经存在");
}
CronExpression cronExpression = new CronExpression(cron);
ScheduleBuilder cronScheduleBuilder = CronScheduleBuilder.cronSchedule(cronExpression);
CronTrigger cronTrigger = TriggerBuilder.newTrigger().withIdentity(triggerKey)
.withSchedule(cronScheduleBuilder).build();
Class extends Job> jobClazz = (Class extends Job>) Class.forName(jobClass);
JobDetail jobDetail = JobBuilder.newJob().withIdentity(jobKey).ofType(jobClazz)
.storeDurably(true).requestRecovery(true).build();
scheduler.scheduleJob(jobDetail, cronTrigger);
} catch (Exception e) {
LOG.error(e.getMessage(), e);
}
}
@SuppressWarnings("unchecked")
@Override
public void updateJobCron(String jobGroup, String jobName, String cron) throws BusinessException {
checkParamNotNull(jobGroup, "任务组名", jobName, "任务名称", cron, "任务Cron表达式");
try {
JobKey jobKey = new JobKey(jobName, jobGroup);
if (!scheduler.checkExists(jobKey)) {
throw new BusinessException(ExceptionCode.PARAM_ERROR, "该任务不存在");
}
List cronTriggers = (List) scheduler.getTriggersOfJob(jobKey);
if (null == cronTriggers || cronTriggers.size() == 0) {
throw new BusinessException(ExceptionCode.PARAM_ERROR, "该任务不存在Trigger");
}
CronTrigger oldCronTrigger = cronTriggers.get(0);
CronExpression cronExpression = new CronExpression(cron);
ScheduleBuilder cronScheduleBuilder = CronScheduleBuilder.cronSchedule(cronExpression);
CronTrigger newCronTrigger = oldCronTrigger.getTriggerBuilder().withSchedule(cronScheduleBuilder).build();
scheduler.rescheduleJob(oldCronTrigger.getKey(), newCronTrigger);
} catch (Exception e) {
LOG.error(e.getMessage(), e);
}
}
@Override
public void updateTrigger(String triggerGroup, String triggerName, String cron) throws BusinessException {
checkParamNotNull(triggerGroup, "Trigger组名", triggerName, "Trigger名称", cron, "Cron表达式");
try {
TriggerKey triggerKey = new TriggerKey(triggerName, triggerGroup);
Trigger trigger = scheduler.getTrigger(triggerKey);
if (null == trigger) {
throw new BusinessException(ExceptionCode.PARAM_ERROR, "该Trigger不存在");
}
CronTrigger oldCronTrigger = (CronTrigger) trigger;
CronExpression cronExpression = new CronExpression(cron);
ScheduleBuilder cronScheduleBuilder = CronScheduleBuilder.cronSchedule(cronExpression);
CronTrigger newCronTrigger = oldCronTrigger.getTriggerBuilder().withSchedule(cronScheduleBuilder).build();
scheduler.rescheduleJob(triggerKey, newCronTrigger);
} catch (Exception e) {
LOG.error(e.getMessage(), e);
}
}
@Override
public void pauseJob(String jobGroup, String jobName) throws BusinessException {
checkParamNotNull(jobGroup, "任务组名", jobName, "任务名称");
try {
JobKey jobKey = JobKey.jobKey(jobName, jobGroup);
if (!scheduler.checkExists(jobKey)) {
throw new BusinessException(ExceptionCode.PARAM_ERROR, "该任务不存在");
}
scheduler.pauseJob(jobKey);
} catch (SchedulerException e) {
LOG.error(e.getMessage(), e);
}
}
@Override
public void pauseTrigger(String triggerGroup, String triggerName) throws BusinessException {
checkParamNotNull(triggerGroup, "Trigger组名", triggerName, "Trigger名称");
try {
TriggerKey triggerKey = new TriggerKey(triggerName, triggerGroup);
if (!scheduler.checkExists(triggerKey)) {
throw new BusinessException(ExceptionCode.PARAM_ERROR, "该Trigger不存在");
}
scheduler.pauseTrigger(triggerKey);
} catch (SchedulerException e) {
LOG.error(e.getMessage(), e);
}
}
@Override
public void resumeJob(String jobGroup, String jobName) throws BusinessException {
checkParamNotNull(jobGroup, "任务组名", jobName, "任务名称");
try {
JobKey jobKey = JobKey.jobKey(jobName, jobGroup);
if (!scheduler.checkExists(jobKey)) {
throw new BusinessException(ExceptionCode.PARAM_ERROR, "该任务不存在");
}
scheduler.resumeJob(jobKey);
} catch (SchedulerException e) {
LOG.error(e.getMessage(), e);
}
}
@Override
public void resumeTrigger(String triggerGroup, String triggerName) throws BusinessException {
checkParamNotNull(triggerGroup, "Trigger组名", triggerName, "Trigger名称");
try {
TriggerKey triggerKey = new TriggerKey(triggerName, triggerGroup);
if (!scheduler.checkExists(triggerKey)) {
throw new BusinessException(ExceptionCode.PARAM_ERROR, "该Trigger不存在");
}
scheduler.resumeTrigger(triggerKey);
} catch (SchedulerException e) {
LOG.error(e.getMessage(), e);
}
}
@Override
public void delete(String jobGroup, String jobName) throws BusinessException {
checkParamNotNull(jobGroup, "任务组名", jobName, "任务名称");
try {
JobKey jobKey = JobKey.jobKey(jobName, jobGroup);
if (!scheduler.checkExists(jobKey)) {
throw new BusinessException(ExceptionCode.PARAM_ERROR, "该任务不存在");
}
scheduler.deleteJob(jobKey);
} catch (SchedulerException e) {
LOG.error(e.getMessage(), e);
}
}
@Override
public JobDTO readJob(String jobGroup, String jobName) throws BusinessException {
checkParamNotNull(jobGroup, "任务组名", jobName, "任务名称");
try {
JobKey jobKey = JobKey.jobKey(jobName, jobGroup);
if (!scheduler.checkExists(jobKey)) {
throw new BusinessException(ExceptionCode.PARAM_ERROR, "该任务不存在");
}
return readJobDTOByJobKey(jobKey);
} catch (SchedulerException e) {
LOG.error(e.getMessage(), e);
}
return null;
}
@Override
public List readJobs() throws BusinessException {
List jobDTOList = new ArrayList();
try {
GroupMatcher matcher = GroupMatcher.anyJobGroup();
Set jobKeys = scheduler.getJobKeys(matcher);
if (null == jobKeys || jobKeys.size() == 0) return jobDTOList;
for (JobKey jobKey : jobKeys) {
JobDTO jobDTO = readJobDTOByJobKey(jobKey);
jobDTOList.add(jobDTO);
}
} catch (SchedulerException e) {
LOG.error(e.getMessage(), e);
}
return jobDTOList;
}
private void checkParamNotNull(Object... params) {
for (int i = 0, len = params.length; i < len; i++) {
checkSingleParamNotNull(params[i], (String) params[++i]);
}
}
private void checkSingleParamNotNull(Object paramValue, String paramName) {
boolean isNull = false;
if (paramValue instanceof String) {
isNull = StringUtils.isBlank((String) paramValue);
} else {
isNull = null == paramValue ? true : false;
}
if (isNull) throw new BusinessException(ExceptionCode.PARAM_NULL, paramName + "不能为空");
}
@SuppressWarnings("unchecked")
private JobDTO readJobDTOByJobKey(JobKey jobKey) {
JobDTO jobDTO = new JobDTO();
try {
jobDTO.setGroup(jobKey.getGroup());
jobDTO.setName(jobKey.getName());
JobDetail jobDetail = scheduler.getJobDetail(jobKey);
jobDTO.setDescription(jobDetail.getDescription());
jobDTO.setJobClass(jobDetail.getJobClass().getName());
jobDTO.setDurable(jobDetail.isDurable());
jobDTO.setRequestsRecovery(jobDetail.requestsRecovery());
List triggerList = (List) scheduler.getTriggersOfJob(jobKey);
for (int i = 0, len = triggerList.size(); i < len; i++) {
CronTrigger cronTrigger = triggerList.get(i);
TriggerKey triggerKey = cronTrigger.getKey();
TriggerDTO triggerDTO = new TriggerDTO();
triggerDTO.setGroup(triggerKey.getGroup());
triggerDTO.setName(triggerKey.getName());
triggerDTO.setDescription(cronTrigger.getDescription());
triggerDTO.setCronExpression(cronTrigger.getCronExpression());
triggerDTO.setStartTime(cronTrigger.getStartTime());
triggerDTO.setEndTime(cronTrigger.getEndTime());
triggerDTO.setFinalFireTime(cronTrigger.getFinalFireTime());
Trigger.TriggerState triggerState = scheduler.getTriggerState(triggerKey);
triggerDTO.setStatus(triggerState.name());
jobDTO.getTriggers().add(triggerDTO);
}
} catch (SchedulerException e) {
LOG.error(e.getMessage(), e);
}
return jobDTO;
}
}
相关信息可以在 https://github.com/fighting-one-piece/repository-public.git 里面查看