Spring+Quartz定时任务调度 2011/5 zqhxuyuan@gmail.com
参考文章: http://www.iteye.com/topic/399980
org.springframework.scheduling.quartz.SchedulerFactoryBean
org.springframework.scheduling.quartz.CronTriggerBean
org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean
org.springframework.scheduling.quartz.JobDetailBean
org.springframework.scheduling.quartz.QuartzJobBean
I.Quartz在Spring中如何动态配置时间(SpringQuartz,JUnit测试)
在项目中有一个需求,需要灵活配置调度任务时间,并能*启动或停止调度。 有关调度的实现我就第一就想到了Quartz这个开源调度组件,因为很多项目使用过,Spring结合Quartz静态配置调度任务时间,非常easy。比如:每天凌晨几点定时运行一个程序,这只要在工程中的spring配置文件中配置好spring整合quartz的几个属性就好。
1).静态(内存)配置方法:Spring配置文件(或者applicationContext-quartz.xml) :
<bean id="jobDetail"class="org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean">
<propertyname="targetObject" ref="simpleService" />
<propertyname="targetMethod" value="test" />
</bean>
<bean id="cronTrigger" class="org.springframework.scheduling.quartz.CronTriggerBean">
<property name="jobDetail" ref="jobDetail" />
<property name="cronExpression" value="0 0/50* ? * * *" />
</bean>
<bean id="schedulerTrigger"class="org.springframework.scheduling.quartz.SchedulerFactoryBean">
<propertyname="triggers">
<list>
<refbean="cronTrigger"/>
</list>
</property>
</bean>
从创建对象到目标方法的执行过程分析:
schedulerTrigger是整个配置的核心(入口). 当创建好SchedulerFactoryBean对象后,SchedulerFactoryBean会
根据triggers列举的定时触发器列表,分别去索引每个触发器cronTrigger,即每个CronTriggerBean实例.
每个触发器实例又分别有一个jobDetail和cronExpression属性.
jobDetail是由Spring的MethodInvokingJobDetailFactoryBean创建的Bean对象,是触发器执行的任务的具体信息对象, jobDetail里有targetObject和targetMethod方法.这是真正执行的任务方法:目标对象的目标方法.
cronExpression代表的是该任务每隔多长时间执行一次,值是一些表达式,类似于正则表达式.
即每隔多长时间执行一次jobDetail里的方法(simpleService.test()).
要弄明白SchedulerFactoryBean对象的创建是整个Quartz任务调度的入口.
理解了这个过程,对后面的动态配置时需要自定义来实现哪些类,都是有很大帮助的.
比如:需要动态配置时间,cronTrigger不能满足我们的需求,即不能通过索引triggers的cronTrigger对象来调度任务了.
(注意<bean id="schedulerTrigger"class="org.springframework.scheduling.quartz.SchedulerFactoryBean">不需要更改,即SchedulerFactoryBean对象的创建仍然是需要的,只不过任务调度内部的实现需要更改了,即SchedulerFactoryBean通过triggers的方式的内部实现通过CronTriggerBean的方式需要变更).
因此就有了SchedulerServiceImpl类.那么该类需要哪些属性?看看SchedulerFactoryBean使用triggers方式实现,
CronTriggerBean创建cronTrigger对象需要:jobDetail和cronExpression.因此实际上自定义的SchedulerServiceImpl 类需要的其实是JobDetail和CronExpression.所以SchedulerServiceImpl 的属性有:Scheduler对象和JobDetail对象.Scheduler对象实际上是可以负责cronExpression的.通过一些属性的设置同样可以达到CronExpression的.
而Scheduler对象引用到SchedulerFactoryBean对象,可以加入到任务调度中去.
这2种方式本质其实是一样的:
第一种通过索引triggers把所有的CronTriggerBean加入到由SchedulerFactoryBean创建的对象中进行任务的管理.
其中JobDetail即任务的业务处理和CronExpression即调度时间间隔表达式均在CronTriggerBean中完成.
我们改造的方式:自定义SchedulerServiceImpl类,引用Scheduler对象和JobDetail对象,Scheduler对象引用(ref=)SchedulerFactoryBean从而被加入到任务管理中.
这种配置就是对quartz的一种简单的使用了,调度任务会在spring启动的时候加载到内存中,按照cronTrigger中定义的 cronExpression定义的时间按时触发调度任务。但是这是quartz使用“内存”方式的一种配置,也比较常见,当然对于不使用spring的项目,也可以单独整合quartz。方法也比较简单,可以从quartz的doc中找到配置方式,或者看一下《Quartz Job Scheduling Framework 》。
2).动态配置方法:
但是对于想持久化调度任务的状态,并且灵活调整调度时间的方式来说,上面的内存方式就不能满足要求了,正如本文开始我遇到的情况,需要采用数据库方式集成 Quartz,这部分集成其实在《Quartz Job Scheduling Framework 》中也有较为详细的介绍,当然doc文档中也有,但是缺乏和spring集成的实例。
一、需要创建Quartz数据库表,建表脚本在Quartz发行包的docs\dbTables目录,里面有各种数据库建表脚本,我采用的Quartz 1.6.5版本,总共12张表,不同版本,表个数可能不同。我用mysql数据库,执行了Quartz发行包的docs\dbTables\tables_mysql_innodb.sql建表。
二、建立java project,完成后目录如下
新建一个JavaProject(这篇文章最后是使用Main来测试,并没有用到Web).下篇文章中用到了Web容器,要在Web容器启动的时候初始化Quartz和Spring集成的SchedulerFactoryBean实例,新建的就是WebProject了.
三、配置数据库连接池 和applicationContext:
jdbc.properties:
jdbc.driverClassName=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/quartz?useUnicode=true&characterEncoding=UTF-8&autoReconnect=true
jdbc.username=root
jdbc.password=kfs
cpool.checkoutTimeout=5000
cpool.minPoolSize=10
cpool.maxPoolSize=25
cpool.maxIdleTime=7200
cpool.acquireIncrement=5
cpool.autoCommitOnClose=true
applicationContext.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" xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx" xmlns:context="http://www.springframework.org/schema/context" xmlns:jee="http://www.springframework.org/schema/jee" xsi:schemaLocation="http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-2.5.xsd
http://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans-2.5.xsd
http://www.springframework.org/schema/txhttp://www.springframework.org/schema/tx/spring-tx-2.5.xsd
http://www.springframework.org/schema/aophttp://www.springframework.org/schema/aop/spring-aop-2.5.xsd
http://www.springframework.org/schema/jee http://www.springframework.org/schema/jee/spring-jee-2.5.xsd">
<!-- 定义被Spring认识的注解扫描的范围 -->
<context:component-scanbase-package="com.sundoctor"/>
<!-- 属性文件读入 -->
<beanid="propertyConfigurer"class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
<property name="locations">
<list>
<value>classpath:jdbc.properties</value>
</list>
</property>
</bean>
<!-- 数据源定义,使用c3p0 连接池 -->
<bean id="dataSource"class="com.mchange.v2.c3p0.ComboPooledDataSource"destroy-method="close">
<propertyname="driverClass" value="${jdbc.driverClassName}" />
<propertyname="jdbcUrl" value="${jdbc.url}" />
<propertyname="user" value="${jdbc.username}" />
<propertyname="password" value="${jdbc.password}" />
<propertyname="initialPoolSize" value="${cpool.minPoolSize}"/>
<propertyname="minPoolSize" value="${cpool.minPoolSize}" />
<propertyname="maxPoolSize" value="${cpool.maxPoolSize}" />
<propertyname="acquireIncrement" value="${cpool.acquireIncrement}"/>
<propertyname="maxIdleTime" value="${cpool.maxIdleTime}"/>
</bean>
</beans>
这里只是配置了数据连接池,我使用c3p0 连接池,还没有涉及到Quartz有关配置,下面且听我慢慢道来。
四、实现动态定时任务
什么是动态定时任务:是由客户制定生成的,服务端只知道该去执行什么任务,但任务的定时是不确定的(是由客户制定)。 这样总不能修改配置文件每定制个定时任务就增加一个trigger吧,即便允许客户修改配置文件,但总需要重新启动web服务啊,研究了下Quartz在Spring中的动态定时,发现
<bean id="jobDetail"class="org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean">
<propertyname="targetObject" ref="simpleService" />
<propertyname="targetMethod" value="test" />
</bean>
<bean id="cronTrigger" class="org.springframework.scheduling.quartz.CronTriggerBean">
<propertyname="jobDetail" ref="jobDetail"/>
<propertyname="cronExpression" value="0 0/50 * ? * * *" />
</bean>
<bean id="schedulerTrigger"class="org.springframework.scheduling.quartz.SchedulerFactoryBean">
<propertyname="triggers">
<list>
<refbean="cronTrigger"/>
</list>
</property>
</bean>
中cronExpression是关键,如果可以动态设置cronExpression的值,就可以顺利解决问题了。这样我们就
不能直接使用org.springframework.scheduling.quartz.CronTriggerBean,
需要自己实现一个动态调度服务类,在其中构建CronTrigger或SimpleTrigger,动态配置时间。
动态调度服务接口:
packagecom.sundoctor.quartz.service;
importjava.util.Date;
importorg.quartz.CronExpression;
publicinterface SchedulerService {
void schedule(String cronExpression);
void schedule(String name,StringcronExpression);
void schedule(CronExpressioncronExpression);
void schedule(String name,CronExpressioncronExpression);
void schedule(Date startTime);
void schedule(String name,DatestartTime);
void schedule(Date startTime,DateendTime);
void schedule(String name,DatestartTime,Date endTime);
void schedule(Date startTime,DateendTime,int repeatCount);
void schedule(String name,DatestartTime,Date endTime,int repeatCount);
void schedule(Date startTime,DateendTime,int repeatCount,long repeatInterval) ;
void schedule(String name,DatestartTime,Date endTime,int repeatCount,long repeatInterval);
}
动态调度服务实现类:
packagecom.sundoctor.quartz.service;
importjava.text.ParseException;
importjava.util.Date;
importjava.util.UUID;
importorg.quartz.CronExpression;
importorg.quartz.CronTrigger;
importorg.quartz.JobDetail;
importorg.quartz.Scheduler;
importorg.quartz.SchedulerException;
importorg.quartz.SimpleTrigger;
importorg.springframework.beans.factory.annotation.Autowired;
importorg.springframework.beans.factory.annotation.Qualifier;
importorg.springframework.stereotype.Service;
@Service("schedulerService")
publicclass SchedulerServiceImpl implements SchedulerService {
privateScheduler scheduler;
privateJobDetail jobDetail;
@Autowired
public void setJobDetail(@Qualifier("jobDetail")JobDetail jobDetail) {
this.jobDetail = jobDetail;
}
@Autowired
public void setScheduler(@Qualifier("quartzScheduler")Schedulerscheduler) {
this.scheduler = scheduler;
}
public void schedule(StringcronExpression) {
schedule(null, cronExpression);
}
public void schedule(String name, StringcronExpression) {
try {
schedule(name, newCronExpression(cronExpression));
} catch (ParseException e) {
throw new RuntimeException(e);
}
}
public void schedule(CronExpressioncronExpression) {
schedule(null, cronExpression);
}
public void schedule(String name,CronExpression cronExpression) {
if (name == null ||name.trim().equals("")) {
name = UUID.randomUUID().toString();
}
try {
scheduler.addJob(jobDetail, true);
CronTrigger cronTrigger =new CronTrigger(name, Scheduler.DEFAULT_GROUP,jobDetail.getName(),Scheduler.DEFAULT_GROUP);
cronTrigger.setCronExpression(cronExpression);
scheduler.scheduleJob(cronTrigger);
} catch (SchedulerException e) {
throw newRuntimeException(e);
}
}
public void schedule(Date startTime) {
schedule(startTime, null);
}
public void schedule(String name, DatestartTime) {
schedule(name, startTime, null);
}
public void schedule(Date startTime, DateendTime) {
schedule(startTime, endTime, 0);
}
public void schedule(String name, DatestartTime, Date endTime) {
schedule(name, startTime, endTime,0);
}
public void schedule(Date startTime, DateendTime, int repeatCount) {
schedule(null, startTime, endTime,0);
}
public void schedule(String name, DatestartTime, Date endTime, int repeatCount) {
schedule(name, startTime, endTime,0, 0L);
}
public void schedule(Date startTime, DateendTime, int repeatCount, long repeatInterval) {
schedule(null, startTime, endTime,repeatCount, repeatInterval);
}
public void schedule(String name, DatestartTime, Date endTime, int repeatCount, long repeatInterval) {
if (name == null ||name.trim().equals("")) {
name =UUID.randomUUID().toString();
}
try {
scheduler.addJob(jobDetail, true);
SimpleTrigger SimpleTrigger= new SimpleTrigger(name, Scheduler.DEFAULT_GROUP,jobDetail.getName(),Scheduler.DEFAULT_GROUP, startTime, endTime, repeatCount,repeatInterval);
scheduler.scheduleJob(SimpleTrigger);
} catch (SchedulerException e) {
throw newRuntimeException(e);
}
}
}
SchedulerService只有一个多态方法schedule,SchedulerServiceImpl实现SchedulerService接口,注入org.quartz.Scheduler和org.quartz.JobDetail,schedule方法可以动态配置org.quartz.CronExpression或org.quartz.SimpleTrigger调度时间。
分析代码:
private Schedulerscheduler;
private JobDetailjobDetail;
@Autowired
publicvoid setJobDetail(@Qualifier("jobDetail") JobDetail jobDetail) {
this.jobDetail= jobDetail;
}
@Autowired
publicvoid setScheduler(@Qualifier("quartzScheduler") Scheduler scheduler){
this.scheduler= scheduler;
}
ScheduleServiceImpl的属性有: Scheduler. JobDetail.
@Qualifier("jobDetail"),@Qualifier("quartzScheduler")的含义是当实例化好ScheduleServiceImpl对象后,同时实例化好Scheduler和JobDetail对象.Scheduler对象会去引用Spring配置文件中name="quartzScheduler"的对象.这个对象正好是:
<bean name="quartzScheduler"class="org.springframework.scheduling.quartz.SchedulerFactoryBean">
即创建了Scheduler对象=创建了SchedulerFactoryBean对象.而这一对象同时也是Quartz调度的入口.
当然Spring的注解还允许不使用@Qualifier方式,可以使用ref来引用SchedulerFactoryBean对象.
scheduler.addJob(jobDetail, true);
SimpleTrigger SimpleTrigger = new SimpleTrigger(…);
scheduler.scheduleJob(SimpleTrigger);
scheduler.addJob(jobDetail,true);
CronTrigger cronTrigger = new CronTrigger(…);
cronTrigger.setCronExpression(cronExpression);
scheduler.scheduleJob(cronTrigger);
<bean id="cronTrigger"class="org.springframework.scheduling.quartz.CronTriggerBean">
<property name="jobDetail" ref="jobDetail" />
<property name="cronExpression" value="0 0/50* ? * * *" />
</bean>
<bean id="schedulerTrigger"class="org.springframework.scheduling.quartz.SchedulerFactoryBean">
<propertyname="triggers">
<list>
<refbean="cronTrigger"/>
</list>
</property>
</bean>
在Quartz调度使用静态在XML中配置CronExpression中,我们知道SchedulerFactoryBean通过索引triggers从而把CronTrigger或SimpleTrigger加入到任务管理中.而使用我们自定义的Scheduler实现类,同样也需要完成这样的功能:
scheduler.scheduleJob(cronTrigger);即Scheduler对象调度了cronTrigger任务.如果有多个任务需要被SchedulerFactoryBean调度,则每一个自定义调度类的实现使用@Qualifier("quartzScheduler")引用SchedulerFactoryBean,并把任务的触发器加入到Scheduler对象中: scheduler.scheduleJob(SimpleTrigger);即可.
五、实现自己的org.quartz.JobDetail
在上一步中SchedulerServiceImpl需要注入org.quartz.JobDetail,在以前的静态配置中
<bean id="jobDetail"class="org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean">
<propertyname="targetObject" ref="simpleService" />
<propertyname="targetMethod" value="testMethod" />
</bean>
中使用org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean来创建JobDetail对象.
在这里使用会报 异常:
Caused by: java.io.NotSerializableException: Unable to serialize JobDataMap forinsertion into database because the value of property 'methodInvoker' is notserializable:org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean
atorg.quartz.impl.jdbcjobstore.StdJDBCDelegate.serializeJobData(StdJDBCDelegate.java:3358)
atorg.quartz.impl.jdbcjobstore.StdJDBCDelegate.insertJobDetail(StdJDBCDelegate.java:515)
atorg.quartz.impl.jdbcjobstore.JobStoreSupport.storeJob(JobStoreSupport.java:1102)
... 11 more
这里不能使用MethodInvokingJobDetailFactoryBean了.
不能pojo了,需要使用org.springframework.scheduling.quartz.JobDetailBean和
org.springframework.scheduling.quartz.QuartzJobBean实现自己的QuartzJobBean,如下:
packagecom.sundoctor.example.service;
importorg.quartz.JobExecutionContext;
importorg.quartz.JobExecutionException;
importorg.quartz.Trigger;
importorg.springframework.scheduling.quartz.QuartzJobBean;
publicclass MyQuartzJobBean extendsQuartzJobBean {
private SimpleService simpleService;
public void setSimpleService(SimpleServicesimpleService) {
this.simpleService =simpleService;
}
protected void executeInternal(JobExecutionContext jobexecutioncontext) throwsJobExecutionException {
Trigger trigger =jobexecutioncontext.getTrigger();
String triggerName = trigger.getName();
//可以根据triggerName执行testMethod()或testMethod2()
simpleService.testMethod(triggerName);
}
}
MyQuartzJobBean继承org.springframework.scheduling.quartz.QuartzJobBean,需要被注入JobDetailBean中.使用jobClass属性将MyQuartzJobBean注入.同时SimpleService需要被注入到MyQuartzJobBean中.使用的是JobDetailBean的属性jobDataAsMap.
SimpleService实际的任务调度业务方法:
packagecom.sundoctor.example.service;
importjava.io.Serializable;
importorg.slf4j.Logger;
importorg.slf4j.LoggerFactory;
importorg.springframework.stereotype.Service;
@Service("simpleService")
publicclass SimpleService implementsSerializable{
privatestatic final long serialVersionUID = 122323233244334343L;
private static final Logger logger =LoggerFactory.getLogger(SimpleService.class);
public void testMethod(String triggerName){
//这里执行定时调度业务
logger.info(triggerName);
}
public void testMethod2(){
logger.info("testMethod2");
}
}
SimpleService主要执行定时调度业务,在这里我只是简单打印一下log日志。
SimpleService需要实现java.io.Serializable接口,否则会报
Caused by: java.io.InvalidClassException: com.sundoctor.example.service.SimpleService;class invalid for deserialization
at java.io.ObjectStreamClass.initNonProxy(ObjectStreamClass.java:587)
at java.io.ObjectInputStream.readNonProxyDesc(ObjectInputStream.java:1583)
at java.io.ObjectInputStream.readClassDesc(ObjectInputStream.java:1496)
at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:1732)
... 64 more
到这里为止我们解决的问题:
1.自己实现一个动态调度服务类(SchedulerService和SchedulerServiceImpl),在其中构建CronTrigger或SimpleTrigger,完成动态配置时间.
2.自定义实现JobDetail类
接下来就可以通过Spring的配置文件把需要创建的类进行管理.
六、配置applicationContext-quartz.xml文件:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN 2.0//EN""http://www.springframework.org/dtd/spring-beans-2.0.dtd">
<beans>
<bean name="quartzScheduler"class="org.springframework.scheduling.quartz.SchedulerFactoryBean">
<propertyname="dataSource"> <refbean="dataSource" /> </property>
<propertyname="applicationContextSchedulerContextKey" value="applicationContextKey" />
<propertyname="configLocation"value="classpath:quartz.properties"/>
<!--quartzScheduler中没有了如下配置,而是通过SchedulerService动态加入CronTrigger或SimpleTrigger.-->
<!--
<property name="triggers">
<list>
<refbean="cronTrigger"/>
</list>
</property>
-->
</bean>
<bean id="jobDetail"class="org.springframework.scheduling.quartz.JobDetailBean">
<property name="jobClass">
<value>com.sundoctor.example.service.MyQuartzJobBean</value>
</property>
<propertyname="jobDataAsMap"> <!-- 注入调度业务类,否则会报空指针错误 -->
<map>
<entry key="simpleService"> <!-- MyQuartzJobBean引用SimpleService的变量名 -->
<refbean="simpleService" /> <!-- @Service("simpleService") -->
</entry>
</map>
</property>
</bean>
</beans>
如果不采用在SchedulerServiceImpl中使用@Qualifier实例化和引用JobDetail对象和Scheduler对象.可以在这里配置bean.
这种方式似乎更加能够看清楚对象之间的依赖关系.
这里没有使用接口编程,而是直接使用SchedulerService类,
<propertyname="jobDetail"
<propertyname="scheduler"
这里的jobDetail和scheduler必须要和SchedulerService类引用的对象的名字一样.否则会报错说无法创建Bean.
这和上面的simpleService是一样的.
<map>
<entry key="simpleService">
同样都是对象的变量名.注意声明变量时,还需要对应的getter,setter方法.
需要实现序列化的类:
jobDataAsMap里的业务类即:MyQuartzJobBean里的引用对象必须实现序列化.
MyQuartzJobBean, SchedulerService等类不需要实现序列化.
<beans>
<beanname="quartzScheduler"class="org.springframework.scheduling.quartz.SchedulerFactoryBean">
<propertyname="dataSource"ref="dataSource"/>
<propertyname="applicationContextSchedulerContextKey"value="applicationContextKey"/>
<propertyname="configLocation"value="classpath:quartz.properties"/>
</bean>
<beanid="schedulerService"class="com.ffcs.som.devices.passwordplan.util.SchedulerService">
<property name="jobDetail"ref="jobDetail" />
<property name="scheduler"ref="quartzScheduler" />
</bean>
<beanid="jobDetail"class="org.springframework.scheduling.quartz.JobDetailBean">
<propertyname="jobClass">
<value>com.ffcs.som.devices.passwordplan.util.MyQuartzJobBean</value>
</property>
<propertyname="jobDataAsMap">
<map>
<entrykey="simpleService">
<refbean="simpleService"/>
</entry>
<entrykey="pwdMailService">
<refbean="pwdMailService"/>
</entry>
</map>
</property>
</bean>
</beans>
dataSource:项目中用到的数据源,里面包含了quartz用到的12张数据库表;
applicationContextSchedulerContextKey: 是org.springframework.scheduling.quartz.SchedulerFactoryBean这个类中把spring上下文以key/value的方式存放在了quartz的上下文中了,可以用applicationContextSchedulerContextKey所定义的key得到对应的spring上下文;
configLocation:用于指明quartz的配置文件的位置,如果不用spring配置quartz的话,本身quartz是通过一个配置文件进行配置的,默认名称是quartz.properties,里面配置的参数在quartz的doc文档中都有介绍,可以调整quartz,我在项目中也用这个文件部分的配置了一些属性,代码如下:
quartz.properties:
org.quartz.scheduler.instanceName = DefaultQuartzScheduler
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 = 10
org.quartz.threadPool.threadPriority = 5
org.quartz.threadPool.threadsInheritContextClassLoaderOfInitializingThread =true
org.quartz.jobStore.misfireThreshold = 60000
#org.quartz.jobStore.class = org.quartz.simpl.RAMJobStore
org.quartz.jobStore.class =org.quartz.impl.jdbcjobstore.JobStoreTX
#org.quartz.jobStore.driverDelegateClass=org.quartz.impl.jdbcjobstore.HSQLDBDelegate
org.quartz.jobStore.driverDelegateClass=org.quartz.impl.jdbcjobstore.StdJDBCDelegate
#org.quartz.jobStore.driverDelegateClass = org.quartz.impl.jdbcjobstore.PostgreSQLDelegate
#org.quartz.jobStore.useProperties = true
org.quartz.jobStore.tablePrefix = QRTZ_
org.quartz.jobStore.isClustered = false
org.quartz.jobStore.maxMisfiresToHandleAtATime=1
这里面没有数据源相关的配置部分,采用spring注入datasource的方式已经进行了配置。
这个配置文件的jobStore.class和jobStore.driverDelegateClass表明了任务是以哪种方式被保存.
RAMJobStore是使用内存的方式保存.一旦服务器重启,任务就不再存在.而使用JobStoreTX表示是以事务的形式,即以数据库的方式把任务存储起来的.这样当服务器重启时,任务还是存在的.如果是以数据库的方式,针对不同的数据库,Delegate方式需要更改.
七、测试
运行如下测试类
packagecom.sundoctor.example.test;
importjava.text.ParseException;
importjava.text.SimpleDateFormat;
importjava.util.Date;
importorg.springframework.context.ApplicationContext;
importorg.springframework.context.support.ClassPathXmlApplicationContext;
importcom.sundoctor.quartz.service.SchedulerService;
publicclass MainTest {
public static void main(String[] args) {
ApplicationContext springContext =new ClassPathXmlApplicationContext(
newString[]{"classpath:applicationContext.xml","classpath:applicationContext-quartz.xml"});
SchedulerService schedulerService= (SchedulerService)springContext.getBean("schedulerService");
//执行业务逻辑...
//设置调度任务
//每10秒中执行调试一次
schedulerService.schedule("0/10* * ? * * *");
Date startTime =parse("2009-06-01 22:16:00");
Date endTime = parse("2009-06-01 22:20:00");
//2009-06-01 21:50:00开始执行调度
schedulerService.schedule(startTime);
//2009-06-01 21:50:00开始执行调度,2009-06-01 21:55:00结束执行调试
//schedulerService.schedule(startTime,endTime);
//2009-06-01 21:50:00开始执行调度,执行5次结束
//schedulerService.schedule(startTime,null,5);
//2009-06-01 21:50:00开始执行调度,每隔20秒执行一次,执行5次结束
//schedulerService.schedule(startTime,null,5,20);
//等等,查看com.sundoctor.quartz.service.SchedulerService
}
private static Date parse(StringdateStr){
SimpleDateFormat format = newSimpleDateFormat("yyyy-MM-dd HH:mm:ss");
try {
returnformat.parse(dateStr);
}catch (ParseException e) {
throw newRuntimeException(e);
}
}
}
输出
[2009-06-02 00:08:50]INFO com.sundoctor.example.service.SimpleService(line:17)-2059c26f-9462-49fe-b4ce-be7e7a29459f
[2009-06-02 00:10:20]INFO com.sundoctor.example.service.SimpleService(line:17)-2059c26f-9462-49fe-b4ce-be7e7a29459f
[2009-06-02 00:10:30]INFO com.sundoctor.example.service.SimpleService(line:17)-2059c26f-9462-49fe-b4ce-be7e7a29459f
[2009-06-02 00:10:40]INFO com.sundoctor.example.service.SimpleService(line:17)-2059c26f-9462-49fe-b4ce-be7e7a29459f
[2009-06-02 00:10:50]INFO com.sundoctor.example.service.SimpleService(line:17)-2059c26f-9462-49fe-b4ce-be7e7a29459f
[2009-06-02 00:11:00]INFO com.sundoctor.example.service.SimpleService(line:17) -2059c26f-9462-49fe-b4ce-be7e7a29459f
[2009-06-02 00:11:10]INFO com.sundoctor.example.service.SimpleService(line:17)-2059c26f-9462-49fe-b4ce-be7e7a29459f
这样只是简单的将quartztrigger名称打印出来。 这样通过SchedulerService就可以动态配置调度时间。
其实SchedulerService还可扩展,比如可以注入多个JobDetail,调度不同的JobDetail。
· 项目: quartzWithSpring.rar (5 MB)
·
补充:增加hibernate
经过这段时间,有人在本例子上增加hibernate后遇到好些问题,比如要在SimpleService注入一个hibernate Dao,我们一般习惯让DAO继承自HibernateDaoSupport类,这样在Dao里取到的HibernateTemplate总为null。因为
SimpleService和它的属性、还有属性的属性等必须实现序列化接口,而HibernateTemplate是没实现序列化接口,所以取到的HibernateTemplate总为null。但是SessionFactory实现了序列化接口,因此我们的hibernate Dao不能继承自HibernateDaoSupport,只实现Serializable接口即可,在DAO注入SessionFactory,通过SessionFactory获取HibernateTemplate,如:
1. package com.sundoctor.quartz.dao;
2. import java.io.Serializable;
3. import org.hibernate.Session;
4. import org.hibernate.SessionFactory;
5. import org.slf4j.Logger;
6. import org.slf4j.LoggerFactory;
7. import org.springframework.beans.factory.annotation.Autowired;
8. import org.springframework.orm.hibernate3.HibernateCallback;
9. import org.springframework.orm.hibernate3.HibernateTemplate;
10.import org.springframework.stereotype.Repository;
11.import com.sundoctor.example.model.Customer;
12.import com.sundoctor.example.service.SimpleService;
13.
14.@Repository("testHibernateDao")
15.public class TestHibernateDaoImpl implements Serializable {
16.
17. private static final long serialVersionUID = 1L;
18. private static final Logger logger = LoggerFactory.getLogger(SimpleService.class);
19. private SessionFactory sessionFactory;
20.
21. @Autowired
22. public void setSessionFactory(SessionFactory sessionFactory) {
23. this.sessionFactory = sessionFactory;
24. }
25.
26. public Customer backupDateabase() {
27. HibernateTemplate hibernateTemplate = new HibernateTemplate(sessionFactory);
28. return (Customer) hibernateTemplate.execute(new HibernateCallback() {
29. public Object doInHibernate(Session session) {
30. logger.info("Session2=={}", session);
31. Customer customer = (Customer)session.createQuery
32. ("from Customer where id = 1").uniqueResult();
33. logger.info("Customer2={}", customer);
34. return customer;
35. }
36. });
37. }
38.
39. public void test() {
40. logger.info("getSessionFactory=={}",this.sessionFactory );
41. HibernateTemplate hibernateTemplate = new HibernateTemplate(sessionFactory);
42. Customer customer = (Customer)hibernateTemplate.get(Customer.class, 1);
43. logger.info("Customer={}", customer);
44. }
45.}
1. @Service("simpleService")
2. public class SimpleService implements Serializable {
3. private static final long serialVersionUID = 122323233244334343L;
4. private static final Logger logger = LoggerFactory.getLogger(SimpleService.class);
5.
6. private TestHibernateDaoImpl testHibernateDao;
7. @Autowired
8. public void setTestHibernateDao(TestHibernateDaoImpl testHibernateDao) {
9. this.testHibernateDao = testHibernateDao;
10. }
11.。。。
其次给SchedulerServiceImpl增加一个初如化方法
1. @PostConstruct
2. public void init() throws SchedulerException{
3. logger.info("init start....................");
4. scheduler.addJob(jobDetail, true);
5. logger.info("init end.......................");
6. }
让应用在启动时进行初始化,主要是让应用每次启动时更新qrtz_cron_triggers表的job_class_name字段,否则应用启动时,如果库里己经存在tirgger,从数据库加载tirgger时会报 异常
ERROR org.springframework.scheduling.quartz.LocalDataSourceJobStore(line:2884)-Error retrieving job, setting trigger state to ERROR.
org.quartz.JobPersistenceException: Couldn't retrieve job because the BLOBcouldn't be deserialized: Could not find a SessionFactory named: null [Seenested exception: java.io.InvalidObjectException: Could not find aSessionFactory named: null]
最后修改applicationContext-quartz.xml . 增加红色内容,这个很重要,让quartzScheduler延时启动,必须在应用启动完后再启动,这里设置60秒,可以根据自己应用启动时间长短修改。
<bean name="quartzScheduler"class="org.springframework.scheduling.quartz.SchedulerFactoryBean">
<propertyname="dataSource" ref ="dataSource"/>
<propertyname="applicationContextSchedulerContextKey"value="applicationContextKey"/>
<propertyname="configLocation"value="classpath:quartz.properties"/>
<propertyname="startupDelay" value="60"/>
</bean>
附件是在原来基础代码上增加了hibernate: quartzMonitor-hibernate
简单的说序列化就是一种用来处理对象流的机制,所谓对象流也就是将对象的内容进行流化。可以对流化后的对象进行读写操作,也可将流化后的对象传输于网络之间。序列化是为了解决在对对象流进行读写操作时所引发的问题。
序列化的实现:将需要被序列化的类实现Serializable接口,该接口没有需要实现的方法,implements Serializable只是为了标注该对象是可被序列化的,然后使用一个输出流(如:FileOutputStream)来构造一个ObjeCTOutputStream(对象流)对象,接着,使用ObjectOutputStream对象的writeObject(Object obj)方法就可以将参数为obj的对象写出(即保存其状态),要恢复的话则用输入流
MyQuartzJobBean类被序列化保存到数据库表qrtz_job_details的job_class_name字段中,quartz在运行时会读取qrtz_job_details表中的job_class_name将其反序列化。这也是为什么SimpleService业务类和其中注入各属性需要实现Serializable序列化接口的原因,所以你每次修改MyQuartzJobBean类或者其中的SimpleService都要删除qrtz_job_details表对应的job记录,否则可能会出现空指针异常,因为你如果你没有删除qrtz_job_details表中的记录,你修改的东东并不会自动更新到qrtz_job_details中,你用的还是原来旧版本的MyQuartzJobBean类。
>>问题
1.Dao为空或者Dao序列化异常:
前面我们知道:继承了QuartzJobBean的MyQuartzJobBean的引用对象SimpleService实际上是最终的任务调度业务类.在SimpleService里面执行真正的业务方法.如果某个调度任务需要执行多个方法.可以在MyQuartzJobBean中引用多个Service对象.这些定义的Service业务逻辑对象最后都需要被加入到JobDetailBean的jobDataAsMap中.还要注意的问题是:每个业务对象都必须实现序列化接口.
在业务逻辑类中如果需要用到Dao,比如HibernateDao或IbatisDao.你可能通常会碰到的异常有2个:
一个是Dao是空的;一个是说你的Service业务类没有序列化.
但明明我们对Service类实现了序列化,为何还报说Service没有序列化.这是因为Service序列化了,但是可能你的Dao没有序列化.
通常这种情况的发生是:你的Dao类使用了@Resource(name ="baseSqlMapDao")来实例化,但是却没有实现序列化.但是一般在我们的项目中在Service层的Dao是一个通用的Dao,会继承HibernateDaoSupport或者SqlMapClientDaoSupport.而这些框架已经固化的DaoSupport类我们无法去更改他,
所以解决办法倾向于如何使获得的Dao类不为空即可.
获得一个Dao的Bean方法可以是: 从ApplicationContext(Spring的BeanFactory)里获取BaseSqlMapDao注意使用了这种方法来获取Dao,通过Spring的注解@Resource(name ="baseSqlMapDao")来获取的方式就不能再采用了. 接下来就可以在你的业务方法里使用Dao来正常地操作数据库了.
ApplicationContext context = ContextLoader.getCurrentWebApplicationContext();
baseSqlMapDao =(BaseSqlMapDao)context.getBean("baseSqlMapDao");
2.多个不同业务类型的任务调度:
如果我们的业务方法是不一样的.可以定义多个不同的SchedulerService.
>>LZ答疑:
Ask:SchedulerServiceschedulerService =(SchedulerService)springContext.getBean("schedulerService");
请问您这个schedulerService在spring的配置文件中是怎么配置的?我用的spring2.0
不通过注解的方式,而是配置spring中,该如何?我配置在spring中调用的时候就报null的错。另外,您的例子我下载看了,有两个测试类。感觉差不多。。。看了有些迷糊。。。。我目前也正遇到这样的问题,请您帮忙!多谢了!
Answer:不用注解应该也没什么问题了,只是配置方式不一样的而己,采用xml配置schedulerService时将其中的scheduler、jobDetail要注入,先要修改一下SchedulerServiceImpl去掉所有注解:@Service("schedulerService")、@Autowired、@Qualifier("jobDetail")、@Qualifier("quartzScheduler").然后在配置文件添加
<bean id="schedulerService" class="com.sundoctor.quartz.service.SchedulerServiceImpl">
<property name="scheduler"ref="quartzScheduler" />
<property name="jobDetail"ref="jobDetail" />
</bean>
Ask:请问一下,添加trigger是怎么保存在数据库中的?因为我没看见你的dao中有保存的方法。是不是jquery实现的。。。。还真不会jquery。。。。 还有一个问题请教,qrtz_simple_triggers表中的开始时间存在哪里了?我只定义了开始时间,可是这个开始时间存在哪里了?期待解答,谢谢!
Answer:trigger的保存是quartz自己本身实现的,跟jquery没有一点关系,jquery只是前台的ajax调用,当我们调用Scheduler.scheduleJob(Trigger trigger)增加一个Trigger时quartz自己就会往库里插入数据了,对于Trigger数据的增删改都quartz本身持久化支持的,我们不用关心。添加一个 simple Trigger时往qrtz_simple_triggers写一条记录,同时也会往qrtz_triggers写入一条记录,两个表根据trigger_name、trigger_group对应,开始时间存在qrtz_triggers表中,还有结束时间、上次执行时间、下次执行时间等都存在于qrtz_triggers表中。添加一个 cron Trigger时也一样,同时往qrtz_cron_triggers、qrtz_triggers各写入一条记录。
Ask: 引用:其实SchedulerService还可扩展,比如可以注入多个JobDetail,调度不同的JobDetail
请问 这个怎么实现呢?有没有什么思路呢,小弟我刚接触quartz不久,对quartz的结构不是很了解,但同时觉得多个 JobDeatail+动态时间配置 这2个都实现的话 就超级完美了
Answer:
1.首先实现多个JobDeatail并注册,比如:
<bean id="jobDetail1"class="org.springframework.scheduling.quartz.JobDetailBean">
<propertyname="jobClass">
<value>com.sundoctor.example.service.MyQuartzJobBean1</value>
</property>
</bean>
<bean id="jobDetail2"class="org.springframework.scheduling.quartz.JobDetailBean">
<propertyname="jobClass">
<value>com.sundoctor.example.service.MyQuartzJobBean2</value>
</property>
</bean>
2.其次将多个JobDeatail放到一个HashMap中
<util:map id = "jobDeatailMap"map-class="java.util.HashMap" key-type="java.lang.String"value-type="org.springframework.scheduling.quartz.JobDetailBean">
<entry key="jobDetail1"ref="jobDetail1"/>
<entry key="jobDetail2"ref="jobDetail2"/>
</util:map>
3.然后在SchedulerServiceImpl注入jobDeatailMap ,
4.修改SchedulerServiceImpl中的schedule方法,增加以jobDeatailMapKEY名字为参数
@Service("schedulerService")
publicclass SchedulerServiceImpl implements SchedulerService {
private Scheduler scheduler;
private Map<String,JobDetailBean>jobDeatailMap;
@Autowired
public voidsetJobDeatailMap(@Qualifier("jobDeatailMap")Map<String,JobDetailBean> jobDeatailMap) {
this.jobDeatailMap =jobDeatailMap;
}
@Autowired
public voidsetScheduler(@Qualifier("quartzScheduler") Scheduler scheduler){
this.scheduler = scheduler;
}
...
@Override
public void schedule(String jobDetailName,String name, CronExpression cronExpression){
if (name == null ||name.trim().equals("")) {
name =UUID.randomUUID().toString();
}
//这个时候JobDetail根据jobDetailName从jobDeatailMap获取
JobDetail jobDetail =jobDeatailMap.get(jobDetailName);
try {
scheduler.addJob(jobDetail,true);
CronTrigger cronTrigger = newCronTrigger(name, Scheduler.DEFAULT_GROUP, jobDetail.getName(),
Scheduler.DEFAULT_GROUP);
cronTrigger.setCronExpression(cronExpression);
scheduler.scheduleJob(cronTrigger);
} catch (SchedulerException e) {
throw newRuntimeException(e);
}
}
//其它多态方法一样修改,增加jobDetailName参数。
...
}
5.调用时,传不同的jobDetailName参数就可以调用不用的JobDetail。
SchedulerServiceschedulerService = (SchedulerService)springContext.getBean("schedulerService");
schedulerService.schedule("jobDetail1","审计任务","0/10 * * ? * * *");
schedulerService.schedule("jobDetail2","发放任务","0/10 * * ? * * *");
Answer2: 恩 开始我是想用list的 后来发现 匹配的问题
改用map后,大体思路和楼主是一样的 现在我只要稍加修改web前端 就可以实现指定任务用自定义的时间设置了,灵活了许多
Answer3:
其实很多时候只需要一个JobDetail就可以了,也可以达到多个JobDetail一样的效果,一个JobDetail的时候可以在Trigger名称上做扩展,可以在调度任务时给Trigger名称加上不同的前缀或后缀,比如Trigger名称增加一个前缀参数,
public void schedule(String name, Stringprefix ,CronExpression cronExpression) {
if (name == null ||name.trim().equals("")) {
name =UUID.randomUUID().toString();
}
try {
scheduler.addJob(jobDetail,true);
//给Trigger名秒加上前缀
name = prefix + name;
CronTrigger cronTrigger = newCronTrigger(name, Scheduler.DEFAULT_GROUP, jobDetail.getName(),
Scheduler.DEFAULT_GROUP);
cronTrigger.setCronExpression(cronExpression);
scheduler.scheduleJob(cronTrigger);
scheduler.rescheduleJob(name,Scheduler.DEFAULT_GROUP, cronTrigger);
} catch (SchedulerException e) {
throw newRuntimeException(e);
}
}
然后在QuartzJobBean中的executeInternal方法取到Trigger名秒,然后根据其前缀或后缀调用不同的业务逻辑
publicclass MyQuartzJobBean extends QuartzJobBean {
private SimpleService simpleService;
public void setSimpleService(SimpleServicesimpleService) {
this.simpleService =simpleService;
}
protected voidexecuteInternal(JobExecutionContext jobexecutioncontext) throwsJobExecutionException {
Trigger trigger =jobexecutioncontext.getTrigger();
//取得Trigger名称,判断名称前缀或后缀调用不同的业务逻辑
String triggerName =trigger.getName();
if(tirggerName ...){
simpleService.testMethod(triggerName);
}else if(tirggerName ...){
simpleService.testMethod2(triggerName);
}
}
}
Ask:我要在simpleService里面注入一个继承HibernateDaoSupport的类,在testMethod()方法中执行数据库操作,spring注入为空,我想问一下,是不是因为用了quartz框架的原因,请lz尽快解答,感谢!
Answer:在simpleService里面注入一个继承HibernateDaoSupport的类,这个继承HibernateDaoSupport的类也必须实现序列化接口,simpleService类被序列化保存到数据库表 qrtz_job_details的job_class_name字段中,quartz在运行时会读取qrtz_job_details表中的 job_class_name将其反序列化。这也是为什么simpleService和其中注入各属性需要实现Serializable序列化接口的原因,所以你每次修改simpleService类或者其中的继承HibernateDaoSupport的类都要删除 qrtz_job_details表对应的job记录,否则可能会出现空指针异常,因为你如果你没有删除qrtz_job_details表中的记录,你修改的东东并不会自动更新到qrtz_job_details中,你用的还是原来旧版本的simpleService类。
Ask:我还想问一下,我做的数据库备份操作话在testMethod里面,服务器启动的时候时间没到也会调用一次,这样不就乱了吗?这个问题怎么解决? 还有就是我把bakcupDao注入到simpleSerivce里了,执行这个方法的,super.getHibernateTemplate()得到的hibernateTemplate为空,是什么原因?
Answer: 服务器起来时不会调用一次的,调不调用跟你的配置时间有关系,时间不到绝不会自动调用的,这个你放心好了
上面说过simpleService和其中注入各属性需要实现Serializable序列化接口,你的BakcupDao继承自HibernateDaoSupport虽然也实现了序列化接口,但是HibernateDaoSupport里的HibernateTemplate并没有实现序列化接口,所以你取得的HibernateTemplate永远为null。因此获取HibernateTemplate必须换一种方式,你的BakcupDao不能继承自HibernateDaoSupport。HibernateTemplate没有实现序列化接口,而SessionFactory是实现序列化接口的,在bakcupDao注入SessionFactory,通过SessionFactory获取HibernateTemplate。
Ask:我的意思是想,如果数据库里没有trigger,那就创建一个,如果没有job,那也新建一个,直接在spring初始化的时候就操作,这样就不用我们认为的去添加trigger和job了,tomcat启动后就自动执行了。这样不知道会出问题不?
Answer:根据你以上的需求,你只要用spring+quartz的简单配置就能满足了,不用持久化,也不用序列化,你用静态配置就行了,《quartz任务监控》说的是动态配置任务可能并不适合你了。
<bean id="schedulerTrigger"class="org.springframework.scheduling.quartz.SchedulerFactoryBean">
<property name="triggers">
<list>
<ref bean="trigger1"/>
<ref bean="trigger2"/>
</list>
</property>
</bean>
<!-- 每天凌晨1点执行-->
<bean id="jobDetail1"class="org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean">
<property name="targetObject"ref="xxxService1" />
<property name="targetMethod"value="testMethod1" />
</bean>
<bean id="trigger1"class="org.springframework.scheduling.quartz.CronTriggerBean">
<property name="jobDetail"ref="jobDetail1" />
<property name="cronExpression"value="0 0 1 ? * * *" />
</bean>
<!--执行50次结束-->
<bean id="jobDeail2" class="org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean">
<property name="targetObject"ref="xxxService2" />
<property name="targetMethod"value="testMethod2" />
</bean>
<bean id="trigger2"class="org.springframework.scheduling.quartz.SimpleTriggerBean">
<property name="jobDetail"ref="jobDeail2" />
<property name="startDelay"value = "4"/>
<property name="repeatCount"value = "50" />
<property name="repeatInterval"value = "1000" />
</bean>
xxxService1是你的业务类,不用实现序列化接口,testMethod1是xxxService1的一个方法,执行调试任务的方法。
你的需求只需要这样简单配置就行了,tomcat启动后就会触发trigger1,trigger2了,trigger1会在每天1点触发xxxService1的testMethod1方法,trigger2会在服务启动后触发xxxService2的testMethod2方法,执行50次结束。因为不用持久化了,你写的所有类都不用实现序列化接口,也不会出现事务问题了,也不出现你所说的多个tirgger问题了,你前面所说的所有问题都没有了。
http://www.iteye.com/topic/441951
II.Quartz任务监控管理(1)
Quartz任务监控管理,类似Windows任务管理器,可以获得运行时的实时监控,查看任务运行状态,动态增加任务,暂停、恢复、移除任务等。对于动态增加任务,可以参加我的前一篇文章《Quartz如何在Spring动态配置时间》,本文在前文的基础上扩展,增加暂停、恢复、移除任务等功能,实现Quartz任务监控管理。
先看一下最终实现实现效果,只有两个页面 ,如下 这个页面可以查看任务实时运行状态,可以暂停、恢复、移除任务等
这个页面可以动态配置调度任务。
这篇文章的实现思路在上一篇中其实都讲到了.实现任务监控,必须能将数据持久化,这里采用数据库方式,Quartz对任务的数据库持久化有着非常好的支持。我在这里采用quartz 1.6.5,在Quartz发行包的docs\dbTables目录包含有各种数据库对应脚本,我用的是MySql 5.0,所以选用tables_mysql_innodb.sql建表。
建表完成后,配置数据库连接池,分两步:
1、配置jdbc.properties文件
jdbc.driverClassName=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/quartz
jdbc.username=root
jdbc.password=kfs
2.配置applicationContext.xml文件
<bean id="propertyConfigurer" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
<property name="locations">
<list>
<value>classpath:jdbc.properties</value>
</list>
</property>
</bean>
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource" destroy-method="close">
<property name="driverClass" value="${jdbc.driverClassName}" />
<property name="jdbcUrl" value="${jdbc.url}" />
<property name="user" value="${jdbc.username}" />
<property name="password" value="${jdbc.password}" />
<property name="initialPoolSize" value="${cpool.minPoolSize}"/>
<property name="minPoolSize" value="${cpool.minPoolSize}" />
<property name="maxPoolSize" value="${cpool.maxPoolSize}" />
<property name="acquireIncrement" value="${cpool.acquireIncrement}" />
<property name="maxIdleTime" value="${cpool.maxIdleTime}"/>
</bean>
配置Quartz,也分两步
1、配置quartz. properties
org.quartz.jobStore.misfireThreshold = 60000
#org.quartz.jobStore.class = org.quartz.simpl.RAMJobStore
org.quartz.jobStore.class = org.quartz.impl.jdbcjobstore.JobStoreTX
org.quartz.jobStore.driverDelegateClass=org.quartz.impl.jdbcjobstore.StdJDBCDelegate
#org.quartz.jobStore.useProperties = true
org.quartz.jobStore.tablePrefix = QRTZ_
org.quartz.jobStore.isClustered = false
org.quartz.jobStore.maxMisfiresToHandleAtATime=1
在这里采用JobStoreTX,将任务持久化到数据中,而不再是简单的内存方式:RAMJobStore
2、配置applicationContext-quartz.xml
<bean name="quartzScheduler"class="org.springframework.scheduling.quartz.SchedulerFactoryBean">
<property name="dataSource" ref ="dataSource" />
<property name="applicationContextSchedulerContextKey" value="applicationContextKey"/>
<property name="configLocation" value="classpath:quartz.properties"/>
</bean>
<bean id="jobDetail" class="org.springframework.scheduling.quartz.JobDetailBean">
<property name="jobClass">
<value>
com.sundoctor.example.service.MyQuartzJobBean
</value>
</property>
<property name="jobDataAsMap">
<map>
<entry key="simpleService">
<ref bean="simpleService"/>
</entry>
</map>
</property>
</bean>
到些,相关配置全部完成,对于配置的具体描述,可以参加我的前一篇文章《Quartz如何在Spring动态配置时间》
实现任务动态添加配置 .请参考com.sundoctor.quartz.service.SchedulerServiceImpl.java中的各种schedule方法,
在《Quartz如何在Spring动态配置时间》有具体描述。在这里说一下:
添加一个Job在表qrtz_job_details插入一条记录
添加一个Simple Trigger在表qrtz_simple_triggers插入一条记录
添加一个Cron Trigger 在表qrtz_cron_triggers插入一条记录
添加Simple Trigger和Cron Trigger都会同进在表qrtz_triggers插入一条记录,
开始看的第一个页面调度任务列表数据就是从qrtz_triggers表获取
实现任务实时监控,暂停、恢复、移除任务等 在SchedulerServiceImpl.java类中
暂停任务
public void pauseTrigger(String triggerName,String group){
try {
scheduler.pauseTrigger(triggerName, group);//停止触发器
} catch (SchedulerException e) {
throw new RuntimeException(e);
}
}
恢复任务
public void resumeTrigger(String triggerName,String group){
try {
scheduler.resumeTrigger(triggerName, group);//重启触发器
} catch (SchedulerException e) {
throw new RuntimeException(e);
}
}
移除任务
public boolean removeTrigdger(String triggerName,String group){
try {
scheduler.pauseTrigger(triggerName, group);//停止触发器
return scheduler.unscheduleJob(triggerName, group);//移除触发器
} catch (SchedulerException e) {
throw new RuntimeException(e);
}
}
其它类的实现请参加《Quartz如何在Spring动态配置时间》,那里有具体说明。
到此,基本简单实现了Quartz任务监控管理。其实面这里只是实现了Trigger任务的监控管理,没有实现Job任务的监控管理,实现Job任务的监控管理跟Trigger差不多。用Quartz可以很方便实现多样化的任务监控管理,Trigger任务和Job任务都可进行分组管理。 Quartz很强大,也很简单,只有想不到的,没有做不到的,人有多大胆,地有多高产。
III.Quartz任务监控管理(2)
在《Quartz 任务监控管理 (1)》http://www.iteye.com/topic/441951?page=1中,我们知道实现的因难是Job持久化需要序列化,主要是以处下三个问题:
一、org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean报java.io.NotSerializableException异常,需要自己实现QuartzJobBean。
二、dao必须要实现序列化接口,Hibernatedao不能直接继承自HibernateDaoSupport,因为HibernateDaoSupport没有实现序列化接口,只能通过SessionFactory构造HibernateTemplate。
三、当库里己存在Trigger,应用启动时会从库里加载己存在Trigger,会报java.io.InvalidObjectException:Could not find a SessionFactory named: null等SessionFactory等相关异常。因为应用每次启动的得到的SessionFactory实例是不一样的,当从库里取到的Job进行反序列化时,Job里包含的SessionFactory与当前的SessionFactory不一致,所以为null。当时解决这问题采用了一个比较笨的方法,在SchedulerServiceImpl增加一个初始化方法
@PostConstruct
public void init() throws SchedulerException{
logger.info("init start....................");
scheduler.addJob(jobDetail, true);
logger.info("init end.......................");
}
并且增加
<property name="startupDelay" value="60"/>
让QuartzScheduler延时启动,为了保证init()先执行,init()是更新Job,其实只是为了更新当前的SessionFactory到Job中,保持Job里的SessionFactory与当前SessionFactory一致。我后来发现解决这个问题还有一个更好的方法,在org.springframework.scheduling.quartz.SchedulerFactoryBean是可配置的
<property name="overwriteExistingJobs" value="true"/>
<property name="jobDetails" >
<list>
<ref bean="jobDetail"/>
</list>
</property>
这样就可以达到与init一样的效果。
这三个问题,经过研究探索,现在都己经不是问题了。下面我简单说说这个三个问题的解决办法。
第一个问题:org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean报NotSerializableException异常,这个 springbug 己经在http://jira.springframework.org/browse/SPR-3797找到解决方案,上面有牛人修改过的MethodInvokingJobDetailFactoryBean.java,详细源码请可以参考附件。哇塞,又可以POJO了。
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN 2.0//EN" "http://www.springframework.org/dtd/spring-beans-2.0.dtd">
<beans>
<bean name="quartzScheduler" class="org.springframework.scheduling.quartz.SchedulerFactoryBean">
<property name="dataSource" ref="dataSource"/>
<property name="applicationContextSchedulerContextKey" value="applicationContextKey"/>
<property name="configLocation" value="classpath:quartz.properties"/>
<!--这个是必须的,QuartzScheduler 延时启动,应用启动完后 QuartzScheduler 再启动-->
<property name="startupDelay" value="30"/>
<!--这个是可选,QuartzScheduler 启动时更新己存在的Job,
这样就不用每次修改targetObject后删除qrtz_job_details表对应记录了-->
<property name="overwriteExistingJobs" value="true"/>
<property name="jobDetails" >
<list>
<ref bean="jobDetail"/>
</list>
</property>
</bean>
<bean id="jobDetail" class="frameworkx.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean">
<!--shouldRecover属性为true,则当Quartz服务被中止后,
再次启动任务时会尝试恢复执行之前未完成的所有任务-->
<property name="shouldRecover" value="true"/>
<property name="targetObject" ref="customerService"/>
<property name="targetMethod" value="testMethod1"/>
</bean>
</beans>
<bean id="jobDetail"class="frameworkx.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean">
是修改过的MethodInvokingJobDetailFactoryBean。
第三个问题:从SchedulerServiceImpl中去掉不需要的init()方法,不用在SchedulerServiceImpl初始化后更新jobDeail。
@PostConstruct
public void init() throws SchedulerException{
logger.info("init start....................");
scheduler.addJob(jobDetail, true);
logger.info("init end.......................");
}
第二个问题与第三个是互相关联的,我想到要解决这两个问题的一个方案是Job中不要包含SessionFactory就没一切OK了,因为SessionFactory是hibernatedao的属性,而hibernatedao是SimpleService的属性,因此SimpleService不能有任何hibernatedao属性了。如此SimpleService业务方法里需要的hibernatedao又如何获取呢?对spring的了解,我们知道可以通过ApplicationContext获取到任何springbean.
但是在这里ApplicationContext又怎么获取呢? ...
查看org.springframework.web.context.ContextLoaderListener
找到org.springframework.web.context.ContextLoader.getCurrentWebApplicationContext()可以获取到ApplicationContext,增加一个SpringBeanService类,实现序列化接口,通过SpringBeanService可以获取到web己经加载的springbean
package com.sundoctor.example.service;
import java.io.Serializable;
import org.springframework.context.ApplicationContext;
import org.springframework.stereotype.Service;
import org.springframework.web.context.ContextLoader;
@SuppressWarnings("unchecked")
@Service("springBeanService")
public class SpringBeanService implements Serializable{
private static final long serialVersionUID = -2228376078979553838L;
public <T> T getBean(Class<T> clazz,String beanName){
ApplicationContext context = ContextLoader.getCurrentWebApplicationContext();
return (T)context.getBean(beanName);
}
}
因为HibernateDao不再持久到Job中,所在不再需要实现序列化接口,可以继承HibernateDaoSupport,当然也可以不继承,可以根据自己喜好的方式编写,不再有任何限制
因为hibernatedao 不再实现序列化接口和继承自HibernateDaoSupport,不能再注入到业务类中了。
在业务类中注入以上的SpringBeanService,业务方法需要的hibernatedao通过以上的SpringBeanService.getBean获取
package com.sundoctor.example.service;
import java.io.Serializable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Service;
import com.sundoctor.example.dao.CustomerHibernateDao;
import com.sundoctor.example.model.Customer;
@Service("customerService")
public class CustomerService implements Serializable {
private static final long serialVersionUID = -6857596724821490041L;
private static final Logger logger = LoggerFactory.getLogger(CustomerService.class);
private SpringBeanService springBeanService;
@Autowired
public void setSpringBeanService(@Qualifier("springBeanService")
SpringBeanService springBeanService) {
this.springBeanService = springBeanService;
}
public void testMethod1() {
// 这里执行定时调度业务
CustomerHibernateDao customerDao =springBeanService.getBean
(CustomerHibernateDao.class,"customerDao");
Customer customer = customerDao.getCustomer1();
logger.info("AAAA:{}", customer);
}
}
实际上可以直接从ApplicationContext中获取到customerDao对象.不需要再写个什么SpringBeanService接口.
三个主要问题就这样解决了。
附件中的其它源码介绍可以参考《Quartz在Spring 中如何动态配置时间》http://www.iteye.com/topic/399980?page=1和《Quartz任务监控管理 (1)》http://www.iteye.com/topic/441951?page=1。
quartzMonitor2.part1.rar (4.8 MB)
下载次数: 960
quartzMonitor2.part3.rar (3.5 MB)
下载次数: 892
quartzMonitor2.part2.rar (4.8 MB)
下载次数: 892
>>LZ答疑:
Ask:请教楼主: 怎么实现多个JobDetail,或者实现一个调用testMethod1(),一个调用testMethod2()
在配置文件里增加一个JobDetail,如:
<?xml version="1.0"encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN 2.0//EN""http://www.springframework.org/dtd/spring-beans-2.0.dtd">
<beans>
<bean name="quartzScheduler"class="org.springframework.scheduling.quartz.SchedulerFactoryBean">
<property name="dataSource"ref="dataSource"/>
<propertyname="applicationContextSchedulerContextKey"value="applicationContextKey"/>
<propertyname="configLocation"value="classpath:quartz.properties"/>
<propertyname="startupDelay" value="30"/>
<propertyname="overwriteExistingJobs" value="true"/>
<propertyname="jobDetails" >
<list>
<ref bean="jobDetail"/>
<refbean="jobDetail2"/>
</list>
</property>
</bean>
<bean id="jobDetail"class="frameworkx.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean">
<propertyname="shouldRecover" value="true"/>
<propertyname="targetObject" ref="customerService"/>
<propertyname="targetMethod" value="testMethod1"/>
</bean>
<beanid="jobDetail2"class="frameworkx.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean">
<propertyname="shouldRecover" value="true"/>
<propertyname="targetObject" ref="customerService"/>
<propertyname="targetMethod" value="testMethod2"/>
</bean>
</beans>
Ask:public class SpringBeanService implements Serializable
也就是说在持久化时,都必须将这个SpringBeanService 序列化到数据库中,若是service还依赖其他的业务逻辑,那就关联的东西就太大了。其实是可以更简单一点的,在quartzScheduler中
<propertyname="applicationContextSchedulerContextKey"value="applicationContextKey"/>
在代码中直接引用这个applicationContext就OK了
public void execute(JobExecutionContext context) throwsJobExecutionException{
SchedulerContextschedulerContext = null;
try{
schedulerContext = context.getScheduler().getContext();
if (null == schedulerContext){
return;
}
}
catch(SchedulerException e) {
e.printStackTrace();
}
this.executionContext =context;
this.applicationContext= (ApplicationContext) schedulerContext
.get(TaskConstant.CONSTANT_APPLICATION_CONTEXT_KEY);
init();
execute();
}
protected abstract void init(); // 自己初始化Bean
这样就可以避免序列化Service时引入依赖的业务逻辑
感谢你的思路,但是太麻烦了,不需如此。
Quartz已经提供了Job实例化的入口:
1. public interface JobFactory {
2. Job newJob(TriggerFiredBundle bundle) throws SchedulerException;
3. }
可以构造一个SpringJobFactory对象,注入Spring ApplicationContext对象。
把任务对象在ApplicationContext配置的名称和要执行的方法放在jobDataMap中。
用SpringJobFactory构造一个Job代理类调用任务对象的方法。
这尼玛意思??????????????????????????????????????????????????????????????????
>>总结:
Quartz任务监控管理(1)实际上只要看懂了Spring+Quartz任务调度就可以了.
Quartz任务监控管理(2)实际上没什么可以看的.
1.库里已存在trigger的情况:只要一个配置就解决:
采用上面修改过的MethodInvokingJobDetailFactoryBean,是为了解决自己实现QuartzJobBean的问题.
有了这种方法.就不需要自己实现QuartzJobBean了.用法跟静态配置时的MethodInvokingJobDetailFactoryBean一样,
只不过添加了一个属性:<property name="shouldRecover" value="true"/>
<property name="overwriteExistingJobs" value="true"/>
<property name="jobDetails" >
<list>
<ref bean="jobDetail"/>
</list>
</property>
如果多个jobDetail存在的情况下,上面jobDetails也不能解决问题.因为SchedulerFactoryBean根本就没有jobDetails这个属性.谈何把多个jobDetail加入到Schedule中进行调度.
后来发现原来是版本的问题:spring2.5的SchedulerFactoryBean没有overwriteExistingJobs和jobDetails属性.那么有没有对应的解决办法?
另外还有一个问题,使用MethodInvokingJobDetailFactoryBean后,
<beanid="jobDetail"class="frameworkx.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean">
<propertyname="shouldRecover" value="true"/>
<propertyname="targetObject" ref="simpleService"/>
<propertyname="targetMethod"value="testMethod1"/>
</bean>
这个 targetMethod怎么来配置动态参数,而不是写死到里面?
targetMethod方法里怎么获取JobExecutionContext这个方法
即SimpleService我们的业务类如何才能传入JobExecutionContext对象.
因为集成QuartzJobBean是可以得到这个对象的.但是没有继承的话如何获取呢?
因为我们的业务方法有可能是通过获取这个对象,然后获取到当前操作的任务的名字.我们需要根据这个名字对数据库进行一些其他的操作.如果没有了这个对象,我们如何才能获取到当前操作的任务的名字,这是个很严重的问题滴.
看看官方2.5版的SchedulerFactoryBean文档:
FactoryBean
that creates and configures a Quartz Scheduler
,manages its lifecycle as part of the Spring application context, and exposesthe Scheduler as bean reference for dependency injection.
Allows registration of JobDetails, CalendarsandTriggers, automatically starting the scheduleron initialization and shutting it down on destruction. In scenarios that justrequire staticregistration of jobs at startup, there isno need to access the Scheduler instance itself inapplication code.不需要获取Scheduler实例.因为在应用程序启动的时候会被静态地注册.
For dynamic registration ofjobs at runtime, use a bean reference to this SchedulerFactoryBean to getdirect access to the Quartz Scheduler (org.quartz.Scheduler
).在运行时动态注册Jobs,用一个Bean引用SchedulerFactoryBean来获取Scheduler对象.(我们项目中使用了SchedulerSerivce,使用了Scheduler对象,来引用到SchedulerFactoryBean).Thisallows you to create new jobs and triggers, and also to control and monitor theentire Scheduler.
Note that Quartz instantiates实例 a new Job for each execution, incontrast to对比Timer which uses a TimerTask instance that is shared between repeatedexecutions.Just JobDetaildescriptors are shared.
When using persistent jobs持久化Jobs到数据库中时, it isstrongly recommended to perform all operations on the Scheduler withinSpring-managed (or plain JTA) transactions操作在Scheduler对象上的事务的控制.Otherwise, database locking will not properly work and might even break. (Seethe setDataSource
javadoc for details.)
The preferred way to achieve transactional execution is todemarcate标定declarative陈述,宣言transactions at the business facade level业务层, which will automatically applyto Scheduler operations performed within those scopes. Alternatively, you mayadd transactional advice for the Scheduler itself.
咔咔,好像没有思路哎.虽然文档说可以注册jobDetails,calendars或者triggers.但是jobDetails,calendars在2.5中真的能用吗?看看之前所在公司的做法能不能得到启示:
<!--Quartz集群Schduler -->
<beanid="clusterQuartzScheduler"lazy-init="false"class="org.springframework.scheduling.quartz.SchedulerFactoryBean">
<!-- Triggers集成 -->
<propertyname="triggers">
<list>
<refbean="timerTrigger"/>
<refbean="syncUser"/>
</list>
</property>
<!-- quartz配置文件路径 -->
<propertyname="configLocation"value="classpath:quartz-cluster.properties"/>
<!-- 启动时延期2秒开始任务 -->
<propertyname="startupDelay"value="15"/>
<!-- 保存Job数据到数据库所需的数据源 -->
<propertyname="dataSource"ref="quartzDataSource"/>
<!-- Job接受applicationContext的成员变量名 -->
<propertyname="applicationContextSchedulerContextKey"value="applicationContext"/>
</bean>
<!-- Timer式 SimpleTrigger定义-->
<beanid="timerTrigger"class="org.springframework.scheduling.quartz.SimpleTriggerBean">
<propertyname="jobDetail"ref="timerJobDetail2"/>
<propertyname="repeatInterval"value="864000000"/>
</bean>
<!--Timer JobDetail, 基于JobDetailBean实例化Job Class,可持久化到数据库实现集群 -->
<beanname="timerJobDetail"class="org.springframework.scheduling.quartz.JobDetailBean">
<propertyname="jobClass"value="com.sgm.crdn.schedule.QuartzClusterableJob"/>
<!-- fail-over节点重新执行之前所有失败或未执行的任务,默认为false. -->
<propertyname="requestsRecovery"value="true"/>
</bean>
<!--定时同步用户的任务[每天早上6点触发]-->
<beanid="syncUser"class="org.springframework.scheduling.quartz.CronTriggerBean">
<propertyname="jobDetail"ref="syncUserDetail"/>
<propertyname="cronExpression"value="0 0 6 * * ?"/>
</bean>
<beanname="syncUserDetail"class="org.springframework.scheduling.quartz.JobDetailBean">
<propertyname="jobClass"value="com.sgm.crdn.schedule.QuartzSyncUser"/>
<propertyname="requestsRecovery"value="true"/>
</bean>
<!--Timer Job的可配置属性 -->
<util:mapid="timerJobConfig">
<entrykey="nodeName"value="${server.node_name}"/>
</util:map>
很奇怪,一个任务配置了2个,一个是SimpleTrigger,一个是JobDetail..
看看实现自定义QuartzJobBean的类是什么写的:
/**
* 被Spring的QuartzJobDetailBean定时执行的Job类,支持持久化到数据库实现Quartz集群.
* 因为需要被持久化, 不能有用XXManager等不能被持久化的成员变量,
* 只能在每次调度时从QuartzJobBean注入的applicationContext中动态取出.
*/
public class QuartzClusterableJob extendsQuartzJobBean {
private static Logger logger = LoggerFactory.getLogger(QuartzClusterableJob.class);
private ApplicationContextapplicationContext;
//从SchedulerFactoryBean注入的applicationContext.
public void setApplicationContext(ApplicationContextapplicationContext) {
this.applicationContext = applicationContext;
}
// 定时打印当前用户数到日志.
@Override
protected voidexecuteInternal(JobExecutionContext ctx) throws JobExecutionException {
SecurityEntityManager accountManager = applicationContext.getBean
(SecurityEntityManager.class);//获取到业务类
long userCount =accountManager.getUserCount();//在这里处理业务方法
Map config =(Map) applicationContext.getBean("timerJobConfig");
String nodeName = (String) config.get("nodeName");
logger.info("There are {} user in database, printby {}'s job.", userCount,nodeName);
}
}
我们的做法是:在QuartzJobBean中有XXXService变量.引用了Service对象.所以QuartzJobBean中的Service对象需要序列化,而在Service中进行业务方法的处理;
但是实际上Service对象是不能被持久化的.所以师傅的做法是不在QuartzJobBean中定义Service层变量,而是在executeInternal中获取Service对象,然后调用业务方法处理任务的业务方法.这样Service类不需要序列化.
因为通过ApplicationContext获取Dao对象,何不如直接获取到Service对象呢?Soga...
2.在业务类中获取Dao为空的方法:可以在我们的业务方法中直接从ApplicationContext中获取对象就可以了.
不需要再写个SpringBeanService什么鬼接口.
如此而已.不到200字.LZ却写的看起来好复杂..而且重要的问题又都没有解决..~~~~(>_<)~~~~
附录: Quartz 项目应用笔记
http://www.blogjava.net/steady/archive/2007/08/02/134017.html
http://www.cnblogs.com/phinecos/
Quartz是一个强大的企业级 Schedule 工具,也是目前最好的开源 Schedule 工具,
最近因为项目的需要,简单的用到了 Quartz 的一些功能,对项目中使用 Quartz 的一些问题做简单的记录。
在 Quartz 的应用中,我们用到了以下的一些东西,ScheduleFactory,Scheduler, Job, JobDetail, Trigger
SchedulerFactory是 Scheduler 的工厂,我们可以从中获得受工厂管理的 Scheduler 对象。
SchedulerFactoryscheduleFactory = new StdSchedulerFactory();
Schedulerscheduler = scheduleFactory.getScheduler();
Scheduler是一个计划集,其中可以包含多个 JobDetail 和 Trigger 组成的计划任务。
我们可以从 SchedulerFactory 中取得 Scheduler。
接口Job是每个业务上需要执行的任务需要实现的接口,该接口只有一个方法:
publicinterface Job {
public void execute(JobExecutionContextcontext)
throws JobExecutionException;
}
我们可以在里面定义我们的 Job 执行逻辑,比如清除过期数据,更新缓存等。
JobDetail描述了一个任务具体的信息,比如名称,组名等等。
JobDetailjobDetail = new JobDetail
("SayHelloWorldJob",Scheduler.DEFAULT_GROUP, SayHelloWorldJob.class);
在上面的构造方法中,第一个是任务的名称,第二个是组名,第三个就是实际当任务需要执行的回调类。
Trigger顾名思义就是触发器,Quartz有个很好的想法就是分离了任务和任务执行的条件。
Trigger就是控制任务执行条件的类,当Trigger认为执行条件满足的时刻,Trigger会通知相关的Job去执行。
分离的好处是:
1.你可以为某个Job关联多个Trigger,其中任何一个条件满足都可以触发job执行,
这样可以完成一些组合的高级触发条件
2.当Trigger失效后(比如:一个永远都不能满足的条件),你不必去声明一个新的job,
代替的是你可以为job关联一个新的Trigger让job可以继续执行。
目前的Quartz实现中,存在两种Trigger: SimpleTrigger和CronTrigger,
SimpleTrigger用来完成一些比如固定时间执行的任务,比如:从现在开始1分钟后等等;
而CronTrigger用来执行calendar-like的任务,比如:每周五下午3:00,每月最后一天等等。
在我们项目中,都是一些固定时间的 Job,所以只用到了 SimpleTrigger。
Triggertrigger = new SimpleTrigger
("SayHelloWorldJobTrigger",Scheduler.DEFAULT_GROUP,newDate(),null,0,0L);
这个构造方法中,第一个是Trigger的名称,第二个是Trigger的组名,第三个是任务开始时间,第四个是结束时间,
第五个是重复次数(使用SimpleTrigger.REPEAT_INDEFINITELY常量表示无限次),
最后一个是重复周期(单位是毫秒),那么这样就创建了一个立刻并只执行一次的任务。
但我们定义好了 JobDetail,Job,和 Trigger 后,就可以开始 Schedule 一个 Job 了。
scheduler.scheduleJob(jobDetail,trigger);
这条语句就是把job和Trigger关联,这样当Trigger认为应该触发的时候就会调用(实际上是Scheduler调用)job.execute方法了。
scheduler.start();
千万别忘了加上上面的语句,这条语句通知Quartz使安排的计划生效。
关于execute方法的参数JobExecutionContext
JobExecutionContext就和很多Context结尾的类功能一样,提供的运行时刻的上下文环境,
JobExecutionContext中有Scheduler,JobDetail,Trigger等很多对象的引用,
从而当你在execute方法内部须需要这些对象的时刻提供的便利。
在项目中,我们把需要执行的 Job 相对应的一些信息放在 JobExecutionContext 中,在 Job 执行的时候可以调用。
jobDetail.getJobDataMap().put(userid,id);
在 Job 中,我们可以拿到相关的 Context 信息:
jobExecutionContext.getJobDetail().getJobDataMap().getInt(userid);
JobDetail和Trigger的name和group
Scheduler实例对应了很多job和trigger的实例,为了方便的区分,Quartz使用name和group这两个特性,
正如你想象的一样,同一个group下不能有两个相同name的JobDetail,Trigger
同理,同一个Scheduler下不能有两个相同group的JobDetail,Trigger
同理,JobDetail和Trigger的完全限定名为:group + name
为了让服务器重启以后,我们的 Scheduler 信息仍然不丢失,我们通常采用数据库持久化 Scheduler 的信息。
DBScript在 Quartz 的下载包中的:quartz-1.6.0\docs\dbTables下,
选择自己使用的 DB 相应的 Script 导入数据库就可以了。
在应用中,我们需要配置一个 quartz.properties 才能正常使用 DB。
我们可以在 quartz-1.6.0\examples\example10中找到该文件的样例,
稍作一些修改,就可以放到自己项目源码的根目录下使用了。
设置org.quartz.jobStore.class = org.quartz.impl.jdbcjobstore.JobStoreTX
即可启用基于 JDBC 的 Quartz 信息持久化。
根据项目情况设置以下配置信息:
org.quartz.jobStore.class= org.quartz.impl.jdbcjobstore.JobStoreTX
org.quartz.jobStore.driverDelegateClass= org.quartz.impl.jdbcjobstore.StdJDBCDelegate
org.quartz.jobStore.useProperties= false
org.quartz.jobStore.dataSource= myDS
org.quartz.jobStore.tablePrefix= QRTZ_
org.quartz.jobStore.isClustered= false
org.quartz.dataSource.myDS.driver= com.mysql.jdbc.Driver
org.quartz.dataSource.myDS.URL= jdbc:mysql://localhost:3306/myapplication
org.quartz.dataSource.myDS.user= root
org.quartz.dataSource.myDS.password=
org.quartz.dataSource.myDS.maxConnections= 5
但是光设置了 Database 不够,我们还需要在 Application 启动的时候自动启动 Scheduler 才行,
我们只需要简单的写一个 Servlet 的 Listener 并在 web.xml 中声明该 Listener ,
在 Servlet 容易启动的时候,Scheduler 就开始自动执行。
publicclass ScheduleStartListener implements ServletContextListener {
public void contextInitialized(ServletContextEventservletContextEvent) {
try {
scheduleFactory.getScheduler().start();
} catch (SchedulerException e) {
// write log
}
}
public voidcontextDestroyed(ServletContextEvent servletContextEvent) {
try {
scheduleFactory.getScheduler().shutdown();
} catch (SchedulerException e) {
// write log
}
}
}
在 web.xml 里面加入以下配置:
<listener>
<listener-class>org.agilejava.scheduler.ScheduleStartListener</listener-class>
</listener>
Reschedule重启任务
rescheduleJob(StringtriggerName, String groupName, Trigger newTrigger)
在进行 reschedule 操作的时候,我们通常只需要修改 Trigger 的时间,这时候我们只需要重新 new 一个含有新的 Schedule 时间的 Trigger 对象,reschedule 一下就可以了。
Unschedule删除任务
unscheduleJob(StringtriggerName, String groupName)
进行 unschedule 的时候,我们只需要知道名字和 group 就可以了。
进行 Schedule 操作前后,Database 中的相关数据都会被更改,在执行 unschedule 或者该 schedule 已经执行过,数据库中的 trigger 信息都会被删除。
<!-- Quartz集群Schduler -->
<bean id="clusterQuartzScheduler" lazy-init="false"
class="org.springframework.scheduling.quartz.SchedulerFactoryBean"> <!-- Triggers集成 --> <property name="triggers"> <list> <ref bean="timerTrigger"/>
<ref bean="syncUser"/>
</list>
</property>
<!-- quartz配置文件路径 -->
<property name="configLocation" value="classpath:quartz-cluster.properties" /> <!-- 启动时延期2秒开始任务 -->
<property name="startupDelay" value="15" /> <!-- 保存Job数据到数据库所需的数据源 -->
<property name="dataSource" ref="quartzDataSource" /> <!-- Job接受applicationContext的成员变量名 -->
<property name="applicationContextSchedulerContextKey" value="applicationContext" />
</bean>
<!-- Timer式 SimpleTrigger定义-->
<bean id="timerTrigger" class="org.springframework.scheduling.quartz.SimpleTriggerBean"> <property name="jobDetail" ref="timerJobDetail2" /> <property name="repeatInterval" value="864000000" />
</bean>
<!-- Timer JobDetail, 基于JobDetailBean实例化Job Class,可持久化到数据库实现集群 -->
<bean name="timerJobDetail" class="org.springframework.scheduling.quartz.JobDetailBean"> <property name="jobClass" value="com.sgm.crdn.schedule.QuartzClusterableJob" /> <!-- fail-over节点重新执行之前所有失败或未执行的任务, 默认为false. --> <property name="requestsRecovery" value="true" /> </bean>
<!--定时同步用户的任务[每天早上6点触发]-->
<bean id="syncUser" class="org.springframework.scheduling.quartz.CronTriggerBean"> <property name="jobDetail" ref="syncUserDetail" />
<property name="cronExpression" value="0 0 6 * * ?"/>
</bean>
<bean name="syncUserDetail" class="org.springframework.scheduling.quartz.JobDetailBean"> <property name="jobClass" value="com.sgm.crdn.schedule.QuartzSyncUser" /> <property name="requestsRecovery" value="true" />
</bean>
<!-- Timer Job的可配置属性 --> <util:map id="timerJobConfig"> <entry key="nodeName" value="${server.node_name}" /> </util:map>
很奇怪,一个任务配置了2个,一个是SimpleTrigger,一个是JobDetail.. 看看实现自定义QuartzJobBean的类是什么写的: /**
* 被Spring的Quartz JobDetailBean定时执行的Job类, 支持持久化到数据库实现Quartz集群. * 因为需要被持久化, 不能有用XXManager等不能被持久化的成员变量,
* 只能在每次调度时从QuartzJobBean注入的applicationContext中动态取出. */
public class QuartzClusterableJob extends QuartzJobBean { private static Logger logger = LoggerFactory.getLogger(QuartzClusterableJob.class); private ApplicationContext applicationContext; //从SchedulerFactoryBean注入的applicationContext.
public void setApplicationContext(ApplicationContext applicationContext) { this.applicationContext = applicationContext;
}
// 定时打印当前用户数到日志. @Override
protected void executeInternal(JobExecutionContext ctx) throws JobExecutionException {
SecurityEntityManager accountManager = applicationContext.getBean
(SecurityEntityManager.class); //获取到业务类
long userCount = accountManager.getUserCount();//在这里处理业务方法
Map config = (Map) applicationContext.getBean("timerJobConfig");
String nodeName = (String) config.get("nodeName");
logger.info("There are {} user in database, print by {}'s job.", userCount, nodeName); }
}
我们的做法是:在QuartzJobBean中有XXXService变量.引用了Service对象.所以QuartzJobBean中的Service对象需要序列化,而在Service中进行业务方法的处理;
但是实际上Service对象是不能被持久化的.所以师傅的做法是不在QuartzJobBean中定义Service层变量,而是在executeInternal中获取Service对象,然后调用业务方法处理任务的业务方法.这样Service类不需要序列化. 因为通过ApplicationContext获取Dao对象,何不如直接获取到Service对象呢?Soga...
2.在业务类中获取Dao为空的方法:可以在我们的业务方法中直接从ApplicationContext中获取对象就可以了. 不需要再写个SpringBeanService什么鬼接口.
如此而已.不到200字.LZ却写的看起来好复杂..而且重要的问题又都没有解决..~~~~(>_<)~~~~
附录: Quartz 项目应用笔记
http://www.blogjava.net/steady/archive/2007/08/02/134017.html
http://www.cnblogs.com/phinecos/
Quartz 是一个强大的企业级 Schedule 工具,也是目前最好的开源 Schedule 工具,
最近因为项目的需要,简单的用到了 Quartz 的一些功能,对项目中使用 Quartz 的一些问题做简单的记录。
在 Quartz 的应用中,我们用到了以下的一些东西,ScheduleFactory, Scheduler, Job, JobDetail, Trigger
SchedulerFactory 是 Scheduler 的工厂,我们可以从中获得受工厂管理的 Scheduler 对象。
SchedulerFactory scheduleFactory = new StdSchedulerFactory(); Scheduler scheduler = scheduleFactory.getScheduler();
Scheduler 是一个计划集,其中可以包含多个 JobDetail 和 Trigger 组成的计划任务。 我们可以从 SchedulerFactory 中取得 Scheduler。
接口Job是每个业务上需要执行的任务需要实现的接口,该接口只有一个方法:
public interface Job {
public void execute(JobExecutionContext context) throws JobExecutionException; }
我们可以在里面定义我们的 Job 执行逻辑,比如清除过期数据,更新缓存等。
JobDetail描述了一个任务具体的信息,比如名称,组名等等。 JobDetail jobDetail = new JobDetail
("SayHelloWorldJob", Scheduler.DEFAULT_GROUP, SayHelloWorldJob.class);
在上面的构造方法中,第一个是任务的名称,第二个是组名,第三个就是实际当任务需要执行的回调类。
Trigger顾名思义就是触发器,Quartz有个很好的想法就是分离了任务和任务执行的条件。
Trigger就是控制任务执行条件的类,当Trigger认为执行条件满足的时刻,Trigger会通知相关的Job去执行。 分离的好处是:
1.你可以为某个Job关联多个Trigger,其中任何一个条件满足都可以触发job执行, 这样可以完成一些组合的高级触发条件
2.当Trigger失效后(比如:一个永远都不能满足的条件),你不必去声明一个新的job, 代替的是你可以为job关联一个新的Trigger让job可以继续执行。
目前的Quartz实现中,存在两种Trigger: SimpleTrigger和CronTrigger,
SimpleTrigger用来完成一些比如固定时间执行的任务,比如:从现在开始1分钟后等等; 而CronTrigger用来执行calendar-like的任务,比如:每周五下午3:00,每月最后一天等等。
在我们项目中,都是一些固定时间的 Job,所以只用到了 SimpleTrigger。 Trigger trigger = new SimpleTrigger
("SayHelloWorldJobTrigger",Scheduler.DEFAULT_GROUP,new Date(),null,0,0L);
这个构造方法中,第一个是Trigger的名称,第二个是Trigger的组名,第三个是任务开始时间,第四个是结束时间, 第五个是重复次数(使用SimpleTrigger.REPEAT_INDEFINITELY常量表示无限次), 最后一个是重复周期(单位是毫秒),那么这样就创建了一个立刻并只执行一次的任务。
但我们定义好了 JobDetail,Job,和 Trigger 后,就可以开始 Schedule 一个 Job 了。
scheduler.scheduleJob(jobDetail, trigger);
这条语句就是把job和Trigger关联,这样当Trigger认为应该触发的时候就会调用(实际上是Scheduler调用)job.execute方法了。
scheduler.start();
千万别忘了加上上面的语句,这条语句通知Quartz使安排的计划生效。
关于execute方法的参数JobExecutionContext
JobExecutionContext就和很多Context结尾的类功能一样,提供的运行时刻的上下文环境, JobExecutionContext中有Scheduler,JobDetail,Trigger等很多对象的引用, 从而当你在execute方法内部须需要这些对象的时刻提供的便利。
在项目中,我们把需要执行的 Job 相对应的一些信息放在 JobExecutionContext 中,在 Job 执行的时候可以调用。
jobDetail.getJobDataMap().put(userid, id);
在 Job 中,我们可以拿到相关的 Context 信息:
jobExecutionContext.getJobDetail().getJobDataMap().getInt(userid);
JobDetail和Trigger的name和group
Scheduler实例对应了很多job和trigger的实例,为了方便的区分,Quartz使用name和group这两个特性, 正如你想象的一样,同一个group下不能有两个相同name的JobDetail,Trigger 同理,同一个Scheduler下不能有两个相同group的JobDetail,Trigger 同理,JobDetail和Trigger的完全限定名为:group + name
为了让服务器重启以后,我们的 Scheduler 信息仍然不丢失,我们通常采用数据库持久化 Scheduler 的信息。 DBScript 在 Quartz 的下载包中的:quartz-1.6.0\docs\dbTables 下, 选择自己使用的 DB 相应的 Script 导入数据库就可以了。
在应用中,我们需要配置一个 quartz.properties 才能正常使用 DB。 我们可以在 quartz-1.6.0\examples\example10 中找到该文件的样例, 稍作一些修改,就可以放到自己项目源码的根目录下使用了。
设置 org.quartz.jobStore.class = org.quartz.impl.jdbcjobstore.JobStoreTX 即可启用基于 JDBC 的 Quartz 信息持久化。
根据项目情况设置以下配置信息:
org.quartz.jobStore.class = org.quartz.impl.jdbcjobstore.JobStoreTX
org.quartz.jobStore.driverDelegateClass = org.quartz.impl.jdbcjobstore.StdJDBCDelegate org.quartz.jobStore.useProperties = false org.quartz.jobStore.dataSource = myDS org.quartz.jobStore.tablePrefix = QRTZ_ org.quartz.jobStore.isClustered = false
org.quartz.dataSource.myDS.driver = com.mysql.jdbc.Driver
org.quartz.dataSource.myDS.URL = jdbc:mysql://localhost:3306/myapplication org.quartz.dataSource.myDS.user = root org.quartz.dataSource.myDS.password =
org.quartz.dataSource.myDS.maxConnections = 5
但是光设置了 Database 不够,我们还需要在 Application 启动的时候自动启动 Scheduler 才行, 我们只需要简单的写一个 Servlet 的 Listener 并在 web.xml 中声明该 Listener , 在 Servlet 容易启动的时候,Scheduler 就开始自动执行。
public class ScheduleStartListener implements ServletContextListener { public void contextInitialized(ServletContextEvent servletContextEvent) { try {
scheduleFactory.getScheduler().start(); } catch (SchedulerException e) { // write log } }
public void contextDestroyed(ServletContextEvent servletContextEvent) { try {
scheduleFactory.getScheduler().shutdown(); } catch (SchedulerException e) { // write log } } }
在 web.xml 里面加入以下配置: <listener>
<listener-class>org.agilejava.scheduler.ScheduleStartListener</listener-class> </listener>
Reschedule 重启任务
rescheduleJob(String triggerName, String groupName, Trigger newTrigger)
在进行 reschedule 操作的时候,我们通常只需要修改 Trigger 的时间,这时候我们只需要重新 new 一个含有新的 Schedule 时间的 Trigger 对象,reschedule 一下就可以了。
Unschedule 删除任务
unscheduleJob(String triggerName, String groupName)
进行 unschedule 的时候,我们只需要知道名字和 group 就可以了。
进行 Schedule 操作前后,Database 中的相关数据都会被更改,在执行 unschedule 或者该 schedule 已经执行过,数据库中的 trigger 信息都会被删除