在开发的项目中有一个需求,需要灵活配置调度任务时间,并能自由启动或停止调度。 马上想到了在另外一个项目中用到的Quartz这个开源调度组件它提供了强大的任务调度机制,允许开发人员灵活的定义触发器的调度时间,并可对触发器和任务进行关联映射。此外,Quartz提供了调度运行环境的持久化机制,可以保存并恢复调度现场,即使系统因故障关闭,任务调度现场数据并不会丢失。
Quartz对任务调度的领域问题进行了高度抽象,提出了调度器、任务和触发器这三个核心概念。
1 Job:这是quartz的任务接口。Job来决定了任务是干什么的。Job暴露了一个接口execute,这里是我们写我们的任务逻辑的地方。Job运行时的信息保存在JobDataMap实例中。
2 JobDetail:这也是quartz的一个接口,它其中存放了Job.class和Job的一些描述性信息,例如Job的名字、组名等。
2 Trigger:这是quartz的触发器。它决定了绑定的JobDetail什么时候执行,就是决定任务的执行的时间规则。
3 Calender是quartz的日历接口。它决定绑定的任务(JobDetails)什么时候不执行。Trigger是决定什么时候执行。二者可以配合使用。例如每月一号执行,但是1月1号不执行。
4 Scheduler:代表一个Quartz的独立运行容器,Trigger和JobDetail可以注册到Scheduler。Scheduler可以将Trigger绑定到某一个JobDetail中,这样当Trigger被触发时,对应的Job就被执行。一个Scheduler可以拥有多个Trigger和多个JobDatail,一个Job可以对应多个Trigger,但一个Trigger只能对应一个Job。
5 ThreadPool:这是quartz暴露的一个线程池接口。线程池是任务执行的基础。在并发任务下,每个任务启用一个线程,如果线程池中的线程已经用完,再来任务时需要等待,直到有线程可用。如果等待时间过长,超过了org.quartz.jobStore.misfireThreshold设置的时间,任务不再执行。
Job有一个StatefulJob子接口,代表有状态的任务,该接口是一个没有方法的标签接口,其目的是让Quartz知道任务的类型,以便采取不同的执行方案。无状态任务在执行时拥有自己的JobDataMap复制,对JobDataMap的更改不会影响下次的执行,而又状态的任务共享同一个JobDataMap实例,每次任务执行对JobDataMap所做的更改会保存下来,后面的执行可以看到这个更改,即每次执行任务后对后面的执行发生影响。
因此,无状态的Job可以并发执行,而有状态的StatefulJob不能并发执行,也就是如果前次的StatefulJob还没有执行完毕,下一次的任务将阻塞等待,直到前次任务执行完毕。有状态任务比无状态任务需要考虑更多的因素,程序往往更复杂,因此除非必要,应尽量避免使用有状态的Job。
如果Quartz使用了数据库持久化任务调度信息,无状态的JobDataMap仅会在Scheduler注册任务时保存一次,而有状态任务对应的JobDataMap在每次执行任务后都会进行保存。
Spring为创建Quartz的Scheduler、Trigger和JobDetail提供了便利的FactoryBean类,以便在Spring容器中享受到注入的好处。所以Spring结合Quartz静态配置调度任务时间,非常easy。
通常情况下,任务都定义在一个业务类方法中。但是按照Quartz Job接口的定义,需要定义一个引用业务类方法的实现类。为了避免创建这个只包含一行调用代码的Job实现类,Spring提供了MethodInvokingJobDetailFactoryBean,借由该FactoryBean,可以将一个Bean的某个方法封装成满足Quartz要求的Job,示例代码使用的就是这种方式。
具体的任务如下:
一、编写业务类
二、配置spring的applicationContext.xml文件
1 配置任务JobDetailBean
2 配置触发器CrontriggerBean
3配置调度器 SchedulerFactoryBean
三、把quartz.properties放到类路径下
<!-- quartz定时器配置 --> <bean id="emailTask" class="com.quartzdemo.service.impl.CheckSendEmail" /> <bean id="jobDetailBean" class="org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean"> <property name="targetObject" ref="emailTask"></property> <property name="targetMethod" value="execute"></property> </bean> <!-- 触发器 --> <bean id="cronTrigger" class="org.springframework.scheduling.quartz.CronTriggerBean"> <!-- 指向我们的任务 --> <property name="jobDetail" ref="jobDetailBean" /> <!-- 执行时间 每天上午11点运行--> <property name="cronExpression" value="0 0 11 * * ?" /> </bean> <!-- 调度器 --> <bean id="schdulerFactory" lazy-init="false" class="org.springframework.scheduling.quartz.SchedulerFactoryBean"> <property name="triggers"> <list> <!-- 触发器列表 --> <ref bean="cronTrigger" /> </list> </property> <property name="configLocation" value="classpath:quartz.properties"> </property> </bean>
jobDetailBean将emailTask#execute方法封装成一个任务,此处没有设置任务的类型,默认情况下封装成武状态的任务。execute方法可以是static,也可以是非static的,但不能拥有方法入参。通过MethodInvokingJobDetailFactoryBean产生的JobDetail不能被序列化,所以不能被持久化到数据库中,如果希望使用持久化任务,用户只能创建正规的Quartz的Job实现类了。
cronExpression表达式就不说了,可以在网上查到相关资料。
使用的jar包主要是spring和quartz,也可以在网上找到。
quartz.properties文件的内容(默认放在类路径下)
# Configure Main Scheduler Properties #============================================================================ org.quartz.scheduler.instanceName = DefaultQuartzScheduler org.quartz.scheduler.instanceId = AUTO org.quartz.scheduler.rmi.export = false org.quartz.scheduler.rmi.proxy = false org.quartz.scheduler.wrapJobExecutionInUserTransaction = 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 #============================================================================ #org.quartz.jobStore.class = org.quartz.impl.jdbcjobstore.JobStoreTX #org.quartz.jobStore.driverDelegateClass = org.quartz.impl.jdbcjobstore.oracle.OracleDelegate org.quartz.jobStore.misfireThreshold = 60000 #org.quartz.jobStore.useProperties = false #org.quartz.jobStore.tablePrefix = QRTZ_ #org.quartz.jobStore.dataSource = myDS #org.quartz.jobStore.isClustered = true #org.quartz.jobStore.clusterCheckinInterval = 15000 #============================================================================ # Configure DataSource #============================================================================ org.quartz.dataSource.myDS.driver = com.mysql.jdbc.Driver org.quartz.dataSource.myDS.URL = jdbc:mysql://localhost/test org.quartz.dataSource.myDS.user = root org.quartz.dataSource.myDS.password = root org.quartz.dataSource.myDS.maxConnections = 10