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()+"】"); } } }