Spring Quartz分布式定时任务框架搭建
代码版本
使用的版本是2.3.2,无漏洞,是最新的发行版本。
参考
官方指导翻译参考 --注意该指导不是最新的版本
建表
建表脚本在源码包的位置
由于使用的是分布式方式,需要建表。使用MySql的InnoDb的引擎建表。脚本在源码包的。
quartz-2.3.2\quartz-core\src\main\resources\org\quartz\impl\jdbcjobstore
Quartz11张表的解释
序号 | 表名 | 说明 | 备注 |
---|---|---|---|
1 | QRTZ_JOB_DETAILS | 存放JobDetail的具体信息。核心:JobName,JobGroup,描述,ClassName与是否独立存储等 | |
2 | QRTZ_TRIGGERS | 存储所有定义的Trigger信息,包括名称、分组、类型(CRON或SIMPLE)、当前运行状态、上下次执行时间、MisFire策略。和qrtz_fired_triggers存放的不一样,不管trigger触发了多少次都只有一条记录,TRIGGER_STATE用来标识当前trigger的状态 | |
3 | QRTZ_SIMPLE_TRIGGERS | 存储SIMPLE类型的Trigger(SimpleTrigger),存储名称、分组、重复次数、执行时间间隔已经已经执行的次数。失火策略该表不存,存放于ORTZ_TRIGGER表中。 | 不使用SimpleTrigger,不需要关注 |
4 | QRTZ_CRON_TRIGGERS | 存储CRON类型的Trigger(CronTrigger),最常用的触发器。包括名称、所属组、Cron表达式、时区信息 | 重点,需要使用CronTrigger |
5 | QRTZ_SIMPROP_TRIGGERS | 存储CalendarIntervalTrigger和DailyTimeIntervalTrigger两种类型的触发器 | 不使用,可暂不关注 |
6 | QRTZ_BLOB_TRIGGERS | 自定义的triggers使用blog类型进行存储,非自定义的triggers不会存放在此表中,Quartz提供的triggers包括:CronTrigger,CalendarIntervalTrigger, DailyTimeIntervalTrigger以及SimpleTrigger,这几个trigger信息会保存在其他的几张表中 | 不需要自定义trigger,可暂不关注 |
7 | QRTZ_CALENDARS | Quartz为我们提供了日历的功能,可以自己定义一个时间段,可以控制触发器在这个时间段内触发或者不触发;现在提供6种类型:AnnualCalendar,CronCalendar,DailyCalendar,HolidayCalendar,MonthlyCalendar,WeeklyCalendar | 暂不使用,使用cron表达式目前已经满足需要 |
8 | QRTZ_PAUSED_TRIGGER_GRPS | 存储暂停的Trigger组 | 重要性低,可暂不关注 |
9 | QRTZ_FIRED_TRIGGERS | 存储正在执行任务中的Trgigger,trigger随着时间的推移状态发生变化,直到最后trigger执行完成,从表中被删除。主要存储运行服务器节点ID、Trigger名称分组、触发器执行状态、触发与调度时间。相同的trigger和task,每触发一次都会创建一个实例;从刚被创建的ACQUIRED状态,到EXECUTING状态,最后执行完从数据库中删除; | |
10 | QRTZ_SCHEDULER_STATE | 存储所有节点的scheduler,会定期检查scheduler是否失效。上次检查时间与检测状态。 | |
11 | QRTZ_LOCKS | Quartz提供的锁表,为多个节点调度提供分布式锁,实现分布式调度 |
脚手架搭建
数据库初始化
在数据库环境上新建数据库common,执行quartz的初始化脚本tables_mysql_innodb.sql
使用Spring提供的Quartz支持
<dependency>
<groupId>org.quartz-scheduler</groupId>
<artifactId>quartz</artifactId>
<version>2.3.2</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context-support</artifactId>
<version>5.2.5.RELEASE</version>
</dependency>
核心类
参考如下,可以整改为基于注解的方式
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:task="http://www.springframework.org/schema/task"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.2.xsd
http://www.springframework.org/schema/task http://www.springframework.org/schema/task/spring-task.xsd">
<bean name="secondCron" class="org.springframework.scheduling.quartz.JobDetailFactoryBean">
<property name="jobClass" value="com.hafiz.www.cron.SecondCron"/>
<property name="jobDataAsMap">
<map>
<entry key="timeout" value="0"/>
</map>
</property>
</bean>
<!--Cron表达式触发器-->
<bean id="cronTrigger" class="org.springframework.scheduling.quartz.CronTriggerFactoryBean">
<property name="jobDetail" ref="secondCron"/>
<property name="cronExpression" value="0/5 * * * * ?"/>
</bean>
<!--配置调度工厂-->
<bean class="org.springframework.scheduling.quartz.SchedulerFactoryBean">
<property name="triggers">
<list>
<!--<ref bean="simpleTrigger"/>-->
<ref bean="cronTrigger"/>
</list>
</property>
</bean>
</beans>
基本关系是,SchedulerFactoryBean通过触发器JobDetailFactoryBean执行包含任务类SecondCron的JobDetailFactoryBean。其中SecondCron是业务实现。
Spring与Quartz的整合
参考下面的链接初始化表信息及配置。
可以将集成修改为注入形式,注入方式配置参考下面的链接:
job中无法注入service解决
job实例在spring容器加载时候,能够注入bean,但是调度时,job对象会重新创建,此时就是导致已经注入的对象丢失,因此报空指针异常。通过重写JobFactory解决该问题
代码
1、配置文件database.properties、quartz.properties、cron.properties
通过database.properties初始化DataSource数据源(可以是Druid等任意实现了DataSource的框架)
@Configuration
@PropertySource("classpath:database.properties")
public class DataSourceConfiguration {
@Value("${servers}")
private String servers;
@Value("${userName}")
private String userName;
@Value("${encPasswd}")
private String encPasswd;
@Bean
public DataSource dataSource() {
DataSource dataSource = new DataSource();
dataSource.setServers(servers);
dataSource.setUserName(userName);
dataSource.setEncPasswd(encPasswd);
return dataSource;
}
}
quartz.properties
#============================================================================
# Configure Main Scheduler Properties
#============================================================================
org.quartz.scheduler.instanceName = petal-quartz
org.quartz.scheduler.instanceId = AUTO
org.quartz.scheduler.threadsInheritContextClassLoaderOfInitializer = true
#============================================================================
# Configure ThreadPool
#============================================================================
org.quartz.threadPool.class = org.quartz.simpl.SimpleThreadPool
org.quartz.threadPool.threadCount = 50
#============================================================================
# Configure JobStore
#============================================================================
org.quartz.jobStore.misfireThreshold = 60000
org.quartz.jobStore.class = org.quartz.impl.jdbcjobstore.JobStoreTX
org.quartz.jobStore.driverDelegateClass = org.quartz.impl.jdbcjobstore.StdJDBCDelegate
org.quartz.jobStore.tablePrefix = QRTZ_
org.quartz.jobStore.useProperties = true
org.quartz.jobStore.isClustered = true
org.quartz.jobStore.clusterCheckinInterval = 20000
org.quartz.scheduler.wrapJobExecutionInUserTranscation=false
cron.properties cron表达式配置文件,后期可以整改为数据库管理
cron.demo=0 * * * * ?
2、使用注解方式配置
//SchedulerConfig
package com.xxx.config.quartz;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import javax.sql.DataSource;
import org.quartz.Scheduler;
import org.quartz.Trigger;
import org.quartz.spi.JobFactory;
import org.springframework.beans.BeansException;
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.CronTriggerFactoryBean;
import org.springframework.scheduling.quartz.SchedulerFactoryBean;
/**
* Spring Quartz配置
*
* @since 2021-01-16
*/
@Configuration
public class SchedulerConfig implements ApplicationContextAware {
private ApplicationContext ctx;
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.ctx = applicationContext;
}
/**
* 目的是让所有的Job都能被 Autorwired
* @param context
* @return job factory
*/
@Bean
public JobFactory jobFactory(ApplicationContext context) {
AutowiringSpringBeanJobFactory jobFactory = new AutowiringSpringBeanJobFactory();
jobFactory.setApplicationContext(context);
return jobFactory;
}
/**
* SchedulerFactoryBean 配置类
*
* @since 8:58 2021/1/18
* @param dataSource 数据源
* @param jobFactory 此处的jobFactory已被重写,使用的是AutowiringSpringBeanJobFactory#createJobInstance 创建job
* @return org.springframework.scheduling.quartz.SchedulerFactoryBean
*/
@Bean
public SchedulerFactoryBean schedulerFactoryBean(DataSource dataSource, JobFactory jobFactory) throws Exception {
SchedulerFactoryBean factory = new SchedulerFactoryBean();
factory.setDataSource(dataSource);
factory.setOverwriteExistingJobs(true);
factory.setJobFactory(jobFactory);
factory.setStartupDelay(60);
factory.setQuartzProperties(quartzProperties());
factory.afterPropertiesSet();
// 将所有CronTriggerFactoryBean类型的触发器都注册到SchedulerFactory
Map<String, CronTriggerFactoryBean> triggerFactoryBeansMap =
this.ctx.getBeansOfType(CronTriggerFactoryBean.class);
List<Trigger> triggerBeansList = new ArrayList<>();
triggerFactoryBeansMap.forEach((k, v) -> {
triggerBeansList.add(v.getObject());
});
factory.setTriggers(triggerBeansList.toArray(new Trigger[triggerBeansList.size()]));
return factory;
}
/**
* quartz的自定义配置
*
* @since 15:53 2021/1/16
* @param
* @return java.util.Properties
*/
@Bean
public Properties quartzProperties() throws IOException {
PropertiesFactoryBean propertiesFactoryBean = new PropertiesFactoryBean();
propertiesFactoryBean.setLocation(new ClassPathResource("/quartz.properties"));
propertiesFactoryBean.afterPropertiesSet();
return propertiesFactoryBean.getObject();
}
/**
*
* @param schedulerFactoryBean
* @return scheduler, 通过定时任务配置,随时rescheduler
*/
@Bean
public Scheduler scheduler(SchedulerFactoryBean schedulerFactoryBean) {
return schedulerFactoryBean.getScheduler();
}
}
//AutowiringSpringBeanJobFactory 解决job无法注入service问题
package com.xxx.config.quartz;
import org.quartz.spi.TriggerFiredBundle;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.AutowireCapableBeanFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.scheduling.quartz.SpringBeanJobFactory;
/**
* job实例在spring容器加载时候,能够注入bean,但是调度时,job对象会重新创建,此时就是导致已经注入的对象丢失,因此报空指针异常。
* 通过重写JobFactory解决该问题
*
* @since 8:56 2021/1/18
*/
public class AutowiringSpringBeanJobFactory extends SpringBeanJobFactory implements ApplicationContextAware {
private transient AutowireCapableBeanFactory beanFactory;
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
beanFactory = applicationContext.getAutowireCapableBeanFactory();
}
/**
* 这里覆盖了super的createJobInstance方法,对其创建出来的类再进行autowire。
*
* @param bundle
* @return
* @throws Exception
*/
@Override
protected Object createJobInstance(final TriggerFiredBundle bundle) throws Exception {
final Object jobInstance = super.createJobInstance(bundle);
beanFactory.autowireBean(jobInstance);
return jobInstance;
}
}
3、配置JobDetailFactoryBean和CronTriggerFactoryBean
package com.xxx.quartztask;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;
import org.springframework.scheduling.quartz.CronTriggerFactoryBean;
import org.springframework.scheduling.quartz.JobDetailFactoryBean;
import com.huawei.hms.mail.constant.JobName;
import com.huawei.hms.mail.quartzjob.QuartzDemoJob;
/**
* Quartz 样例,构建CronTriggerFactoryBean
*
* @since 16:20 2021/1/16
*/
@Configuration
@PropertySource("classpath:cron.properties")
public class QuartzDemoTask {
@Value("${cron.demo}")
private String cron;
@Bean(name = "quartzDemoJob")
public JobDetailFactoryBean jobDetailFactoryBean() {
JobDetailFactoryBean factoryBean = new JobDetailFactoryBean();
factoryBean.setDurability(true);
factoryBean.setJobClass(QuartzDemoJob.class);
factoryBean.setName(JobName.DEMO_JOB);
return factoryBean;
}
@Bean(name = "quartzDemoCronTrigger")
public CronTriggerFactoryBean cronTriggerFactoryBean() {
CronTriggerFactoryBean factoryBean = new CronTriggerFactoryBean();
factoryBean.setJobDetail(jobDetailFactoryBean().getObject());
factoryBean.setStartDelay(10);
factoryBean.setCronExpression(cron);
return factoryBean;
}
}
4、Job类
@Slf4j
@Component
public class QuartzDemoJob implements Job {
@Autowired
private QuartsDemoService quartsDemoService;
@Override
public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {
quartsDemoService.sayHello();
}
}
5、Service业务实现类
/**
* Quartz 样例 定时任务 业务逻辑
*
* @since 2021-01-16
*/
@Service
public class QuartsDemoService {
public void sayHello() {
System.out.println("hello world");
}
}
添加定时任务的步骤
1、新建Task类,构建JobDetailFactoryBean和CronTriggerFactoryBean并分别使用names属性唯一标识
2、在cron.properties中配置唯一命名的表达式,如cron.demo= xxx
3、新建Job类,需要实现Job接口,并重写execute方法,一般直接调用service实现
4、新建Servcie业务实现类,给Job类调用
5、声明job唯一名称JobName.DEMO_JOB常量