欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Quartz任务调度框架

程序员文章站 2024-03-18 17:32:58
...

使用场景:我们经常在网站上看到一些限时活动,当活动未结束时,活动显示进行时,当活动到达结束日期时我们要将活动设置为已结束,如下图:
Quartz任务调度框架
怎样让程序自动实现这个效果呢
Quartz就是启动定时任务的框架,下载地址:http://www.quartz-scheduler.org/
我们在maven中使用需要导入以下依赖:

<dependency>
        <groupId>org.quartz-scheduler</groupId>
        <artifactId>quartz</artifactId>
        <version>2.2.1</version>
    </dependency>
    <dependency>
        <groupId>org.quartz-scheduler</groupId>
        <artifactId>quartz-jobs</artifactId>
        <version>2.2.1</version>
    </dependency>
    <dependency>
        <groupId>org.slf4j</groupId>
        <artifactId>slf4j-log4j12</artifactId>
        <version>1.7.12</version>
    </dependency>

使用Quartz我们需要掌握三个对象的使用:
1. Scheduler: 定时器,用于管理JobDetail和Trigger。
2. Job: 执行的任务,需要实现org.quartz.Job接口
3. Trigger:触发器,指定任务执行的时间和循环的方式, 什么时间执行, 多久执行一次
触发器有两种:
1. SimpleTrigger: 设置开始时间,设置间隔时间。特点: 间隔时间必须是相同的情况下。
2. CronTrigger: 表达式

Quartz入门

案例一:我们的工作任务是输出一句话:”Hello Quartz!!!”,现在要让这句话每3秒就输出一次;
编写工作任务:

package cn.itcast.demo2;

import org.quartz.Job;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;

public class HelloJob implements Job{

    public void execute(JobExecutionContext context) throws JobExecutionException {
        System.out.println("Hello Quartz!!!");
    }

}

编写定时任务代码(SimpleTrigger):

package cn.itcast.demo1;

import java.util.Date;

import org.quartz.JobBuilder;
import org.quartz.JobDetail;
import org.quartz.Scheduler;
import org.quartz.SchedulerException;
import org.quartz.SimpleScheduleBuilder;
import org.quartz.Trigger;
import org.quartz.TriggerBuilder;
import org.quartz.impl.StdSchedulerFactory;

public class QuartzDemo1{
    public static void main(String[] args) throws SchedulerException {
         //定时器对象
         Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler(); 
         //定义工作对象
         JobDetail job = JobBuilder.newJob(HelloJob.class).withIdentity("job1", "group1").build();
         //定义触发器
         Trigger tigger = TriggerBuilder.newTrigger().
                 withIdentity("trigger1", "group1").startAt(new Date()).
                 withSchedule(SimpleScheduleBuilder.repeatSecondlyForever(3))
                 .build();
         //开启定时任务
         scheduler.scheduleJob(job, tigger);
         scheduler.start(); 
         //关闭定时任务
        // scheduler.shutdown(); 
    }
}

ps:1.对象 TriggerBuilder 启动任务时间
startNow 立即启动
startAt (Date) 指定时间启动
对象 SimpleScheduleBuilder 进行简单任务重复执行
repeatSecondly …() 多少秒后重复执行
repeatminutely …() 多少分钟后重复执行
repeatHourly …() 多少小时后重复执行
2.Job类需要实现Job接口

案例二:按照按照某个时间安排输出:”Hello Quartz!!!”;
CronTrigger的使用:CronTrigger使用较为复杂,主要是表达式的写法:
一个cron表达式有至少6个(也可能7个)有空格分隔的时间元素,分别为:

按顺序依次为
      1  秒(0~59)
      2  分钟(0~59)
      3 小时(0~23)
      4  天(1~31)
      5 月(1~12)
      6  星期(1~7 (1=SUN )或 SUN,MON,TUE,WED,THU,FRI,SAT)
      7.年份(1970-2099),可选

一些通配符的使用:
“*”字符代表所有可能的值
“/”字符用来指定数值的增量
“?”字符仅被用于天(月)和天(星期)两个子表达式,表示不指定值
“L” 字符仅被用于天(月)和天(星期)两个子表达式,它是单词“last”的缩写
“W”字符代表着平日(Mon-Fri),并且仅能用于日域中。它用来指定离指定日的最近的一个平日。大部分的商业处理都是基于工作周的,所以 W 字符可能是非常重要的。
“C”代表“Calendar”的意思。它的意思是计划所关联的日期,如果日期没有被关联,则相当于日历中所有日期。例如5C在日期字段中就相当于日历5日以后的第一天。1C在星期字段中相当于星期日后的第一天。
下图列出了个字段能出现的值:
Quartz任务调度框架
下面还有一些我们在工作中可能会用到的表达式:

       0 0 10,14,16 * * ? 每天上午10点,下午2点,40  0/30 9-17  * *  ?  朝九晚五工作时间内每半小时
       0 0 12 ? * WED 表示每个星期三中午12"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:102: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触发 
       有些子表达式能包含一些范围或列表
       例如:子表达式(天(星期))可以为 “MON-FRI”,“MON,WED,FRI”,“MON-WED,SAT”

了解完CronTrigger的表达式后,我们这个案例就很简单了,比如我现在的时间是2018/9/3的20:22分,我需要输出”Hello Quartz!!!”,要求是让这句话在今天20:23分的时候每隔2秒就输出一次,代码如下:

package cn.itcast.demo2;

import org.quartz.CronScheduleBuilder;
import org.quartz.JobBuilder;
import org.quartz.JobDetail;
import org.quartz.Scheduler;
import org.quartz.SchedulerException;
import org.quartz.Trigger;
import org.quartz.TriggerBuilder;
import org.quartz.impl.StdSchedulerFactory;

import cn.itcast.demo2.HelloJob;

public class QuartzDemo2{
    public static void main(String[] args) throws SchedulerException {
        // 定时器对象
        Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();

        // 定义一个工作对象
        JobDetail job = JobBuilder.newJob(HelloJob.class)
                .withIdentity("job1", "group1").build();
        // 定义触发器
        Trigger trigger = TriggerBuilder.newTrigger()
                .withIdentity("trigger1", "group1")
                .withSchedule(CronScheduleBuilder.cronSchedule("0/2 23 20 3 9 ?"))
                .build();

        scheduler.scheduleJob(job, trigger);
        // 开启定时任务
        scheduler.start();
    }
}

ps需要注意的点:
day-of-month 和 day-of-week 只能设置一个值,另一个值写 ?
当我们不需要制定年份是,年份一般省略;

**

Quartz整合Spring框架

**
我们在工作中使用Quartz框架往往是整合spring来使用,这样复杂的执行代码就不用我们编写了,我们只需要关心配置:

JobDetialFactoryBean:

<!--job-->
<bean id="exampleJob"
        class="org.springframework.scheduling.quartz.JobDetailFactoryBean">
        <property name="jobClass" value="exampleJob的全类名" />
    </bean>

TriggerFactoryBean:

<!--simpleTrigger-->
<bean id="simpleTrigger"
        class="org.springframework.scheduling.quartz.SimpleTriggerFactoryBean">
        <property name="jobDetail" ref="exampleJob" />
        <!--程序加载就开始运行-->
        <property name="startDelay" value="0" />
        <!--每1分钟执行一次-->
        <property name="repeatInterval" value="60000" />
    </bean>
<!--cromTrigger-->
<bean id="cronTrigger"
        class="org.springframework.scheduling.quartz.CronTriggerFactoryBean">
        <property name="jobDetail" ref="exampleJob" />
        <!--每天早上6点执行-->
        <property name="cronExpression" value="0 0 6 * * ?" />
    </bean>

SchedulerFactoryBean:

    <!--scheduler-->
        <bean class="org.springframework.scheduling.quartz.SchedulerFactoryBean">
        <property name="jobFactory" ref="jobFactory" />
        <property name="triggers">
        <list>
        <ref bean="simpleTrigger"/>
        <ref bean="cronTrigger"/>
        </list>
        </property>
        </bean>

问题:我们在代码中往往是在web层注入service层对象,再调用service层对象执行方法,但是在 Job 中 spring 管理的 Bean 无法注入:

package cn.itcast.quartz.job;

import org.quartz.Job;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.springframework.beans.factory.annotation.Autowired;

import cn.itcast.quartz.service.HelloService;

public class HelloJob implements Job{
    @SuppressWarnings("unused")
    @Autowired
    private HelloService helloService;

    public void execute(JobExecutionContext context) throws JobExecutionException {
    //这里会出现空指针异常
        helloService.sayHello();

    }
}

解决: 需要在 Scheduler 中自定义 JobFactory类:

package cn.itcast.quartz.service;

import org.quartz.spi.TriggerFiredBundle;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.config.AutowireCapableBeanFactory;
import org.springframework.scheduling.quartz.AdaptableJobFactory;
import org.springframework.stereotype.Service;

@Service("jobFactory")
public class JobFactory extends AdaptableJobFactory {

    @Autowired
    private AutowireCapableBeanFactory capableBeanFactory;

    @Override
    protected Object createJobInstance(TriggerFiredBundle bundle)
            throws Exception {
        Object jobInstance = super.createJobInstance(bundle);
        capableBeanFactory.autowireBean(jobInstance);
        return jobInstance;
    }

}

在配置文件中修改:
Quartz任务调度框架