Quartz框架详解及Quartz与Sping框架整合

一、什么是quartz作业调度?

Quartz是一个完全由java编写的开源作业调度框架。不要让作业调度这个术语吓着你。尽管Quartz框架整合了许多额外功能,比另一个调度框架Timer强大了许多,但是它使用也不难,下面我废话不多说,直奔主题。


二、quartz的体系结构。


1.quartz中使用了一下几种设计模式。

Builer模式
Factory模式
组件模式
链式写法

2.三个主要的概念

  • 调度器 :Quartz框架的核心是调度器。调度器负责管理Quartz应用运行时环境。调度器不是靠自己做所有的工作,而是依赖框架内一些非常重要的部件。Quartz不仅仅是线程和线程池管理。为确保可伸缩性,Quartz采用了基于多线程的架构。启动时,框架初始化一套worker线程,这套线程被调度器用来执行预定的作业。这就是Quartz怎样能并发运行多个作业的原理。Quartz依赖一套松耦合的线程池管理部件来管理线程环境。
  • 任务:这个很简单,就是我们自己编写的业务逻辑,交给quartz帮我们执行 。
  • 触发器:简单的讲就是调度作业,什么时候开始执行,什么时候结束执行。

3.quartz的体系结构

  • quartz框架至少有三百多个类组成,这里我们重点介绍几个它的核心部分
  • JobDetail:quartz每次都会直接创建一个JobDetail,同时创建一个Job实例,它不直接接受一个Job的实例,但是它接受一个Job的实现类,通过new instance()的反射方式来实例一个Job,在这里Job是一个接口,我们需要自己编写类去实现这个接口。下面我们会讲到这个接口。
  • Trigger : 它由SimpleTrigger和CronTrigger组成,SimpleTrigger实现类似Timer的定时调度任务,CronTrigger可以通过cron表达式实现更复杂的调度逻辑·。
  • Scheduler:调度器,JobDetail和Trigger可以通过Scheduler绑定到一起。

4.quartz重要组成部分

1).Job接口:可以通过实现该就接口来实现我们自己的业务逻辑,该接口只有execute()一个方法,我们可以通过下面的方式来实现Job接口来实现我们自己的业务逻辑

public class HelloJob implements Job{

    public void execute(JobExecutionContext context) throws JobExecutionException {
    //编写我们自己的业务逻辑
    }

2).JobDetail:
每次都会直接创建一个JobDetail,同时创建一个Job实例,它不直接接受一个Job的实例,但是它接受一个Job的实现类,通过new instance()的反射方式来实例一个Job.可以通过下面的方式将一个Job实现类绑定到JobDetail中

JobDetail jobDetail=JobBuilder.newJob(HelloJob.class).
                withIdentity("myJob", "group1")
                .build();

3)JobBuiler:
主要是用来创建jobDeatil实例
4)JobStore:
绑定了Job的各种数据
5)trigger:前文讲到它主要用来执行Job实现类的业务逻辑的,我们可以通过下面的代码来创建一个Trigger实例:(这里我们会看到cron表达式,可以先不用,我们后面会介绍)

CronTrigger trigger = (CronTrigger) TriggerBuilder
                .newTrigger()
                .withIdentity("myTrigger", "group1")    //创建一个标识符
                .startAt(date)//什么时候开始触发
                //每秒钟触发一次任务
                .withSchedule(CronScheduleBuilder.cronSchedule("* * * * * ? *"))

                .build();

6)Scheduler:创建Scheduler有两种方式
通过StdSchedulerFactory来创建

SchedulerFactory sfact=new StdSchedulerFactory();
Scheduler scheduler=sfact.getScheduler();

通过DirectSchedulerFactory来创建

DiredtSchedulerFactory factory=DirectSchedulerFactory.getInstance();
Scheduler scheduler=factory.getScheduler();

Scheduler 配置参数一般存储在quartz.properties中,我们可以修改参数来配置相应的参数。通过调用getScheduler()方法就能创建和初始化调度对象。

Scheduler的主要函数介绍:

Date schedulerJob(JobDetail,Trigger trigger);返回最近触发的一次时间
void standby()暂时挂起
void shutdown()完全关闭,不能重新启动了
shutdown(true)表示等待所有正在执行的job执行完毕之后,再关闭scheduler
shutdown(false)即直接关闭scheduler

在这里我们不得不提一下quartz.properties这个资源文件,在org.quartz这个包下,当我们程序启动的时候,它首先会到我们的根目录下查看是否配置了该资源文件,如果没有就会到该包下读取相应信息,当我们咋实现更复杂的逻辑时,需要自己指定参数的时候,可以自己配置参数来实现。下面我们简单看一下这个资源文件:

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.scheduler.instanceName属性用来区分特定的调度器实例,可以按照功能用途来给调度器起名。
org.quartz.scheduler.instanceId属性和前者一样,也允许任何字符串,但这个值必须是在所有调度器实例中是唯一的,尤其是在一个集群当中,作为集群的唯一key,假如你想quartz帮你生成这个值的话,可以设置我Auto

线程池属性:
threadCount设置线程的数量

threadPriority设置线程的优先级

org.quartz.threadPool.class 线程池的实现

作业存储设置:
描述了在调度器实例的声明周期中,job和trigger信息是怎么样存储的

插件配置:
满足特定需求用到的quartz插件的配置

5.监听器
监听器顾名思义,就是对事件进行监听并且加入自己相应的业务逻辑,主要有以下三个监听器分别对Job,Trigger,Scheduler进行监听。

JobListener
TriggerListener
SchedulerListener


三、Cron表达式


在这里,我们着重讲解一下cron表达式,quartz之所以能够实现更加复杂的业务逻辑,主要在依赖于cron表达式。
cron表达式编写的顺序一次是”秒 分 时 日 月 周 年”。
在这里我们可以看这张图片就能了解到cron表达式的基本语法了。
Quartz框架详解及Quartz与Sping框架整合_第1张图片

允许值范围: 0~59 ,不允许为空值,若值不合法,调度器将抛出SchedulerException异常

“*” 代表每隔1秒钟触发

“,” 代表在指定的秒数触发,比如"0,15,45"代表0秒、15秒和45秒时触发任务

“-” 代表在指定的范围内触发,比如"25-45"代表从25秒开始触发到45秒结束触发,每隔1秒触发1次

“/” 代表触发步进(step),"/“前面的值代表初始值(”“等同"0”),后面的值代表偏移量,比如"0/20"或者"/20"代表从0秒钟开始,每隔20秒钟触发1次,即0秒触发1次,20秒触发1次,40秒触发1次;"5/20"代表5秒触发1次,25秒触发1次,45秒触发1次;"10-45/20"代表在[10,45]内步进20秒命中的时间点触发,即10秒触发1次,30秒触发1次

分钟
允许值范围: 0~59 ,不允许为空值,若值不合法,调度器将抛出SchedulerException异常

“*” 代表每隔1分钟触发

“,” 代表在指定的分钟触发,比如"10,20,40"代表10分钟、20分钟和40分钟时触发任务

“-” 代表在指定的范围内触发,比如"5-30"代表从5分钟开始触发到30分钟结束触 发,每隔1分钟触发

“/” 代表触发步进(step),"/“前面的值代表初始值(”“等同"0”),后面的值代表偏移量,比如"0/25"或者"/25"代表从0分钟开始,每隔25分钟触发1次,即0分钟触发1次,第25分钟触发1次,第50分钟触发1次;"5/25"代表5分钟触发1次,30分钟触发1次,55分钟触发1次;"10-45/20"代表在[10,45]内步进20分钟命中的时间点触发,即10分钟触发1次,30分钟触发1次

小时
允许值范围: 0~23 ,不允许为空值,若值不合法,调度器将抛出SchedulerException异常

“*” 代表每隔1小时触发

“,” 代表在指定的时间点触发,比如"10,20,23"代表10点钟、20点钟和23点触发任务

“-” 代表在指定的时间段内触发,比如"20-23"代表从20点开始触发到23点结束触发,每隔1小时触发

“/” 代表触发步进(step),"/“前面的值代表初始值(”“等同"0”),后面的值代表偏移量,比如"0/1"或者"/1"代表从0点开始触发,每隔1小时触发1次;"1/2"代表从1点开始触发,以后每隔2小时触发一次

日期
允许值范围: 1~12 (JAN-DEC),不允许为空值,若值不合法,调度器将抛出SchedulerException异常

“*” 代表每个月都触发

“,” 代表在指定的月份触发,比如"1,6,12"代表1月份、6月份和12月份触发任务

“-” 代表在指定的月份范围内触发,比如"1-6"代表从1月份开始触发到6月份结束触发,每隔1个月触发

“/” 代表触发步进(step),"/“前面的值代表初始值(”“等同"1”),后面的值代表偏移量,比如"1/2"或者"/2"代表从1月份开始触发,每隔2个月触发1次;"6/6"代表从6月份开始触发,以后每隔6个月触发一次;"1-6/12"表达式意味着每年1月份触发

星期
允许值范围: 1~7 (SUN-SAT),1代表星期天(一星期的第一天),以此类推,7代表星期六(一星期的最后一天),不允许为空值,若值不合法,调度器将抛出SchedulerException异常

“*” 代表每星期都触发;

“?” 与{日期}互斥,即意味着若明确指定{日期}触发,则表示{星期}无意义,以免引起冲突和混乱

“,” 代表在指定的星期约定触发,比如"1,3,5"代表星期天、星期二和星期四触发

“-” 代表在指定的星期范围内触发,比如"2-4"代表从星期一开始触发到星期三结束触发,每隔1天触发

“/” 代表触发步进(step),"/“前面的值代表初始值(”“等同"1”),后面的值代表偏移量,比如"1/3"或者"/3"代表从星期天开始触发,每隔3天触发1次;"1-5/2"表达式意味着在[1,5]范围内,每隔2天触发,即星期天、星期二、星期四触发

“L” 如果{星期}占位符如果是"L",即意味着星期的的最后一天触发,即星期六触发,L= 7或者 L = SAT,因此,"5L"意味着一个月的最后一个星期四触发

“#” 用来指定具体的周数,"#“前面代表星期,”#"后面代表本月第几周,比如"2#2"表示本月第二周的星期一,"5#3"表示本月第三周的星期四,因此,“5L"这种形式只不过是”#"的特殊形式而已

年份
允许值范围: 1970~2099 ,允许为空,若值不合法,调度器将抛出SchedulerException异常

"*"代表每年都触发

","代表在指定的年份才触发,比如"2011,2012,2013"代表2011年、2012年和2013年触发任务

"-"代表在指定的年份范围内触发,比如"2011-2020"代表从2011年开始触发到2020年结束触发,每隔1年触发

"/“代表触发步进(step),”/“前面的值代表初始值(”“等同"1970”),后面的值代表偏移量,比如"2011/2"或者"/2"代表从2011年开始触发,每隔2年触发1次

注意:除了{日期}和{星期}可以使用"?"来实现互斥,表达无意义的信息之外,其他占位符都要具有具体的时间含义,且依赖关系为:年->月->日期(星期)->小时->分钟->秒数

特殊字符

”字符被用来指定所有的值。如:"*"在分钟的字段域里表示“每分钟”。

“?”
“?”字符只在日期域和星期域中使用。它被用来指定“非明确的值”。当你需要通过在这两个域中的一个来指定一些东西的时候,它是有用的。看下面的例子你就会明白。 月份中的日期和星期中的日期这两个元素时互斥的一起应该通过设置一个问号来表明不想设置那个字段。

“-”
“-”字符被用来指定一个范围。如:“10-12”在小时域意味着“10点、11点、12点”。

“,”
“,”字符被用来指定另外的值。如:“MON,WED,FRI”在星期域里表示”星期一、星期三、星期五”。

“/”
“/”字符用于指定增量。如:“0/15”在秒域意思是每分钟的0,15,30和45秒。“5/15”在分钟域表示每小时的5,20,35和50。符号“”在“/”前面(如:/10)等价于0在“/”前面(如:0/10)。记住一条本质:表达式的每个数值域都是一个有最大值和最小值的集合,如:秒域和分钟域的集合是0-59,日期域是1-31,月份域是1-12。字符“/”可以帮助你在每个字符域中取相应的数值。如:“7/6”在月份域的时候只有当7月的时候才会触发,并不是表示每个6月。

“L”
L是‘last’的省略写法可以表示day-of-month和day-of-week域,但在两个字段中的意思不同,例如day-of-month域中表示一个月的最后一天。如果在day-of-week域表示‘7’或者‘SAT’,如果在day-of-week域中前面加上数字,它表示一个月的最后几天,例如‘6L’就表示一个月的最后一个星期五。

“W”
字符“W”只允许日期域出现。这个字符用于指定日期的最近工作日。例如:如果你在日期域中写 “15W”,表示:这个月15号最近的工作日。所以,如果15号是周六,则任务会在14号触发。如果15好是周日,则任务会在周一也就是16号触发。如果是在日期域填写“1W”即使1号是周六,那么任务也只会在下周一,也就是3号触发,“W”字符指定的最近工作日是不能够跨月份的。字符“W”只能配合一个单独的数值使用,不能够是一个数字段,如:1-15W是错误的。

“L”和“W”可以在日期域中联合使用,LW表示这个月最后一周的工作日。

“#”
字符“#”只允许在星期域中出现。这个字符用于指定本月的某某天。例如:“6#3”表示本月第三周的星期五(6表示星期五,3表示第三周)。“2#1”表示本月第一周的星期一。“4#5”表示第五周的星期三。

“C”
字符“C”允许在日期域和星期域出现。这个字符依靠一个指定的“日历”。也就是说这个表达式的值依赖于相关的“日历”的计算结果,如果没有“日历”关联,则等价于所有包含的“日历”。如:日期域是“5C”表示关联“日历”中第一天,或者这个月开始的第一天的后5天。星期域是“1C”表示关联“日历”中第一天,或者星期的第一天的后1天,也就是周日的后一天(周一)。

表达式举例
“0 0 12 * * ?” 每天中午12点触发

“0 15 10 ? * *” 每天上午10:15触发

“0 15 10 * * ?” 每天上午10:15触发

“0 15 10 * * ? *” 每天上午10:15触发

“0 15 10 * * ? 2005” 2005年的每天上午10:15触发

“0 * 14 * * ?” 在每天下午2点到下午2:59期间的每1分钟触发

“0 0/5 14 * * ?” 在每天下午2点到下午2:55期间的每5分钟触发

“0 0/5 14,18 * * ?” 在每天下午2点到2:55期间和下午6点到6:55期间的每5分钟触发

“0 0-5 14 * * ?” 在每天下午2点到下午2:05期间的每1分钟触发

“0 10,44 14 ? 3 WED” 每年三月的星期三的下午2:10和2:44触发

“0 15 10 ? * MON-FRI” 周一至周五的上午10:15触发

“0 15 10 15 * ?” 每月15日上午10:15触发

“0 15 10 L * ?” 每月最后一日的上午10:15触发

“0 15 10 ? * 6L” 每月的最后一个星期五上午10:15触发

“0 15 10 ? * 6L 2002-2005” 2002年至2005年的每月的最后一个星期五上午10:15触发

“0 15 10 ? * 6#3” 每月的第三个星期五上午10:15触发


四、quartz框架实战


这里我们简要讲解一个我们这个实例,我们先实现Job接口来编写我们相应的业务逻辑,然后创建JobDetail实例将job实现类绑定,接着创建Trigger,然后创建Scheduler实例将JobDetail实例和Trigger绑定到一起。

不好意思,在这里我们漏掉了一个重要的属性(不好意思 ,hhhhhhhhh)
JobDataMap主要用啦设置和获取一些自定义的参数,比如JobDetail和trigger的name和group属性。它可以通过两种方式来获取和设置参数。

1.第一种方式,通过map的方式获取JobDataMap中的值,JobDataMap获取自定义的一些参数,下面这段代码获取的是我们即将要写的例子中的JobDetail和trigger的一些自定义参数(在实例中我们将省略这些代码,避免复杂性)

JobDataMap jobDataMap=context.getJobDetail().getJobDataMap();
        JobDataMap tDataMap=context.getTrigger().getJobDataMap();
//      JobDataMap jobDataMap2=context.getMergedJobDataMap();       //把两个map合并
        String jobMsg=jobDataMap.getString("msg");
        float jobFloat=jobDataMap.getFloat("JobFloatValue");
        String triMsg=tDataMap.getString("msg");
        double triDouble=tDataMap.getDouble("TriggerDoubleValue");
        System.out.println("job message are:"+jobMsg+" "+jobFloat);
        System.out.println("trigger message are:"+triMsg+" "+triDouble);

2.第二种方式,创建每一个key的属性,并且创建set,get方法,然后在Job实现类中通过set和get方法来获取信息。

private String msg;
    private float JobFloatValue;
    private double TriggerDoubleValue;
        public void setJobFloatValue(float jobFloatValue) {
        JobFloatValue = jobFloatValue;
    }
    public float getJobFloatValue() {
        return JobFloatValue;
    }
    public void setMsg(String msg) {
        this.msg = msg;
    }
    public String getMsg() {
        return msg;
    }
    public void setTriggerDoubleValue(double triggerDoubleValue) {
        TriggerDoubleValue = triggerDoubleValue;
    }
    public double getTriggerDoubleValue() {
        return TriggerDoubleValue;
    }

好了,终于讲完了,下面我们继续我们的实例。
下面是Job实现类

public class HelloJob implements Job{
//JobExecutionContext 提供了调度上下文的各种参数
    public void execute(JobExecutionContext context) throws JobExecutionException {
        //编写具体的逻辑关系
        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        //打印当前时间
        SimpleDateFormat simpleDateFormat =new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        System.out.println("Current time is :"+simpleDateFormat.format(new Date()));
        System.out.println("Hello World");
        }

触发器:

public class HelloSchedule{
    public static void main(String[] args) throws SchedulerException, InterruptedException {
        //创建一个JobDetail实例,与HelloJob class绑定

        JobDetail jobDetail=JobBuilder.newJob(HelloJob.class).
                withIdentity("myJob", "group1")
                .build();
        Date date=new Date();
        //使用CronTrigger每秒钟触发一次任务
        CronTrigger trigger = (CronTrigger) TriggerBuilder
                .newTrigger()
                .withIdentity("myTrigger", "group1")    //创建一个标识符
                .startAt(date)
                //每秒钟触发一次任务
                .withSchedule(CronScheduleBuilder.cronSchedule("* * * * * ? *"))
                .build();
        //创建一个schedule实例
        SchedulerFactory schedulerFactory=new StdSchedulerFactory();
        Scheduler scheduler=schedulerFactory.getScheduler();
        scheduler.start();
        //打印当前时间
        SimpleDateFormat simpleDateFormat =new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        System.out.println("Current time is :"+simpleDateFormat.format(new Date()));
        System.out.println("Current time is :"+scheduler.scheduleJob(jobDetail, trigger))

    }
}

五,整合spring框架


quartz的强大还在于它可以和各种框架进行无缝整合,当然包括最流行的spring框架。下面我们来整合spring。
首先建立maven项目导入相关依赖,在这里我们不讲如何搭建spring框架,读者需要有spring的知识。
pom.xml文件


    
        junit
        junit
        3.8.1
        test
    
    
    
        org.springframework
        spring-webmvc
        4.3.12.RELEASE
    
    
    
        org.springframework
        spring-context
        4.3.12.RELEASE
    
    
    
        org.springframework
        spring-core
        4.3.12.RELEASE
    
    
    
        org.springframework
        spring-beans
        4.3.12.RELEASE
    
    
    
        org.springframework
        spring-web
        4.3.12.RELEASE
    
    
    
        org.springframework
        spring-aop
        4.3.12.RELEASE
    
    
    
        io.rest-assured
        spring-mock-mvc
        3.0.1
    

    
        org.springframework
        spring-context-support
        4.3.12.RELEASE
    

    
        org.springframework
        spring-tx
        4.3.12.RELEASE
    
    
        org.quartz-scheduler
        quartz
        2.3.0
    
  

然后,我们在src/main/java目录下创建几个bean类,分别对应着我们的JobDetail,Trigger,和Scheduler,这里显而易见,我们通过spring容器来实例化bean,然后在spring中的配置文件中来用Scheduler把JobDetail和Trigger来绑定到一起,来实现业务逻辑。
Mybean.java:

@Component("myBean")
public class MyBean {
    public void printMessage(){
        Date date=new Date();
        SimpleDateFormat sdf=new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        System.out.println("It is mybean "+sdf.format(date));
    }
}

FristSchedulerJob.java

@Component("fristSchedulerJob")
public class FristSchedulerJob extends QuartzJobBean{
    private AnotherBean  anotherBean;

    public void setAnotherBean(AnotherBean anotherBean) {
        this.anotherBean = anotherBean;
    }
    @Override
    protected void executeInternal(JobExecutionContext context)
            throws JobExecutionException {
        Date date=new Date();
        SimpleDateFormat sdf=new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        System.out.println("It is my frist job "+sdf.format(date));
        this.anotherBean.printAotherMessage();
    }
}

AnotherBean.java

@Component("anotherBean")
public class AnotherBean {
    public void printAotherMessage(){
        System.out.println("AnotherBean");
    }
}

下面我们看spring的配置文件,在这里需要说明的是我们通过controller对web请求进行管理控制,quartz在后台进行调度业务。



        
        
        
        
        
        
        
        

        
            
            
        

        
            
            
                
                    
                
            
            
        
        
        
            
            
            
        
        
        
            
            
        

       
       
            
                
                    
                    
                
            
                
                
                    
                    
                
            
       
        
        


在这里我们省略controller层和web.xml文件的代码,相信大家都能够自己完成。
quartz框架还是挺强大的,有什么问题可以留言哦

你可能感兴趣的:(JAVA,Quartz定时器)