quartz 在spring框架中的使用

Quartz

源文: http://www.blogjava.net/baoyaer/articles/155645.html

了解Quartz体系结构

Quartz对任务调度的领域问题进行了高度的抽象,提出了调度器、任务和触发器这3个核心的概念,
并在org.quartz通过接口和类对重要的这些核心概念进行描述


Job:是一个接口,只有一个方法void execute(JobExecutionContext context),开发者实现该接口定义运行任务,
JobExecutionContext类提供了调度上下文的各种信息。Job运行时的信息保存在JobDataMap实例中;

JobDetail:Quartz在每次执行Job时,都重新创建一个Job实例,所以它不直接接受一个Job的实例,相反它接收一个Job实现类,
以便运行时通过newInstance()的反射机制实例化Job。
因此需要通过一个类来描述Job的实现类及其它相关的静态信息,如Job名字、描述、关联监听器等信息,JobDetail承担了这一角色。
通过该类的构造函数可以更具体地了解它的功用:
JobDetail(java.lang.String name, java.lang.String group, java.lang.Class jobClass),
该构造函数要求指定Job的实现类,以及任务在Scheduler中的组名和Job名称;

Trigger:是一个类,描述触发Job执行的时间触发规则。主要有SimpleTrigger和CronTrigger这两个子类。
当仅需触发一次或者以固定时间间隔周期执行,SimpleTrigger是最适合的选择;
而CronTrigger则可以通过Cron表达式定义出各种复杂时间规则的调度方案:如每早晨9:00执行,周一、周三、周五下午5:00执行等

Calendar:org.quartz.Calendar和java.util.Calendar不同,它是一些日历特定时间点的集合
(可以简单地将org.quartz.Calendar看作java.util.Calendar的集合——java.util.Calendar代表一个日历时间点,
无特殊说明后面的Calendar即指org.quartz.Calendar)。一个Trigger可以和多个Calendar关联,以便排除或包含某些时间点。

假设,我们安排每周星期一早上10:00执行任务,但是如果碰到法定的节日,任务则不执行,
这时就需要在Trigger触发机制的基础上使用Calendar进行定点排除。针对不同时间段类型,
Quartz在org.quartz.impl.calendar包下提供了若干个Calendar的实现类,
如AnnualCalendar、MonthlyCalendar、WeeklyCalendar分别针对每年、每月和每周进行定义


Scheduler:代表一个Quartz的独立运行容器,Trigger和JobDetail可以注册到Scheduler中,两者在Scheduler中拥有各自的组及名称,
组及名称是Scheduler查找定位容器中某一对象的依据,Trigger的组及名称必须唯一,JobDetail的组和名称也必须唯一(但可以和Trigger的组和名称相同,因为它们是不同类型的)。
Scheduler定义了多个接口方法,允许外部通过组及名称访问和控制容器中Trigger和JobDetail。

Scheduler可以将Trigger绑定到某一JobDetail中,这样当Trigger触发时,对应的Job就被执行。一个Job可以对应多个Trigger,但一个Trigger只能对应一个Job。
可以通过SchedulerFactory创建一个Scheduler实例。Scheduler拥有一个SchedulerContext,它类似于ServletContext,保存着Scheduler上下文信息,
Job和Trigger都可以访问SchedulerContext内的信息。SchedulerContext内部通过一个Map,以键值对的方式维护这些上下文数据,
SchedulerContext为保存和获取数据提供了多个put()和getXxx()的方法。可以通过Scheduler# getContext()获取对应的SchedulerContext实例;

ThreadPool:Scheduler使用一个线程池作为任务运行的基础设施,任务通过共享线程池中的线程提高运行效率

SimpleTrigger拥有多个重载的构造函数,用以在不同场合下构造出对应的实例:

●SimpleTrigger(String name, String group):通过该构造函数指定Trigger所属组和名称;

●SimpleTrigger(String name, String group, Date startTime):除指定Trigger所属组和名称外,还可以指定触发的开发时间;

●SimpleTrigger(String name, String group, Date startTime, Date endTime, int repeatCount, long repeatInterval):
除指定以上信息外,还可以指定结束时间、重复执行次数、时间间隔等参数;

●SimpleTrigger(String name, String group, String jobName, String jobGroup, Date startTime, Date endTime, int repeatCount, long repeatInterval):
这是最复杂的一个构造函数,在指定触发参数的同时,还通过jobGroup和jobName,让该Trigger和Scheduler中的某个任务关联起来。

import java.util.Date;
import org.quartz.Job;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
public class SimpleJob  implements Job{
	@Override
	public void execute(JobExecutionContext arg0) throws JobExecutionException {
		System.out.println(arg0.getTrigger().getName()+ " triggered. time is:" + (new Date()));		
	}
}


import java.util.Date;
import org.quartz.JobDetail;
import org.quartz.Scheduler;
import org.quartz.SchedulerException;
import org.quartz.SchedulerFactory;
import org.quartz.SimpleTrigger;
import org.quartz.impl.StdSchedulerFactory;

public class SimpleTriggerRunner {
	public static void main(String[] args) {
		// 创建一个JobDetail实例,指定SimpleJob
		JobDetail jobDetail = new JobDetail("job1_1", "jGroup1",
				SimpleJob.class);
		// 通过SimpleTrigger定义调度规则:马上启动,每2秒运行一次,共运行100次
		SimpleTrigger simpleTrigger = new SimpleTrigger("trigger1_1", "tgroup1");
		simpleTrigger.setStartTime(new Date());
		simpleTrigger.setRepeatInterval(2000);
		simpleTrigger.setRepeatCount(100);
		// 通过SchedulerFactory获取一个调度器实例
		SchedulerFactory schedulerFactory = new StdSchedulerFactory();
		Scheduler scheduler;
		try {
			scheduler = schedulerFactory.getScheduler();
			scheduler.scheduleJob(jobDetail, simpleTrigger);// 注册并进行调度
			scheduler.start();// 调度启动
		} catch (SchedulerException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
}


CronTrigger 能够提供比 SimpleTrigger 更有具体实际意义的调度方案,调度规则基于 Cron 表达式,CronTrigger 支持日历相关的重复时间间隔(比如每月第一个周一执行),
而不是简单的周期时间间隔。因此,相对于SimpleTrigger而言,CronTrigger在使用上也要复杂一些。

import org.quartz.CronExpression;
import org.quartz.CronTrigger;
import org.quartz.JobDetail;
import org.quartz.Scheduler;
import org.quartz.SchedulerFactory;
import org.quartz.impl.StdSchedulerFactory;
public class CronTriggerRunner {
	public static void main(String args[]) {
		try {
			JobDetail jobDetail = new JobDetail("job1_2", "jGroup1",
					SimpleJob.class);
			// 1:创建CronTrigger,指定组及名称
			CronTrigger cronTrigger = new CronTrigger("trigger1_2", "tgroup1");
			CronExpression cexp = new CronExpression("0/5 * * * * ?");// -2:定义Cron表达式
			cronTrigger.setCronExpression(cexp);// -3:设置Cron表达式
			SchedulerFactory schedulerFactory = new StdSchedulerFactory();
			Scheduler scheduler = schedulerFactory.getScheduler();
			scheduler.scheduleJob(jobDetail, cronTrigger);
			scheduler.start();
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
}

任务调度信息存储

在默认情况下Quartz将任务调度的运行信息保存在内存中,这种方法提供了最佳的性能,因为内存中数据访问最快。
不足之处是缺乏数据的持久性,当程序路途停止或系统崩溃时,所有运行的信息都会丢失。



通过配置文件调整任务调度信息的保存策略

其实Quartz JAR文件的org.quartz包下就包含了一个quartz.properties属性配置文件并提供了默认设置。如果需要调整默认配置,
可以在类路径下建立一个新的quartz.properties,它将自动被Quartz加载并覆盖默认的设置。

先来了解一下Quartz的默认属性配置文件:

配置详细介绍:http://www.quartz-scheduler.org/documentation/quartz-1.x/configuration/ConfigMain

代码清单5 quartz.properties:默认配置

①集群的配置,这里不使用集群
#instanceName属性可为任何值,用在 JDBC JobStore 中来唯一标识实例,但是所有集群节点中必须相同 
org.quartz.scheduler.instanceName = DefaultQuartzScheduler
#属性为 AUTO即可,程序会基于主机名和时间戳来产生实例 ID
org.quartz.scheduler.instanceId = AUTO
#通过rmi作为服务器导出Quartz Scheduler
org.quartz.scheduler.rmi.export = false
#是否支持连接远程服务器的scheduler
org.quartz.scheduler.rmi.proxy = false
#在调用job之前,是否开启一个用户事务
org.quartz.scheduler.wrapJobExecutionInUserTransaction = false

②配置调度器的线程池

#线程池实现类,采用SimpleThreadPool即可
org.quartz.threadPool.class = org.quartz.simpl.SimpleThreadPool
#控制多少个线程被创建用来调度Job
org.quartz.threadPool.threadCount = 10
#设置工作者线程的优先级
org.quartz.threadPool.threadPriority = 5
org.quartz.threadPool.threadsInheritContextClassLoaderOfInitializingThread = true

③配置任务调度现场数据保存机制

org.quartz.jobStore.driverDelegateClass = org.quartz.impl.jdbcjobstore.StdJDBCDelegate
org.quartz.jobStore.useProperties = true
#org.quartz.jobStore.dataSource = myDS
org.quartz.jobStore.tablePrefix = QRTZ_
org.quartz.jobStore.isClustered = true
org.quartz.jobStore.clusterCheckinInterval = 30000
org.quartz.jobStore.maxMisfiresToHandleAtATime=10
#org.quartz.jobStore.doubleCheckLockMisfireHandler=fal

Quartz的属性配置文件主要包括三方面的信息:

1)集群信息;
2)调度器线程池;
3)任务调度现场数据的保存。

如果任务数目很大时,可以通过增大线程池的大小得到更好的性能。默认情况下,Quartz采用org.quartz.simpl.RAMJobStore保存任务的现场数据,
顾名思义,信息保存在RAM内存中,我们可以通过以下设置将任务调度现场数据保存到数据库中

Spring配置

<?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"
	     xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.0.xsd
           http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.0.xsd
           http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.0.xsd" default-autowire="byName">

	<bean id="scheduleService" class="com.erayt.frame.batch.ScheduleService">
		<property name="scheduler" ref="scheduler" />
		<property name="tgrGroup" value="${GROUP_TGR}" />
		<property name="tgrBatchtoday" value="${BATCHTODAYTGR_NAME}" />
	</bean>

    <bean id="scheduler"
		class="org.springframework.scheduling.quartz.SchedulerFactoryBean">
		<!-- 指定数据源 -->
		<property name="dataSource">
			<ref bean="dataSource" />
		</property>
		<property name="schedulerName" value="XREPORT-PFUND-SCHEDULER" />
		<property name="configLocation"
			value="classpath:quartz.properties" />
		<!--是org.springframework.scheduling.quartz.SchedulerFactoryBean这个类中把spring上下 文以key/value的方式存放在了quartz的上下文中了,
		可以用applicationContextSchedulerContextKey所定义的key得到对应的spring上下文 -->
		<property name="applicationContextSchedulerContextKey"
			value="applicationContextKey" />
		<property name="autoStartup" value="true" />
		<property name="startupDelay" value="10" />
		<property name="overwriteExistingJobs" value="true" />
		<property name="triggers">
			<list>
			    <ref local="batchTrigger"/>
			</list>
		</property>
		<property name="jobDetails">
			<list>
				<ref local="pfundzJob" />
			</list>
		</property>
		<property name="schedulerContextAsMap">
		    <map>
                <entry key="tgrGroup" value="${GROUP_TGR}"/>
            </map>
		</property>
	</bean>
	
	<bean id="batchTrigger" class="org.quartz.CronTrigger">
		<property name="name" value="${BATCHTODAYTGR_NAME}" />
		<property name="group" value="基金任务组" />
		<property name="jobGroup" value="${GROUP_JOB}" />
    		<property name="jobName" value="pfundzJob" /><!-- 需要找到与之对应的配置 -->
		<property name="cronExpression" value="${BATCHTODAY_TIME}" />
	</bean>		
	
    <bean id="pfundzJob"
    	class="org.springframework.scheduling.quartz.JobDetailBean">
    	<property name="jobClass" value="com.erayt.pfund.batch.PfundzJob" />
    	<property name="group" value="${GROUP_JOB}" />
    	<!-- 标识job是持久的,删除触发器的时候不被删除 -->
    	<property name="durability" value="true" />
    	<!--requestsRecovery属性为true,则当Quartz服务被中止后,再次启动任务时会尝试恢复执行之前未完成的所有任务-->
    	<property name="requestsRecovery" value="false" />
    </bean>
	
	<bean id="pfundzOverNightBatch" class="com.erayt.pfund.batch.PfundzOverNightBatch">
		<property name="batchList">
			<list>
				<!-- <ref bean="exportDBHandler"/> -->
				<ref bean="scheduleJobHandler"/>
				<ref bean="changeSystemDate"/>
				<ref bean="synchronizeCache"/>
			</list>
		</property>
	</bean>		
</beans>


取得Spring的调度bean并执行


public class PfundzJob extends QuartzJobBean {
	private static final Logger LOGGER = LoggerFactory
			.getLogger(PfundzJob.class);
	private static final String APPLICATION_CONTEXT_SCHEDULER_CONTEXTKEY = "applicationContextKey";
	/**
	 * 晚批处理
	 */
	public static final int TYPE_OVERNIGHT = 1;
	
	protected void executeInternal(JobExecutionContext arg0)
			throws JobExecutionException {
		try {
			XmlWebApplicationContext context = (XmlWebApplicationContext) arg0
					.getScheduler().getContext()
					.get(APPLICATION_CONTEXT_SCHEDULER_CONTEXTKEY);
			PfundzOverNightBatch overNightBatch = (PfundzOverNightBatch) context.getBean("pfundzOverNightBatch");
			overNightBatch.exector();
		} catch (SchedulerException e) {
			LOGGER.error("晚间批量任务调度失败!【触发器名称:"+arg0.getTrigger().getName()+"】");
		}
	}
}

猜你喜欢

转载自yangeoo.iteye.com/blog/2217954