本文采用spring的版本4.0+,quartz的版本采用2.2.1(spring3.1以下版本和quartz2版本不兼容的一个bug。(spring3.1以及以后版本支持quartz2),之前看过的n多博客没有注明quartz框架的版本,容易出错。
1,首先配置quartz.properties文件,文件目录跟项目那些properties文件一致
#==============================================================
#Configure Main Scheduler Properties
#==============================================================
org.quartz.scheduler.instanceName = mapScheduler
org.quartz.scheduler.instanceId = AUTO
#org.quartz.scheduler.instanceIdGenerator.class
#==============================================================
#Configure JobStore
#==============================================================
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.isClustered = true
org.quartz.jobStore.clusterCheckinInterval = 20000
org.quartz.jobStore.dataSource = myDS
org.quartz.jobStore.maxMisfiresToHandleAtATime = 1
org.quartz.jobStore.misfireThreshold = 120000
org.quartz.jobStore.txIsolationLevelSerializable = true
#==============================================================
#Configure DataSource
#==============================================================
# org.quartz.dataSource.myDS.driver = com.mysql.jdbc.Driver
# org.quartz.dataSource.myDS.URL = jdbc:mysql://localhost:3306/sdn?useUnicode=true&characterEncoding=UTF-8
# org.quartz.dataSource.myDS.user = root
# org.quartz.dataSource.myDS.password =
# org.quartz.dataSource.myDS.maxConnections = 30
# org.quartz.jobStore.selectWithLockSQL = SELECT * FROM {0}LOCKS WHERE LOCK_NAME = ? FOR UPDATE
#==============================================================
#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
#==============================================================
#Skip Check Update
#update:true
#not update:false
#==============================================================
org.quartz.scheduler.skipUpdateCheck = true
#============================================================================
# Configure Plugins
#============================================================================
org.quartz.plugin.triggHistory.class = org.quartz.plugins.history.LoggingJobHistoryPlugin
org.quartz.plugin.shutdownhook.class = org.quartz.plugins.management.ShutdownHookPlugin
org.quartz.plugin.shutdownhook.cleanShutdown = true
需要注意的是,如果quartz需要配置数据库,需要将上文的mysql改成自己相对应的数据库,此处不建议改配置数据库,因为applicationContext.xml文件已经配置了数据源,此处省略即可。这个配置文件能满足绝大多数需求,具体的含义不做解释。
2,重写quartz的QuartzJobBean类
原因是在使用 quartz+spring 把 quartz 的 task 实例化进入数据库时,会产生: serializable 的错误,原因在于:
<bean id="jobtask" class="org.springframework.scheduling.quartz. MethodInvokingJobDetailFactoryBean ">
<property name="targetObject">
<ref bean="quartzJob"/>
</property>
<property name="targetMethod">
<value>execute</value>
</property>
</bean>
这个 MethodInvokingJobDetailFactoryBean 类中的 methodInvoking 方法,是不支持序列化的,因此在把 QUARTZ 的 TASK 序列化进入数据库时就会抛错。网上有说把 SPRING 源码拿来,修改一下这个方案,然后再打包成 SPRING.jar 发布,这些都是不好的方法,是不安全的。
必须根据 QuartzJobBean 来重写一个自己的类,然后使用 SPRING 把这个重写的类(我们就名命它为: MyDetailQuartzJobBean )注入 appContext 中后,再使用 AOP 技术反射出原有的 quartzJobx( 就是开发人员原来已经做好的用于执行 QUARTZ 的 JOB 的执行类 ) 。我们这个主要是通过反射机制调用实际要执行的操作方法,所以,调用的时候要符合反射机制的规范。
package com.cdsmartlink.framework.quartz.job;
import java.lang.reflect.Method;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.springframework.context.ApplicationContext;
import org.springframework.scheduling.quartz.QuartzJobBean;
public class MyDetailQuartzJobBean extends QuartzJobBean{
private String targetObject;
private String targetMethod;
private ApplicationContext ctx;
@Override
protected void executeInternal(JobExecutionContext context) throws JobExecutionException {
try {
//LogUtils.Log("execute [" + targetObject + "] at once>>>>>>");
Object otargetObject = ctx.getBean(targetObject);
Method m = null;
try {
m = otargetObject.getClass().getMethod(targetMethod, new Class[] {JobExecutionContext.class}); //方法中的参数是JobExecutionContext类型
m.invoke(otargetObject, new Object[] {context});
} catch (SecurityException e) {
e.printStackTrace();
} catch (NoSuchMethodException e) {
e.printStackTrace();
}
} catch (Exception e) {
throw new JobExecutionException(e);
}
}
public void setApplicationContext(ApplicationContext applicationContext) {
this.ctx = applicationContext;
}
public void setTargetObject(String targetObject) {
this.targetObject = targetObject;
}
public void setTargetMethod(String targetMethod) {
this.targetMethod = targetMethod;
}
}
3,创建在quartz中执行的类和方法
import org.quartz.JobExecutionContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
@Component
public class AnalysisScheduleTask {
static final Logger logger= LoggerFactory.getLogger(AnalysisScheduleTask.class);
/**
* 调度创建表,方法中的参数是JobExecutionContext类型,要使MyDetailQuartzJobBean中的executeInternal方法中利用反射机制调用到相应的方法
*/
public void createTableTask(JobExecutionContext context){
System.out.println(String.valueOf(System.currentTimeMillis()).substring(6, 12) +"这个一个job");
}
}
4,配置一个applicationContext-quartz.xml文件
<?xml version="1.0" encoding="UTF-8"?>
<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-3.0.xsd">
<!-- 注册调度任务 -->
<bean id="mapScheduler" lazy-init="false" autowire="no"
class="org.springframework.scheduling.quartz.SchedulerFactoryBean" destroy-method="destroy">
<!--可选,QuartzScheduler 启动时更新己存在的Job,这样就不用每次修改targetObject后删除qrtz_job_details表对应记录了 -->
<property name="overwriteExistingJobs" value="true" />
<!--必须的,QuartzScheduler 延时启动,应用启动完后 QuartzScheduler 再启动 -->
<property name="startupDelay" value="30" />
<!-- 设置自动启动 -->
<property name="autoStartup" value="true" />
<property name="triggers">
<list>
<ref bean="createTableShelvesTrigger" />
</list>
</property>
<property name="applicationContextSchedulerContextKey" value="applicationContext" />
<property name="configLocation" value="classpath:quartz.properties" />
</bean>
<!-- 配置处理日志任务 -->
<bean name="analysisScheduleTask" class="com.cdsmartlink.analysis.proxy.AnalysisScheduleTask"></bean>
<!-- 动态创建日志记录表 -->
<bean id="createTableShelvesInvokingJob"
class="org.springframework.scheduling.quartz.JobDetailFactoryBean">
<!-- durability 表示任务完成之后是否依然保留到数据库,默认false -->
<property name="durability" value="true" />
<property name="requestsRecovery" value="true" />
<property name="jobClass">
<value>com.cdsmartlink.framework.quartz.job.MyDetailQuartzJobBean</value>
</property>
<property name="jobDataAsMap">
<map>
<entry key="targetObject" value="analysisScheduleTask" />
<entry key="targetMethod" value="createTableTask" />
</map>
</property>
</bean>
<!-- 定期动态创建数据表 -->
<bean id="createTableShelvesTrigger" class="org.springframework.scheduling.quartz.CronTriggerFactoryBean">
<property name="jobDetail" ref="createTableShelvesInvokingJob"/>
<!-- 每月最后一天的23:55分执行任务 -->
<property name="cronExpression" value="0 55 23 L * ?"/>
</bean>
<!-- 注册调度任务 -->
<!-- <bean class="org.springframework.scheduling.quartz.SchedulerFactoryBean" lazy-init="false" destroy-method="destroy">
<property name="triggers">
<list>
<ref bean="createTableShelvesTrigger"/>
</list>
</property>
</bean> -->
</beans>
此处需要注意的是红色字体的表达式:cron表达式,语法如下:
说明:
1)Cron表达式的格式:秒 分 时 日 月 周 年(可选)。
字段名 允许的值 允许的特殊字符
秒 0-59 , - * /
分 0-59 , - * /
小时 0-23 , - * /
日 1-31 , - * ? / L W C
月 1-12 or JAN-DEC , - * /
周几 1-7 or SUN-SAT , - * ? / L C #
年 (可选字段) empty, 1970-2099 , - * /
“?”字符:表示不确定的值
“,”字符:指定数个值
“-”字符:指定一个值的范围
“/”字符:指定一个值的增加幅度。n/m表示从n开始,每次增加m
“L”字符:用在日表示一个月中的最后一天,用在周表示该月最后一个星期X
“W”字符:指定离给定日期最近的工作日(周一到周五)
“#”字符:表示该月第几个周X。6#3表示该月第3个周五
2)Cron表达式范例:
每隔5秒执行一次:*/5 * * * * ?
每隔1分钟执行一次:0 */1 * * * ?
每天23点执行一次:0 0 23 * * ?
每天凌晨1点执行一次:0 0 1 * * ?
每月1号凌晨1点执行一次:0 0 1 1 * ?
每月最后一天23点执行一次:0 0 23 L * ?
每周星期天凌晨1点实行一次:0 0 1 ? * L
在26分、29分、33分执行一次:0 26,29,33 * * * ?
每天的0点、13点、18点、21点都执行一次:0 0 0,13,18,21 * * ?
如果你设计的任务定时方式为隔一段时间执行,此处需要注意跟scheduler延时启动时间的原因造成的问题,这个也是比较容易犯的错误。首先scheduler延迟启动的原因是保证容器先加载完毕,即tomcat等容器必须在scheduler先加载完毕。假如延时30s启动,任务设置的频率时间是5s一次,那么scheduler启动成功后首次执行任务的次数是 延时时间/任务频率 +1 ,所以第一次加载完毕会执行30/5+1=7次打印行输出,不过当频率为10s以上时,60/10 的延时频率比实际上才执行6次,估计频率的误差或者其他原因造成的结果,这个读者慢慢体会。
5,存储quartz相关表
下载quartz-2.2.1包,解压,在quartz-2.2.1-distribution/quartz-2.2.1/docs/dbTables中有各个数据库的SQL运行脚本文件,我们只需要打开相应的数据库运行相应的脚本文件就可以在数据库中创建存储quartz相关的数据表了。
6,将项目打包发布到两个tomcat容器下,就能发现无论何时情况下都只有一个容器会执行打印语句,当一个任务宕机停止时,另一个容器会开始执行任务
如果是多个任务的话,配置如下,触发器和任务需要相应地重复配置几个
<!-- 注册调度任务 -->
<bean id="mapScheduler" lazy-init="false" autowire="no"
class="org.springframework.scheduling.quartz.SchedulerFactoryBean"
destroy-method="destroy">
<property name="dataSource" ref="dataSource" />
<!--可选,QuartzScheduler 启动时更新己存在的Job,这样就不用每次修改targetObject后删除qrtz_job_details表对应记录了 -->
<property name="overwriteExistingJobs" value="true" />
<!--必须的,QuartzScheduler 延时启动,应用启动完后 QuartzScheduler 再启动 -->
<property name="startupDelay" value="30" />
<!-- 设置自动启动 -->
<property name="autoStartup" value="true" />
<property name="triggers">
<list>
<ref bean="createTableShelvesTrigger" />
<ref bean="createTableShelvesTrigger2" />
<ref bean="createTableShelvesTrigger3" />
</list>
</property>
<property name="applicationContextSchedulerContextKey"
value="applicationContext" />
<property name="configLocation" value="classpath:properties/quartz1.properties" />
</bean>
<!-- 配置处理日志任务 -->
<!-- <bean name="analysisScheduleTask" class="com.ky.sdn.fk.server.controller.AnalysisScheduleTask"></bean> -->
<!-- 动态创建日志记录表 -->
<bean id="createTableShelvesInvokingJob1"
class="org.springframework.scheduling.quartz.JobDetailFactoryBean">
<!-- durability 表示任务完成之后是否依然保留到数据库,默认false -->
<property name="durability" value="true" />
<property name="requestsRecovery" value="true" />
<property name="jobClass">
<value>com.ky.sdn.fk.server.controller.MyDetailQuartzJobBean2</value>
</property>
<property name="jobDataAsMap">
<map>
<entry key="targetObject" value="analysisScheduleTask" />
<entry key="targetMethod" value="createTableTask1" />
</map>
</property>
</bean>
<bean id="createTableShelvesInvokingJob2"
class="org.springframework.scheduling.quartz.JobDetailFactoryBean">
<!-- durability 表示任务完成之后是否依然保留到数据库,默认false -->
<property name="durability" value="true" />
<property name="requestsRecovery" value="true" />
<property name="jobClass">
<value>com.ky.sdn.fk.server.controller.MyDetailQuartzJobBean2</value>
</property>
<property name="jobDataAsMap">
<map>
<entry key="targetObject" value="analysisScheduleTask" />
<entry key="targetMethod" value="createTableTask2" />
</map>
</property>
</bean>
<bean id="createTableShelvesInvokingJob3"
class="org.springframework.scheduling.quartz.JobDetailFactoryBean">
<!-- durability 表示任务完成之后是否依然保留到数据库,默认false -->
<property name="durability" value="true" />
<property name="requestsRecovery" value="true" />
<property name="jobClass">
<value>com.ky.sdn.fk.server.controller.MyDetailQuartzJobBean2</value>
</property>
<property name="jobDataAsMap">
<map>
<entry key="targetObject" value="analysisScheduleTask" />
<entry key="targetMethod" value="createTableTask3" />
</map>
</property>
</bean>
<!-- 定期动态创建数据表 -->
<bean id="createTableShelvesTrigger"
class="org.springframework.scheduling.quartz.CronTriggerFactoryBean">
<property name="jobDetail" ref="createTableShelvesInvokingJob1" />
<!-- 每月最后一天的23:55分执行任务 -->
<property name="cronExpression" value="0/15 * * * * ?" />
</bean>
<!-- 定期动态创建数据表 -->
<bean id="createTableShelvesTrigger2"
class="org.springframework.scheduling.quartz.CronTriggerFactoryBean">
<property name="jobDetail" ref="createTableShelvesInvokingJob2" />
<!-- 每月最后一天的23:55分执行任务 -->
<property name="cronExpression" value="2 2,4,6,8,10,12,14 15 * * ?" />
</bean>
<!-- 定期动态创建数据表 -->
<bean id="createTableShelvesTrigger3"
class="org.springframework.scheduling.quartz.CronTriggerFactoryBean">
<property name="jobDetail" ref="createTableShelvesInvokingJob3" />
<!-- 每月最后一天的23:55分执行任务 -->
<property name="cronExpression" value="3 1,3,5,7,9,11,13,15 15 * * ?" />
</bean>