Spring整合Quartz框架定时任务

        最近项目中常常用到队列和定时任务及线程的整合应用,涉及的场景是当多人访问系统时须要回调客户系统处理结果时,如何减小服务器压力并能处理业务需求,这里用到了队列减小服务器压力加入定时任务发送机制,使用的是Spring框架整合 Quartz框架实现的定时任务,所以学习了一下Quartz的来龙去脉 在此记录学习一下。html

1、引入

      你曾经须要应用执行一个任务吗?这个任务天天或每周星期二晚上11:30,或许仅仅每月的最后一天执行。一个自动执行而无须干预的任务在执行过程当中若是发生一个严重错误,应用可以知到其执行失败并尝试从新执行吗?你和你的团队是用Java编程吗?若是这些问题中任何一个你回答是,那么你应该使用Quartz调度器。java

2、为何研发团队会选择quartz

java编写的开源做业调度框架设计,用于J2SE和J2EE应用方便集成。web

资历够老,创立于1998年,比struts1还早,并且一直在更新(24 Sept 2013: Quartz 2.2.1 Released),文档齐全。spring

设计清晰简单:核心概念scheduler,trigger,job,jobDetail,listener,calendar 编程

支持集群:org.quartz.jobStore.isClustered  最重要的一点缘由是quartz是支持集群的。否则JDK自带Timer就能够实现相同的功能。服务器

支持任务恢复:requestsRecovery 多线程

普及面很广,JAVA开发人员比较熟悉。架构

Apache2.0开源协议,容许代码修改,再商业发布。并发

3、如何使定时任务的开发方便,易于管理。


阿里开源项目easySchedule在quartz集群的基础上搭建了一个简单的管理平台。解决了可视化、易配置、统一监控告警功能。
实现调度与执行的分离,使任务不须要再去关注定时,只须要实现任务接口便可。
调度经过HTTP来调用执行任务。
app

easySchedule系统特色:

一、 Server和Client分别支持集群和分布式部署

二、 任务的执行与调度分离

三、 可视化管理全部任务

四、 任务状态持久化于DB

五、 完善的日志跟踪和告警策略

六、 任务支持异步调度

七、 灵活支持各类自定义任务,扩展方便

 这里后期会学习

4、quartz核心概念





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

二、JobDetail:Quartz在每次执行Job时,都从新建立一个Job实例,因此它不直接接受一个Job的实例,相反它接收一个Job实现类,以便运行时经过newInstance()的反射机制实例化Job。所以须要经过一个类来描述Job的实现类及其它相关的静态信息,如Job名字、描述、关联监听器等信息,JobDetail承担了这一角色。

三、Trigger:是一个类,描述触发Job执行的时间触发规则。主要有SimpleTrigger和CronTrigger这两个子类。当仅需触发一次或者以固定时间间隔周期执行,SimpleTrigger是最适合的选择;而CronTrigger则能够经过Cron表达式定义出各类复杂时间规则的调度方案:如每早晨9:00执行,周1、周3、周五下午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进行定点排除。

五、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使用一个线程池做为任务运行的基础设施,任务经过共享线程池中的线程提升运行效率。


Quartz内部架构

  在规模方面,Quartz跟大多数开源框架相似。大约有300个Java类和接口,并被组织到12个包中。这能够和Apache Struts把大约325个类和接口以及组织到11个包中相比。尽管规模几乎不会用来做为衡量框架质量的一个特性,但这里的关键是quarts内含不少功能,这些功能和特性集是否成为、或者应该成为评判一个开源或非开源框架质量的因素。

Quartz调度器

Quartz框架的核心是调度器。调度器负责管理Quartz应用运行时环境。调度器不是靠本身作全部的工做,而是依赖框架内一些很是重要的部件。Quartz不只仅是线程和线程管理。为确保可伸缩性,Quartz采用了基于多线程的架构。

  启动时,框架初始化一套worker线程,这套线程被调度器用来执行预约的做业。这就是Quartz怎样能并发运行多个做业的原理。Quartz依赖一套松耦合的线程池管理部件来管理线程环境。本文中,咱们会屡次提到线程池管理,但Quartz里面的每一个对象是可配置的或者是可定制的。因此,例如,若是你想要插进本身线程池管理设施,我猜你必定能!

做业和触发器

Quartz设计者作了一个设计选择来从调度分离开做业。Quartz中的触发器用来告诉调度程序做业何时触发。框架提供了一把触发器类型,但两个最经常使用的是SimpleTrigger和CronTrigger。SimpleTrigger为须要简单打火调度而设计。

  典型地,若是你须要在给定的时间和重复次数或者两次打火之间等待的秒数打火一个做业,那么SimpleTrigger适合你。另外一方面,若是你有许多复杂的做业调度,那么或许须要CronTrigger。


Cron表达式

CronTrigger是基于Calendar-like调度的。当你须要在除星期六和星期天外的天天上午10点半执行做业时,那么应该使用CronTrigger。正如它的名字所暗示的那样,CronTrigger是基于Unix克隆表达式的。

  做为一个例子,下面的Quartz克隆表达式将在星期一到星期五的天天上午10点15分执行一个做业。

0 15 10 ? * MON-FRI

下面的表达式

0 15 10 ? * 6L 2002-2005

  将在2002年到2005年的每月的最后一个星期五上午10点15分执行做业。你不可能用SimpleTrigger来作这些事情。你能够用二者之中的任何一个,但哪一个跟合适则取决于你的调度须要。

 

Quartz有两大触发器,除了上面使用的SimpleTrigger外,就是CronTrigger。CronTrigger可以提供复杂的触发器表达式的支持。CronTrigger是基于Unix Cron守护进程,它是一个调度程序,支持简单而强大的触发器语法。
 
使用CronTrigger主要的是要掌握Cron表达式。Cron表达式包含6个必要组件和一个可选组件,以下表所示。


特殊字符的含义


Cron表达式举例:
 
"30 * * * * ?" 每半分钟触发任务
"30 10 * * * ?" 每小时的10分30秒触发任务
"30 10 1 * * ?" 天天1点10分30秒触发任务
"30 10 1 20 * ?" 每个月20号1点10分30秒触发任务
"30 10 1 20 10 ? *" 每一年10月20号1点10分30秒触发任务
"30 10 1 20 10 ? 2011" 2011年10月20号1点10分30秒触发任务
"30 10 1 ? 10 * 2011" 2011年10月天天1点10分30秒触发任务
"30 10 1 ? 10 SUN 2011" 2011年10月每周日1点10分30秒触发任务
"15,30,45 * * * * ?" 每15秒,30秒,45秒时触发任务
"15-45 * * * * ?" 15到45秒内,每秒都触发任务
"15/5 * * * * ?" 每分钟的每15秒开始触发,每隔5秒触发一次
"15-30/5 * * * * ?" 每分钟的15秒到30秒之间开始触发,每隔5秒触发一次
"0 0/3 * * * ?" 每小时的第0分0秒开始,每三分钟触发一次
"0 15 10 ? * MON-FRI" 星期一到星期五的10点15分0秒触发任务
"0 15 10 L * ?" 每月最后一天的10点15分0秒触发任务
"0 15 10 LW * ?" 每月最后一个工做日的10点15分0秒触发任务
"0 15 10 ? * 5L" 每月最后一个星期四的10点15分0秒触发任务
"0 15 10 ? * 5#3" 每月第三周的星期四的10点15分0秒触发任务


错过触发(misfire)

trigger还有一个重要的属性misfire;若是scheduler关闭了,或者Quartz线程池中没有可用的线程来执行job,此时持久性的trigger就会错过(miss)其触发时间,即错过触发(misfire)。不一样类型的trigger,有不一样的misfire机制。它们默认都使用“智能机制(smart policy)”,即根据trigger的类型和配置动态调整行为。当scheduler启动的时候,查询全部错过触发(misfire)的持久性trigger。而后根据它们各自的misfire机制更新trigger的信息。当你在项目中使用Quartz时,你应该对各类类型的trigger的misfire机制都比较熟悉,这些misfire机制在JavaDoc中有说明。关于misfire机制的细节,会在讲到具体的trigger时做介绍。


 5、Spring整合Quartz

     定时任务两种方式,Spring很好的封装使用Quartz的细节,第一种方式是利用SPring封装的Quartz类进行特定方法的实现,第二种是经过透明的使用Quartz达到定时任务开发的目的,整体说第二种对开发人员更方便!

   配置Spring的任务调度抽象层简化了任务调度,在Quartz的基础上提供了更好的调度对象。Spring使用Quartz框架来完成任务调度,建立Quartz的做业Bean(JobDetail),有一下两种方法:

   1:利用JobDetailBean包装QuartzJobBean子类(即Job类)的实例。

   2:利用MethodInvokingJobDetailFactoryBean工厂Bean包装普通的Java对象(即Job类)。

   说明:

      1:采用第一种方法 建立job类,必定要继承QuartzJobBean ,实现 executeInternal(JobExecutionContext
jobexecutioncontext)方法,此方法就是被调度任务的执行体,而后将此Job类的实例直接配置到JobDetailBean中便可。这种方法和在普通的Quartz编程中是同样的。

      2:采用第二种方法 建立Job类,无须继承父类,直接配置MethodInvokingJobDetailFactoryBean便可。但须要指定一下两个属性:

        targetObject:指定包含任务执行体的Bean实例。

        targetMethod:指定将指定Bean实例的该方法包装成任务的执行体。

这里咱们介绍第二种

package com.kay.quartz;
public class QuartzJob
{

    public void work()
    {
    System.out.println("Quartz的任务调度!!!");
    }
}


Spring的配置文件:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd">

<beans>    
        <!-- 要调用的工做类 -->
        <bean id="quartzJob" class="com.kay.quartz.QuartzJob"></bean>
        <!-- 定义调用对象和调用对象的方法 -->
        <bean id="jobtask" class="org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean">
            <!-- 调用的类 -->
            <property name="targetObject">
                <ref bean="quartzJob"/>
            </property>
            <!-- 调用类中的方法 -->
            <property name="targetMethod">
                <value>work</value>
            </property>
        </bean>
        <!-- 定义触发时间 -->
        <bean id="doTime" class="org.springframework.scheduling.quartz.CronTriggerBean">
            <property name="jobDetail">
                <ref bean="jobtask"/>
            </property>
            <!-- cron表达式 -->
            <property name="cronExpression">
                <value>10,15,20,25,30,35,40,45,50,55 * * * * ?</value>
            </property>
        </bean>
        <!-- 总管理类 若是将lazy-init='false'那么容器启动就会执行调度程序  -->
        <bean id="startQuertz" lazy-init="false" autowire="no" class="org.springframework.scheduling.quartz.SchedulerFactoryBean">
            <property name="triggers">
                <list>
                    <ref bean="doTime"/>
                </list>
            </property>
        </bean>
    
</beans>


在须要添加Spring 配置文件的Spring.xml/application.xml添加

xmlns:task="http://www.springframework.org/schema/task"  
    xsi:schemaLocation="  
    http://www.springframework.org/schema/task          
    http://www.springframework.org/schema/task/spring-task-3.0.xsd


配置自动调度的包和定时开关

<!-- 自动调度须要扫描的包 -->  
        <context:component-scan base-package="*" />   
        <task:executor id="executor" pool-size="5" />      
        <task:scheduler id="scheduler" pool-size="10" />    
        <task:annotation-driven executor="executor" scheduler="scheduler" />

配置调度和注解调度

<!-- 配置调度 须要在类名前添加 @Service -->  
    <!--    
        <task:scheduled-tasks>  
            <task:scheduled ref="demoTask" method="myTestWork" cron="0/10 * * * * ?"/>  
        </task:scheduled-tasks>  
    -->  
    <!-- 不经过配置调度,须要在类名前 @Component/@Service,在方法名 前添加@Scheduled(cron="0/5 * * * * ? ")-->


在web.xml配置里添加启动时要扫描的配置文件和监听

<context-param>  
       <param-name>contextConfigLocation</param-name>  
       <param-value>/WEB-INF/applicationContext*.xml</param-value>  
     </context-param>  
     <listener>  
       <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>  
     </listener>