Quartz---任务调度入门学习

一、什么是Quartz?

Quartz是一个完全由java编写的开源作业调度框架,它可以与J2EE与J2SE应用程序相结合也可以单独使用。Quartz可以用来创建简单或为运行十个,百个,甚至是好几万个Jobs这样复杂的程序。

二、Quartz的体系结构

在这里插入图片描述

三、Quartz的基本入门案例

第一步、导入相应的jar包

<!--quartz核心包-->
    <!-- https://mvnrepository.com/artifact/org.quartz-scheduler/quartz -->
    <dependency>
      <groupId>org.quartz-scheduler</groupId>
      <artifactId>quartz</artifactId>
      <version>2.3.2</version>
    </dependency>

    <!-- 工具包 -->
    <dependency>
      <groupId>org.quartz-scheduler</groupId>
      <artifactId>quartz-jobs</artifactId>
      <version>2.3.2</version>
    </dependency>

    <!--日志包整合 -->
    <dependency>
      <groupId>org.slf4j</groupId>
      <artifactId>slf4j-log4j12</artifactId>
      <version>2.0.0-alpha1</version>
    </dependency>

    <!-- log4j -->
    <dependency>
      <groupId>log4j</groupId>
      <artifactId>log4j</artifactId>
      <version>1.2.17</version>
    </dependency>

第二步、新建一个任务类(Task/Job),实现Job接口,重写execute方法

public class helloJob implements Job {
    @Override
    public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {
        Date date = new Date();
        SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        String test = dateFormat.format(date);
        //工作内容
        System.out.println("数据库备份成功,当前时间为:"+test);
    }
}

第三步、编写调度类scheduler,编写任务实例和触发器,通过scheduler将触发器与任务绑定。

public class helloScheduler {

    public static void main(String[] args) throws SchedulerException {
        //调度器(Scheduler),通过工厂模式从工厂中获取调度实例
        Scheduler defaultScheduler = StdSchedulerFactory.getDefaultScheduler();

        //任务实例(JobDetail)
        JobDetail jobDetail = JobBuilder//加载任务类,和具体任务绑定,该任务必须实现Job接口
                .newJob(helloJob.class)
                .withIdentity("job1", "group1")//参数一是任务名称,唯一实例;参数二是任务组的名称,将任务分组
                .build();

        //触发器
        Trigger trigger = TriggerBuilder.newTrigger()
                .withIdentity("trigger1", "group1")//参数一是触发器名称,参数二是触发器组,将触发器分组
                .startNow()//马上启动触发器
                .withSchedule(SimpleScheduleBuilder.simpleSchedule().withIntervalInSeconds(1).repeatForever()).build();//每1秒触发一次

        //scheduler关联任务实例和触发器
        defaultScheduler.scheduleJob(jobDetail, trigger);
        //启动
        defaultScheduler.start();
    }
}   

四、Job和JobDetail介绍

这里是引用
创建新的Job实例演示:
我们只需要在构造函数中输出一个字符串就好了,如下图所示:
在这里插入图片描述
结果:
在这里插入图片描述
这就说明在每次调度执行Job的时候,都会实例化一个Job类。
此外,JobDetail还有四个比较重要的属性:

  • name:
  • group
  • jobClass
  • jobDataMap
    具体的作用看下图
    在这里插入图片描述
    输出结果:
    在这里插入图片描述

五、JobExecutionContext介绍

这里是引用
Job能通过JobExecutionContext上下文对象访问到Quartz运行时候的环境以及Job本身的详细数据。

public class helloJob implements Job {

    @Override
    public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {
        Date date = new Date();
        SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        String test = dateFormat.format(date);
        //获取JobDetail的内容
        JobKey jobKey = jobExecutionContext.getJobDetail().getKey();
        System.out.println("工作任务名称:"+jobKey.getName()+"工作任务组:"+jobKey.getGroup());
        System.out.println("任务类的名称(带路径):"+ jobExecutionContext.getJobDetail().getJobClass().getName());
        System.out.println("任务类的名称:"+ jobExecutionContext.getJobDetail().getJobClass().getSimpleName());
        //工作内容
        System.out.println("数据库备份成功,当前时间为:"+test);
    }
}

运行结果:

工作任务名称:job1工作任务组:group1
任务类的名称(带路径):com.gdpu.quartz.job.helloJob
任务类的名称:helloJob
数据库备份成功,当前时间为:2020-11-30 10:45:45

六、JobDataMap介绍

在这里插入图片描述
通过JobDataMap,我们可以获取JobDetail,Trigger在scheduler中传递的参数,返回给任务类Job接收,具体的操作如下:

scheduler类:

public class helloScheduler {

    public static void main(String[] args) throws SchedulerException {
        //调度器(Scheduler),通过工厂模式从工厂中获取调度实例
        Scheduler defaultScheduler = StdSchedulerFactory.getDefaultScheduler();

        //任务实例(JobDetail)
        JobDetail jobDetail = JobBuilder//加载任务类,和具体任务绑定,该任务必须实现Job接口
                .newJob(helloJob.class)
                .withIdentity("job1", "group1")//参数一是任务名称,唯一实例;参数二是任务组的名称,将任务分组
                .usingJobData("message","任务实例传递参数")//传递参数给Job任务类
                .build();

        Trigger trigger = TriggerBuilder.newTrigger()
                .withIdentity("trigger1", "group1")//参数一是触发器名称,参数二是触发器组,将触发器分组
                .startNow()//马上启动触发器
                .usingJobData("message","触发器传递参数")//传递参数给Job任务类
                .withSchedule(SimpleScheduleBuilder.simpleSchedule().withIntervalInSeconds(1).repeatForever()).build();//每1秒触发一次

        //scheduler关联任务实例和触发器
        defaultScheduler.scheduleJob(jobDetail, trigger);
        //启动
        defaultScheduler.start();
    }
}

Job任务类:

public class helloJob implements Job {

    @Override
    public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {
        Date date = new Date();
        SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        String test = dateFormat.format(date);
        //获取Scheduler中JobDetail,trigger传递的参数
        //获取JobDetail中传递的参数
        JobDataMap jobDataMap = jobExecutionContext.getJobDetail().getJobDataMap();
        String message = jobDataMap.getString("message");
        System.out.println("JobDetail中的参数:"+message);
        //获取Trigger中传递的参数
        JobDataMap jobDataMap1 = jobExecutionContext.getTrigger().getJobDataMap();
        String message1 = jobDataMap1.getString("message");
        System.out.println("Trigger中的参数:"+message1);

        //工作内容
        System.out.println("数据库备份成功,当前时间为:"+test);
    }
}

同理,想要获取Trigger的内容的操作,也是同获取JobDetail的操作是类似的,包括获取Trigger的任务名组名等,这里就不展示了。通过:TriggerKey key= jobExecutionContext.getTrigger().getKey();即可。
获取其他内容,如任务的当前执行时间以及下次执行时间:
在这里插入图片描述
最后总结一点,如果我们觉得先通过Context上下文对象获取jobData,然后再获取JobMap这些方式很麻烦,如下展示的是从JobDetail获取dataMap,Trigger中获取的形式是一样的:
在这里插入图片描述
我们也可以在Job类定义一个相关类型的成员变量,例如我在Scheduler中JobDetail通过dataMap穿的value是String类型的,我在Job类就可以定义一个String类型的成员变量去接收,重写set方法即可。
在这里插入图片描述在这里插入图片描述
在这里插入图片描述
但是这个方法如果遇到同名的key,则Trigger中的value会覆盖JobDetail中的value。在这里插入图片描述

七、有状态的Job和无状态的Job

什么是有状态的Job?
有状态的Job得意思其实就是每次调用Job的时候,只会有JobDataMap这一个实例Map,而无状态的Job在每次调用Job的时候都会新建一个新的JobDataMap,就会导致一些状态信息无法储存共享,其实这就有点像我们Bean的单例和多例模式,演示如下:

//任务实例(JobDetail)
        JobDetail jobDetail = JobBuilder//加载任务类,和具体任务绑定,该任务必须实现Job接口
                .newJob(helloJob.class)
                .withIdentity("job1", "group1")//参数一是任务名称,唯一实例;参数二是任务组的名称,将任务分组
                .usingJobData("count",0)//把数值传给Job接收
                .build();
//Job任务类(Job)
//演示无状态的Job
        ++count;//通过set方法直接获取到JobDetail传递过来的count数值,做加法运算后输出
        System.out.println("当前的count数值为:"+count);
        jobDataMap.put("count",count);//将新增的数值增加后返回给JobDetail任务实例
//展示结果
当前的count数值为:1
数据库备份成功,当前时间为:2020-11-30 11:34:01
当前的count数值为:1
数据库备份成功,当前时间为:2020-11-30 11:34:02
当前的count数值为:1
数据库备份成功,当前时间为:2020-11-30 11:34:03

我们可以很明显的看到,结果一直都是1,因为这个时候我们的Job是无状态的,每一次DataMap都是一个新的实例。我们可以通过在Job类上加上这样一个注解即可保证DataMap一直都是同一个实例。
@PersistJobDataAfterExecution
加上这个注解之后,我们的Job就变成有状态的了。
在这里插入图片描述

八、Trigger介绍

Trigger的相关实现类:这里是引用

1、SimpleTrigger

通过simpleTrigger可以简单的设置一些设置,如开始时间,结束时间,执行次数,执行间隔等,从而完成在规定的一个时间段执行一些具体的任务,如:

Job类:

public class testSimpleTrigger implements Job {



    @Override
    public void execute(JobExecutionContext context) throws JobExecutionException {
        Date date = new Date();
        SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        String test = dateFormat.format(date);
        //获取trigger
        Trigger trigger = context.getTrigger();
        //获取trigger实例,一个JobKey对应一个trigger实例
        TriggerKey key = trigger.getKey();
        System.out.println("任务开始时间为:"+dateFormat.format(trigger.getStartTime()));
        System.out.println("任务结束时间为:"+dateFormat.format(trigger.getEndTime()));
        System.out.println("Trigger名称为:"+key.getName()+"------------"+"Trigger组名称为:"+key.getGroup());
        //工作内容
        System.out.println("数据库备份成功,当前时间为:"+test);
    }
}

scheduler类:

public class simpleTriggerScheduler {

    public static void main(String[] args) throws SchedulerException {
        //调度器(Scheduler),通过工厂模式从工厂中获取调度实例
        Scheduler defaultScheduler = StdSchedulerFactory.getDefaultScheduler();
        //设置任务开始时间
        Date startDate = new Date();
        startDate.setTime(startDate.getTime()+3000);//延迟三秒后开始任务
        //设置任务结束时间
        Date endDate = new Date();
        endDate.setTime(endDate.getTime()+10000);//任务的结束时间延迟10秒

        //任务实例(JobDetail)
        JobDetail jobDetail = JobBuilder
                .newJob(testSimpleTrigger.class)
                .withIdentity("job1", "group1")
                .build();

        Trigger trigger = TriggerBuilder.newTrigger()
                .withIdentity("trigger1", "group1")
                .startNow()//马上启动触发器
                .startAt(startDate)
                .endAt(endDate)
      		    //withRepeatCount表示执行次数,值得注意的是,当我们规定了指定的执行时间区间
      		    //如果在时间区间结束了,执行任务次数没达到规定的五次,按照执行时间区间来算而不是按照执行次数来算      
      		    //表示每隔五秒执行一次,需要执行五次,但由于时间规定了只有十秒,因此最终只执行了两次   
                .withSchedule(SimpleScheduleBuilder.simpleSchedule().withIntervalInSeconds(5).withRepeatCount(5)).build();

        //scheduler关联任务实例和触发器
        defaultScheduler.scheduleJob(jobDetail, trigger);
        //启动
        defaultScheduler.start();
    }

}

输出如下:

任务开始时间为:2020-11-30 14:09:10
任务结束时间为:2020-11-30 14:09:17
Trigger名称为:trigger1------------Trigger组名称为:group1
数据库备份成功,当前时间为:2020-11-30 14:09:10
任务开始时间为:2020-11-30 14:09:10
任务结束时间为:2020-11-30 14:09:17
Trigger名称为:trigger1------------Trigger组名称为:group1
数据库备份成功,当前时间为:2020-11-30 14:09:15

2、CronTrigger

如果你要通过像日历那样按日程来触发任务,可以基于CronTrigger来进行任务调度。如每个工作日的上午9点,或者每隔一三五的上午9点-12点,诸如此类。

Cron表达式:
注意两点的是:
一、周中SAT表示的是7,SUN表示的是一。而非我们传统的周一到周日对应的1-7。
二、周和日字段不会同时存在,例如我们制定了日这个字段为每个月的15号,我们不能保证每个月的15号都是周一或者周二。同理,如果我们指定了每周一或者周二,日这个字段也不需要了,我们可以通过?来表示我不需要这个字段。在这里插入图片描述
Cron特殊字符相关解释:
在这里插入图片描述
具体示例:
在这里插入图片描述
写在最后,当然我们也可以不用自己手写,我们可以直接通过网上的一些在线工具直接生成Cron表达式:Cron表达式在线生成

九、配置、资源SchedulerFactory(了解)

public class testScheduler implements Job {

    @Override
    public void execute(JobExecutionContext context) throws JobExecutionException {
        Date date = new Date();
        SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        String test = dateFormat.format(date);
        //工作内容
        System.out.println("数据库备份成功,当前时间为:"+test);
    }
}
public class testJobScheduler {

    public static void main(String[] args) throws SchedulerException, InterruptedException {
        //通过子类的方式获取对象
        StdSchedulerFactory factory = new StdSchedulerFactory();
        Scheduler scheduler = factory.getScheduler();

        JobDetail jobDetail = JobBuilder
                .newJob(testScheduler.class)
                .withIdentity("job1", "group1")
                .build();

        Trigger trigger = TriggerBuilder.newTrigger()
                .withIdentity("trigger1", "group1")
                .startNow()//马上启动触发器
                .withSchedule(CronScheduleBuilder.cronSchedule("* * * * * ?"))
                .build();


        //scheduler关联任务实例和触发器
        Date date = scheduler.scheduleJob(jobDetail, trigger);
        SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        System.out.println("scheduler开始的时间是"+dateFormat.format(date));
        //启动
        scheduler.start();
        //Scheduler执行两秒后挂起
        Thread.sleep(2000L);
        //挂起,暂停任务
        scheduler.standby();
        //中止,杀死任务后不允许重启,有一个布尔类型的参数,true会等待所有任务执行完毕,而false则会直接关闭scheduler
        scheduler.shutdown();
        //scheduler执行5秒后自动开启
        Thread.sleep(5000L);
        scheduler.start();
    }

}

十、Quartz.Properties

默认的Quartz.Properties,如果我们不想用这个文件,我们也可以在资源类目录下新建一个同名文件进行覆盖。

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

十一、Quartz监听器(重要)

在任务调度中,监听你想关注的任务。主要的监听器有三种,分别为:
TriggerListener
JobListener
SchedulerListener
在这之前得先了解全局监听器和非全局监听器的区别:
全局监听器:能够接收所有Job/Listener的通知
非全局监听器:只能接收在其注册上的Job或者Trigger

1、JobListener

JobListener的四种方法:这里是引用
JobListener的使用方法:

第一步、编写监听类,实现JobListener接口,重写四种监听方法:

//实现JobListener接口
public class MyJobListener implements JobListener {
    @Override
    public String getName() {
        String name = this.getClass().getName();
        System.out.println("监听器的名称是:"+name);
        return name;
    }

    //前置方法
    @Override
    public void jobToBeExecuted(JobExecutionContext context) {
        String name = context.getJobDetail().getKey().getName();
        System.out.println("Job的名称是"+name+ "Scheduler在JobDetails即将执行时调用的方法");
    }

    @Override
    public void jobExecutionVetoed(JobExecutionContext context) {
        String name = context.getJobDetail().getKey().getName();
        System.out.println("job的名称是:"+name+"Scheduler在JobDetails即将执行时,但又被TriggerListener否决时调用的方法");
    }

    //后置方法
    @Override
    public void jobWasExecuted(JobExecutionContext context, JobExecutionException jobException) {
        String name = context.getJobDetail().getKey().getName();
        System.out.println("job的名称是:"+name+"Scheduler在JobDetails执行之后调用的方法");
    }
}

第二步、注册监听器即可

public class JobListenerScheduler {

    public static void main(String[] args) throws SchedulerException {
        //调度器(Scheduler),通过工厂模式从工厂中获取调度实例
        Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();

        //任务实例(JobDetail)
        JobDetail jobDetail = JobBuilder
                .newJob(HelloJobListener.class)
                .withIdentity("job1", "group1")
                .build();

        Trigger trigger = TriggerBuilder.newTrigger()
                .withIdentity("trigger1", "group1")
                .startNow()//马上启动触发器
                .withSchedule(SimpleScheduleBuilder.simpleSchedule().withIntervalInSeconds(5).withRepeatCount(3)).build();

        //scheduler关联任务实例和触发器
        scheduler.scheduleJob(jobDetail, trigger);

        //注册局部的JobListener,表示指定的任务job
        //scheduler.getListenerManager().addJobListener(new MyJobListener(), KeyMatcher.keyEquals(JobKey.jobKey("job1","group1")));


        //注册全局的JobListener
        //scheduler.getListenerManager().addJobListener(new MyJobListener(),EverythingMatcher.allJobs());

        //启动
        scheduler.start();
    }

}

2、TriggerListener

TriggerListener的五种方法:
在这里插入图片描述

public class MyTriggerListener implements TriggerListener {

    private String name;

    public MyTriggerListener(String name) {
        this.name = name;
    }

    @Override
    public String getName() {
        System.out.println("监听器的名称是:"+name);
        return name;
    }

    @Override
    public void triggerFired(Trigger trigger, JobExecutionContext context) {
        String name = trigger.getKey().getName();
        System.out.println(name+"触发器被触发");
    }

    @Override
    public boolean vetoJobExecution(Trigger trigger, JobExecutionContext context) {
        String name = trigger.getKey().getName();
        System.out.println(name+"触发器没被触发");
        return true;//不会执行job方法,也就是说我的Job类中的数据库备份语句不会被输出
    }

    @Override
    public void triggerMisfired(Trigger trigger) {
        String name = trigger.getKey().getName();
        System.out.println(name+"错过触发");
    }

    @Override
    public void triggerComplete(Trigger trigger, JobExecutionContext context, Trigger.CompletedExecutionInstruction triggerInstructionCode) {
        String name = trigger.getKey().getName();
        System.out.println(name+"完成之后触发");
    }
}
public class TriggerListenerScheduler {

    public static void main(String[] args) throws SchedulerException {
        //调度器(Scheduler),通过工厂模式从工厂中获取调度实例
        Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();

        //任务实例(JobDetail)
        JobDetail jobDetail = JobBuilder
                .newJob(HelloJobListener.class)
                .withIdentity("job1", "group1")
                .build();

        Trigger trigger = TriggerBuilder.newTrigger()
                .withIdentity("trigger1", "group1")
                .startNow()//马上启动触发器
                .withSchedule(SimpleScheduleBuilder.simpleSchedule().withIntervalInSeconds(5).withRepeatCount(3)).build();

        //scheduler关联任务实例和触发器
        scheduler.scheduleJob(jobDetail, trigger);

        //注册局部的JobListener,表示指定的任务job
        //scheduler.getListenerManager().addTriggerListener(new MyTriggerListener("simple"), KeyMatcher.keyEquals(TriggerKey.triggerKey("trigger1","group1")));


        //注册全局的TriggerListener
        //scheduler.getListenerManager().addTriggerListener(new MyTriggerListener("simple"), EverythingMatcher.allTriggers());

        //启动
        scheduler.start();
    }

}

3、SchedulerListener

与Scheduler有关的事件,增加删除一个Trigger/Job,scheduler发生错误,关闭scheduler等。
相关方法:
在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值