Spring framework(9):Quartz 的基本使用和 Spring 集成

Quartz 的快速使用

JDK 1.3 开始通过 java.util.Timer 和 java.util.TimerTask 提供了简单的任务调度功能,允许用户调度一个按固定时间间隔运行的任务,但是对于复杂任务调度业务实现起来还是很麻烦;

OpenSymphony 提供的 Quartz 开源库在此基础上,提供了复杂灵活的任务调度功能,使用 quertz 需要导入以下依赖:
org.quartz-scheduler:quartz

Quartz 官方技术文档:http://www.quartz-scheduler.org

Quartz 对于任务调度进行了高度抽象,提出了调度器、任务、触发器3个核心的概念,并提供了以下的接口和实现类进行支持:
  • Job
用于定义需要执行的任务,JobExecutionContext 类提供了调度上下文的信息,JobDataMap 用于保存 Job 运行时的信息;
  • JobDetail
用于描述 Job 的事项类及其相关的静态信息,这是由于 Quartz 每次执行 Job 时,都会重新创建一个新的 Job 实例,因此需要一个类用于反射实例化 Job;
  • Trigger
用于描述触发 Job 执行的时间触发规则,主要有以下2个实现类:
    • SimpleTrigger:简单触发器,用于仅需要触发一次或以固定时间间隔周期性执行的任务;
    • CronTrigger:通过 Cron 表达式定义各种复杂的调度方案;
一个 Trigger 只能对应一个 Job,一个 Job 可以对应多个 Trigger;
  • Calendar
用于描述一些特定时间点的集合,可以看成 java.util.Calendar 时间点的集合;
一个 Trigger 可以与多个 Calendar 关联,org.quartz.impl.calendar 包下提供了 Calendar 的几个实现类:AnnualCalendarMonthlyCalendarWeeklyCalendar 分别针对每年、每月、每周进行定义;
  • Scheduler
用于代表一个 Quartz 的独立运行容器,Trigger 和 JobDetail 可以注册到 Schedular 中;
  • ThreadPool
Schedular  使用一个线程池作为任务运行的基础设施,任务通过共享线程池中的线程来提高运行效率;




快速使用

以下是 Quartz 的快速使用示例,示例的 quartz 版本为 2.2.3;
创建一个 Job 的实现类, HelloJob
 
public class HelloJob implements Job{
    @Override
    public void execute(JobExecutionContext context) throws JobExecutionException {
        JobDetail jobDetail = context.getJobDetail();
        System.out.println("["+jobDetail.getKey().getName() + ","
                +jobDetail.getKey().getGroup() +"] "
                +"Hello world! "
                + new Date());
    }
}
演示SimpleTrigger的使用的示例代码
 
//通过工厂方法创建Scheduler
SchedulerFactory schedulerFactory = new StdSchedulerFactory();
Scheduler scheduler = schedulerFactory.getScheduler();
//创建 JobDetail
JobDetail jobDetail = newJob(HelloJob.class)           //指定实现类类型
        .withIdentity("job1","group1")   //指定identity
        .build();
//创建 Trigger
SimpleTrigger simpleTrigger =(SimpleTrigger)newTrigger()
        .withIdentity("trigger1","group1")  //指定 identity
        .startAt(new Date())                   //指定启动时间,如果为现在启动,使用.startNow()
        .withSchedule(simpleSchedule()
                    .withIntervalInSeconds(5)   //指定循环间隔时间,可以 withIntervalInMinutes,withIntervalInHours
                    .repeatForever())    //指定循环次数,无限循环,可以.withRepeatCount(200) 指定固定循环次数
        .endAt(dateOf(19,0,0)) //停止时间为当天的 19:00:00,可选
        .build();
//在 scheduler 绑定 jobDetail 和 trigger
scheduler.scheduleJob(jobDetail,simpleTrigger);
//启动 scheduler
scheduler.start();  
演示 CronTrigger 使用的示例代码
 
SchedulerFactory schedulerFactory = new StdSchedulerFactory();
Scheduler scheduler = schedulerFactory.getScheduler();
JobDetail jobDetail = newJob(HelloJob.class)
        .withIdentity("job2","group1")
        .build();
CronTrigger cronTriggerr = (CronTrigger)newTrigger()
        .withIdentity("trigger2","group1")
        .withSchedule(cronSchedule("0/5 * * * * ?"))  //指定 cron 表达式:每分钟从0s开始,间隔5s执行
        .build();
scheduler.scheduleJob(jobDetail,cronTriggerr);
scheduler.start();

Cron 表达式

Quartz 使用的 CronTrigger 使用类似 Linux 的 Cron 表达式定义时间规则,一个典型的 Cron 表达式由6或7个空格分隔的字段组成,如表达式 “0 0 12 * * ?”  表示每一天的12:00 这一个时刻,具体每一段的规则如下:

※注意星期1表示周日,7表示周六;
※Cron表达式对大小写不敏感;
其中个各个特殊符号的含义如下:
符号适用字段说明
*所有字段表示对应时间区域的每一个时刻。如: * 在分钟字段时,表示“每分钟”
?日期/星期表示占位符,无意义。
因为日期,星期值往往不需要同时指定,只需要指定其中一个,另一个使用 ? 占位即可;
-所有字段表示一个范围。如:小时字段值为“9-12” ,表示“9点到12点”;
,所有字段表示一个列表值。如:月份字段值为“1,4,6” ,表示“1月、4月、6月”;
/所有字段x/y 表示一个等步长序列,x为起始值,y为增量步长值。
如:在分钟字段中 0/15 表示“0,15,30,45分钟”, */y 等同于 0/y
L日期/星期即“Last”,在两个字段的意思不用;
日期字段:表示该月份的最后一天,“* * * L 1 ?” 表示1月最后一天,即1月31日;
星期字段:表示该周六,等于值 7,在该字字段中,NL 可以表示该月的最后的周几,如 “6L” 表示该月的最后一个周五;
W日期对前导日期进行修饰,表示里改日期最近的工作日。如:日期字段中 “15W” 表示离15日最近的工作日,注意该匹配不会跨月;
LW日期表示当月的最后一个工作日;
#星期表示当月的某个工作日。如星期字段字段中 “6#3” 表示当月的第3个周五,“4#5”表示当月的第5个周三,如果当月不存在这个日期,则不触发;
C日期/星期即“Calendar”,表示计划关联的日期,如果日期没有被关联,则相当于日历中的所有日期。
如:在日期字段中 “5C” 表示5日以后的第一天,在星期字段中“1C”表示周日后的第一天;

使用 Calendar 排除特定日期

在 Quartz 中可以使用 org.quartz.impl.calendar.Calendar 排除一些特殊的日期,如节假日等,一个示例如下:
 
 //以下要执行一个任务,每1小时执行一次,将国庆节10.1-10.7排除在外
 SchedulerFactory schedulerFactory = new StdSchedulerFactory();
 Scheduler scheduler = schedulerFactory.getScheduler();
 JobDetail jobDetail = newJob(HelloJob.class)
         .withIdentity("job1","group1")
         .build();
 //设定 Calendar 集合
 AnnualCalendar holidays = new AnnualCalendar();
 ArrayList<Calendar> nationDayList = new ArrayList<>();
 for(int i=1;i<=7;i++){
     Calendar nationDay = new GregorianCalendar();
     nationDay.add(Calendar.MONTH,10);
     nationDay.add(Calendar.DATE,i++);
 }
 holidays.setDaysExcluded(nationDayList);  //设置每年执行集合的排除时间规则
 SimpleTrigger simpleTrigger = (SimpleTrigger) newTrigger()
         .withIdentity("job3","group1")
         .startAt(dateOf(19,5,0,19,1,2017))  //在 2017-1-19 19:05:00 开始执行
         .withSchedule(simpleSchedule()
                 .withIntervalInHours(1)
                 .repeatForever()
         )
         .build();
 scheduler.scheduleJob(jobDetail,simpleTrigger);
 scheduler.start();

修改调度信息的保存策略

Quartz的默认调度信息保存在内存中,可以通过修改配置文件来修改调度信息的保存策略,同时还可以修改一些运行参数;
默认的配置文件位于 Quartz JAR 包下 org.quartz 的 quartz.properties ;如果要覆盖其配置信息,只需要在项目的根目录下创建一个 quartz.properties 即可;
默认的部分配置如下:
 
# 配置调度器的线程池
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.class = org.quartz.simpl.RAMJobStore
以下配置将调度信息使用Mysql数据库作为持久化保存策略:
 
# 配置任务调度现场数据保存机制:使用数据库持久化
org.quartz.jobStore.class = org.quartz.impl.jdbcjobstore.JobStoreTX
org.quartz.jobStore.tablePrefix = QUTZ_
org.quartz.jobStore.dataSource = qzDS
# 定义具体数据源的配置属性,这里使用 Mysql
org.quartz.dataSource.qzDS.driver = com.mysql.jdbc.Driver
org.quartz.dataSource.qzDS.URL = jdbc:mysql://localhost:3306/sampledb
org.quartz.dataSource.qzDS.user = root
org.quartz.dataSource.qzDS.password = 123456
org.quartz.dataSource.qzDS.maxConnections = 30
注意 Quartz 是使用 JDBC 作为持久化层的,项目中需要导入 Mysql-JDBC 驱动依赖;

如果使用数据库保存调度信息,Quartz 支持从数据库中恢复被中断的任务,如下示例恢复定位为 group1.trigger1 的触发器相关的任务:
 
SchedulerFactory schedulerFactory = new StdSchedulerFactory();
Scheduler scheduler = schedulerFactory.getScheduler();
//获取定位为“group1.trigger1”的 Trigger
SimpleTrigger trigger = (SimpleTrigger) scheduler.getTrigger(TriggerKey.triggerKey("trigger1","group1"));
//恢复运行指定的 Trigger
scheduler.rescheduleJob(trigger.getKey(),trigger);
scheduler.start();





在 Spring 中使用 Quartz 


Spring 对于 Quartz 的支持主要包括以下2方面:
  • 为 Quartz 的重要组件提供更具 Bean 风格的拓展类;
  • 为创建 Scheduler 的 BeanFactory 类,方便在 Spring 环境下创建对应的组件对象,并结合 Spring 容器生命周期执行启动和停止工作;

以下示例在 Spring 中使用 Quartz 的过程,同样需要导入相关的依赖;
以下示例完整代码地址:https://gitee.com/assad/springframework-test-quartz

创建 JobDetail

有2种创建 JobDetail 的方式:
1)通过 JobDetailFactoryBean 注册
在 applicationContext.xml 中的配置如下:
 
    <!--封装 JobDetail 方式1:通过 JobDetailFactoryBean 注册-->
    <bean id="jobDetail_1" class="org.springframework.scheduling.quartz.JobDetailFactoryBean"
          p:name="jobDetail-1"
          p:group="group-1"
          p:jobClass="site.assad.quartz.MyJob"
          p:applicationContextJobDataKey="applicationContext" >
        <property name="jobDataAsMap">
            <map>
                <entry key="message" value="Hello world" />
            </map>
        </property>
    </bean>
编写 Job 实现类 MyJob
 
package site.assad.quartz;
public class MyJob implements Job{
    @Override
    public void execute(JobExecutionContext context) throws JobExecutionException {
        Map dataMap = context.getJobDetail().getJobDataMap(); //获取储存在配置文件中的 DataMap
        String message = (String) dataMap.get("message");  //获取key="message"的value
        ApplicationContext applicationContext = (ApplicationContext) dataMap.get("applicationContext");  //获取Spring容器中的上下文对象
        System.out.println("message: "+message);
    }
}
2)通过服务封装
在 applicationContext.xml 中的配置如下:
 
    <!--封装 JobDetail 方式2:通过服务封装-->
    <bean id="jobDetail_2"  class="org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean"
          p:name="jobDetail-2"
          p:group="group-1"
          p:targetObject-ref="myService"
          p:targetMethod="doJob"
          p:concurrent="false"/>
    <bean id="myService" class="site.assad.service.MyService" />
Job 的指定逻辑是编写在服务层的某个服务方法的,MyService
 
package site.assad.service;
import org.springframework.stereotype.Service;
@Service
public class MyService {
    public void doJob(){  //被封装的任务方法
        System.out.println("in MyService.doJob()");
    }
}

创建 Trigger

在 applicationContext.xml 中的配置如下:
 
    <!--装载 SimpleTrigger-->
    <bean id="simpleTrigger" class="org.springframework.scheduling.quartz.SimpleTriggerFactoryBean"
          p:name="trigger-1"
          p:group="group-1"
          p:jobDetail-ref="jobDetail_1"
          p:repeatInterval="3000"
          p:repeatCount="100" />
    <!--装载 CronTrigger-->
    <bean id="cronTrigger" class="org.springframework.scheduling.quartz.CronTriggerFactoryBean"
          p:name="trigger-2"
          p:group="group-1"
          p:jobDetail-ref="jobDetail_2"
          p:cronExpression="0/5 * * * * ?" />

创建 Scheduler

在 applicationContext.xml 中的配置如下:
    <!--装载 Scheduler -->
    <bean id="scheduler" class="org.springframework.scheduling.quartz.SchedulerFactoryBean">
        <property name="triggers">
            <list>
                <ref bean="simpleTrigger"/>
                <ref bean="cronTrigger"/>
            </list>
        </property>
        <!--显式设置配置文件地址-->
        <!--<property name="configLocation" value="classpath:quartz.properties" />-->
    </bean>

测试代码如下:
 
public class QuartzTest {
    private ApplicationContext ctx ;
    @Before
    public void init(){
        ctx = new ClassPathXmlApplicationContext("classpath:applicationContext.xml");
    }
    @Test
    public void testScheduler1() throws SchedulerException, InterruptedException {
        Scheduler scheduler = ctx.getBean("scheduler",Scheduler.class);
        scheduler.start();
        Thread.sleep(30 * 1000);
    }
}
以上的配置方式是在配置文件中直接将所有的 Trigger 装配到 Scheduler 中,实际使用过程中,可以通过获取 scheduler 的实例,根据业务情况很灵活地为其加载相关地 Trigger、JobDetail;
 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值