quartz(一):quartz简介及使用

一   简介   

        在生活中,我们经常会制定一些“计划任务”,即在某个时间点做某件事情。同样地,在企业级应用中,也会经常碰到类似的任务调度的需求,下面来看几个例子。在购物网站,每天凌晨统计商品名、商家排名,每天晚上定点统计当日的销量、销售额、盈利等信息并生成报表,每15分钟查询用户的新订单并推送给对应处理人。在社区网站,每天统计用户的在线时长,并按照某种规则给予一定的称号和奖励等。在后台服务中做系统维护,每个工作日的固定时间将数据进行备份。可见,企业应用中离不开灵活的任务调度。

从以上例子可以看到,调度的核心是以时间为关注点,即在一个特定的时间点,系统执行指定的-个操作。任务调度本身涉及多线程并发、运行时间规则解析、运行现场的保护与恢复及线程池维护等。这项非常复杂的工作,可以通过一个开源任务调度框架来实现,它就是Quartz框架。

Quartz框架是一个开源的企业级任务调度服务,它可以被单独使用,也可以整合进任何Java应用,从小型应用到大型的电子商务系统,Quartz已经被作为任务调度的良好解决方案。Quartz提供了强大的任务调度机制,使用也非常简单。Quartz允许开发人员灵活定义调度时间表,提供和任务进行关联的便捷方法,另外,Quartz提供了调度运行环境的持久化机制,使系统出现故障关闭时,任务调度现场数据可以保存下来不致丢失。

1.1   Quartz框架核心概念

        Quartz是一款开源框架,为了在应用中使用它,需要在官方网站(http://quartz-scheduler.org)下载Quartz,并添加到项目的classpath中。本文中,我们选用Quatz的1.8.6版本,此版本在项目中应用较多,也较为稳定。

       在Quartz完整的下载包中包括docs文件夹和examples'文件夹。其中从docs文件夹中,可以找到Quarz完整的API文档,为开发提供了帮助;另外,examples文件中的多个示例程序,可以让你快速了解Quartz的核心功能。

下面来看一个具体的问题。

例如在某个OA系统中,允许用户制定工作提醒,包括任务时间和任务内容。通过定时任务,对员工张三的工作任务进行提醒,实现每3秒进行一次任务提醒,定时器在10秒后关闭。效果如图所示。


任务调度的核心是支持“某个时间”执行“一个计划任务”,在以上问题中,时间点为“每隔3秒”,执行的任务是“进行工作提醒”,即根据定制的提醒列表输出提醒信息。因此,需要设置一个调度来决定什么时间调用工作提醒任务。Quartz对任务调度进行了高度抽象,提出了3个核心概念一一任务、触发器和调度器,并在org.quartz中通过类和接口对核心概念进行了描述。

1.任务

顾名思义,任务就是执行的工作内容。Quartz提供Job接口来支持任务定义。Job接口的方法声明如下。

public interface   Job {

   void  execue(JobExceutionContextcontext)  throws  JobExecutionException;

}

Job接口中只有一个execute()方法,开发者需要在自己的任务类中实现该方法,完成具体任务的执行。通过该方法中传人的JobExecutionContext,可以获取调度上下文的各种信息,如任务名称等。

Quartz每次执行Job时,都会创建一个Job实现类的新的实例,JobDetail类就是Job接口的一个实现类。Quartz允许对Job进行分组。

 

2.触发器

创建的Job要在什么时间定时执行呢?在Quartz中,触发器Trigger类允许定义触发Job执行的时间触发规则。例如,每隔1小时执行一次,每天15:00执行等。Trigger有两个实现类,分别为SimpleTrigger和CronTrigger,两个不同的触发器为不同的应用场景提供支持。

 

3.调度器

如何将工作任务和触发器绑定,保证任务可以在正确的时间执行呢?Quartz提供了调度器Scheduler类,它是Quartz独立运行的容器。Trigger和JobDetail可以注册到Scheduler中。Scheduler定义了多个接口方法,允许通过组及名称访问容器中的Trigger和JobDetail。Scheduler可以将Trigger绑定到一个JobDetail上,当Trigger被触发后,一个Job就会被执行。通过任务、触发器和调度器,就可以通过Quartz轻松实现任务调度。除此之外,Quartz还对一些特殊场景提供了支持,如Calendar对象。具体的使用在后面的小节会重点讲解。


1.2   quartz框架的使用流程

1.   建立java工程,导入jar包


2.  编写job

===================TaskEntity.java===================

package com.obtk.entitys;

public class TaskEntity {
	private String dateStr;
	private String task;
	
	public TaskEntity() {
		
	}
	public TaskEntity(String dateStr, String task) {
		super();
		this.dateStr = dateStr;
		this.task = task;
	}
	public String getDateStr() {
		return dateStr;
	}
	public void setDateStr(String dateStr) {
		this.dateStr = dateStr;
	}
	public String getTask() {
		return task;
	}
	public void setTask(String task) {
		this.task = task;
	}
	public void showInfo(){
		System.out.println(this.dateStr+"===>"+this.task);
	}
}
==================TaskDao.java==================

package com.obtk.dao;

import java.util.ArrayList;
import java.util.List;

import com.obtk.entitys.TaskEntity;

public class TaskDao {
	public List<TaskEntity> queryAllTask(){
		List<TaskEntity> taskList=new ArrayList<TaskEntity>();
		TaskEntity task1=new TaskEntity("2018-03-15 08:00:00", "起床执行打假任务");
		TaskEntity task2=new TaskEntity("2018-03-15 20:30:00", "到打假办公室315进行研讨");
		taskList.add(task1);
		taskList.add(task2);
		return taskList;
	}
	
}
==================DaJiaJob.java====================

package com.obtk.jobs;

import java.util.List;

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

import com.obtk.dao.TaskDao;
import com.obtk.entitys.TaskEntity;

public class DaJiaJob implements Job{
	private TaskDao taskDao;
	
	public void setTaskDao(TaskDao taskDao) {
		this.taskDao = taskDao;
	}
	
	public void execute(JobExecutionContext arg0) throws JobExecutionException {
		String userName=(String)arg0.getJobDetail().getJobDataMap().get("user");
		setTaskDao(new TaskDao());
		List<TaskEntity> taskList=taskDao.queryAllTask();
		System.out.println("请注意,这是"+userName+"发的消息==>");
		for(TaskEntity task : taskList){
			//输出打假任务通知的内容
			task.showInfo();
		}
	}
}


3.  编写测试类,在测试类中编写触发器和调度器
======================TestJob.java===================

package com.obtk.test;

import java.util.Date;

import org.quartz.JobDataMap;
import org.quartz.JobDetail;
import org.quartz.Scheduler;
import org.quartz.SchedulerException;
import org.quartz.SchedulerFactory;
import org.quartz.SimpleTrigger;
import org.quartz.impl.StdSchedulerFactory;

import com.obtk.jobs.DaJiaJob;

public class TestJob {
	public static void main(String[] args) {
		try {
			//创建一个任务
			JobDetail job=new JobDetail("remind task", "group1", DaJiaJob.class);
			//传递参数
			JobDataMap dataMap=job.getJobDataMap();
			dataMap.put("user", "段玉");
			//创建一个触发器,每隔3s执行一次,重复4次,总共执行5次
			SimpleTrigger simTrig=new SimpleTrigger("dajiaTrig",4, 3000);
			//1s钟之后启动
			simTrig.setStartTime(new Date(System.currentTimeMillis()+1000));
			//得到调度者工厂
			SchedulerFactory factory=new StdSchedulerFactory();
			//由工厂得到调度者对象
			Scheduler sched=factory.getScheduler();
			//调度任务执行
			sched.scheduleJob(job, simTrig);
			sched.start();//启动一个单独线程
			Thread.sleep(15000); //睡眠15秒
			sched.shutdown();
			System.out.println("main方法结束了");
		} catch (SchedulerException e) {
			e.printStackTrace();
		}catch (Exception e) {
			e.printStackTrace();
		}
		
	}
}


二    使用CronTrigger实现简单调度

1.    对比SimpeTrigger和CronTrigger

在上一节中提到,Quartz提供的Trigger包括SimpleTrigger和CronTrigger,之前的小节中,已经了解了SimpleTrigger,那么,SimpleTrigger和CronTrigger在应用场景和使用方式上有什么差异呢?如表所示。

触发器

应用场景

使用方式

SimpleTrigger

固定时间间隔的调度任务(例如每隔2小时执行1次)

通过设置触发器的属性:开始时间、结束时间、重复次数、重复间隔等

CronTrigger

指定时间点的调度任务(例如每天1:00执行1次)

通过定义Cron表达式


通过表7-1可知,CronTrigger允许用户更精准地控制任务的运行时间和日期。它不是定义工作盼频度,而是通过Cron表达式定义准确的运行时间。在实际的企业级应用中,CronTrigger也更加实用。例如,可以使用下面代码替换示例3中的SimpleTrigger,运行效果一样。其中“1/3  *  *  *  *  ?"就是一个Cron表达式。

      CronTrigger   cronTrig=new CronTrigger("remindJob","group1", "1/3   *   *   *   *   ?");

2   cron表达式

要使用CronTrigger,必须掌握Cr on表达式的定义方法。Cron表达式至少有6个(也可能是7个)由空格分隔的时间元素组成。第7个元素是可选的。从左至右,这些元素定义如表所示。

位  置

字段含义

范  围

允许的特殊字符

1

O~59

*   /

2

分钟

0~59

*   /

3

小时

0~23

*   /

4

月份中的哪一天

1~31

*   /  ?   L

5

月份

l~l2或者JAN--DEC

*   /

6

星期几

l~7或者SUN-- SAT

*   /   ?   L   #

7

年份

1970~2099

*   /


Cron表达式的每个字段,都可以显式地规定一个值(如49)、一个范围(如1-6)、一个列表(如l,3,5)或者一个通配符(如*)表示每个值。有几个特殊的字符,具体说明如下。

(1)“一”:中划线,表示一个范围。

(2)“,”:使用逗号间隔的数据,表示一个列表。

(3)“*”:表示每个值,它可以用于所有字段。例如,在小时字段表示每小时。

(4)“?”:该字符仅用于“月份中的哪一天”字段和“星期几”字段,表示不指定值。当这两个字段其中之一被指定了值之后,为了避免冲突,需要将另外一个字段的值设为“?”。

(5)“/”:通常表示为x/y,x为起始值,y表示值的增量。例如,在分钟字段中使用“0/15”,表示0.15,30,45;在分钟字段中使用“3/20”表示从第3分钟开始,即3,23,43。

(6)“L”:表示“Last”,仅在日期和星期字段中使用,但是在两个字段中表示的含义不同。L在“月份中的哪一天”字段中,表示一个月的最后一天。,L在“星期几”字段中,表示一个星期的最后一天,也就是SAT(或者数字7)。例如,“0 0 14 7*L“表示每个月的最后一个星期的星期六14:00执行。另外,在“星期几”字段上,你可以用一个数字和L连用,表示月份的最后一个星期×。

例如,表达式“0'0 0 7*2L”中“2L”指每个月的最后一个星期一触发。需要注意的是,在使用L时,不要指定列表或范围,否则会导致错误。

(7)“≠≠”:只能用于“星期几”字段,表示这个月的第几个周几。例如,“6#3”指这个月第3个周五(6指周五,3指第3个)。如果指定的日期不存在,则不被触发。

下面通过几个示例来熟悉Cron表达式,如下表所示。

Cron表达式

含   义

0  15  4  *  * ?

每天凌晨4:15

0 0 8-12 ? * MON-FRI

每个工作日的8:00~12:00

30  0  0  1  1 ?2014

2014年1月l曰凌晨过30秒

0 0 14  1,10,20 * ?*

每月的l曰、10日、20 E|的14:00

0  0 17  L  *  ?

每月最后一天17:00运行

0  0  10  ?  *  6L

每月最后一个星期五10:00运行

0  0/5  15,17  *  *  ?

每天15:00-16:00每5分钟运行一次,此外,每天17:00~18:00每5分钟运行一次

0  30  10  ?  *   6#3 2013

2013年每月的第3个星期五10:30触发

==================

测试案例:

package com.obtk.test;

import java.util.Date;

import org.quartz.CronTrigger;
import org.quartz.JobDataMap;
import org.quartz.JobDetail;
import org.quartz.Scheduler;
import org.quartz.SchedulerException;
import org.quartz.SchedulerFactory;
import org.quartz.SimpleTrigger;
import org.quartz.impl.StdSchedulerFactory;

import com.obtk.jobs.DaJiaJob;

public class TestJob2 {
	public static void main(String[] args) {
		try {
			//创建一个任务
			JobDetail job=new JobDetail("remind task", "group1", DaJiaJob.class);
			//传递参数
			JobDataMap dataMap=job.getJobDataMap();
			dataMap.put("user", "段玉");
			//创建一个crontrigger
			//2017年每月13号每分钟的第5秒开始,每5秒执行一次
			CronTrigger cronTrig=new CronTrigger("cronTrig", "group1", "5/5 * * 13 * ? 2017");
			//1s钟之后启动
			cronTrig.setStartTime(new Date(System.currentTimeMillis()+1000));
			//得到调度者工厂
			SchedulerFactory factory=new StdSchedulerFactory();
			//由工厂得到调度者对象
			Scheduler sched=factory.getScheduler();
			//调度任务执行
			sched.scheduleJob(job, cronTrig);
			sched.start();//启动一个单独线程
			//Thread.sleep(15000); //睡眠15秒
			//sched.shutdown();
			System.out.println("main方法结束了");
		} catch (SchedulerException e) {
			e.printStackTrace();
		}catch (Exception e) {
			e.printStackTrace();
		}
		
	}
}

3.  使用Calendar

在某OA系统中,对于计划任务的提醒要求在每个工作日9:30发出,元旦除外。

分析上述问题,这里需要Cron定义一个精确的提醒任务的执行时间,另外,还需要排除每年中的节假日,这时就需要使用Quartz提供的Calendar对象(这个不同于Java API中的java.util.Calendar对象)。Quartz Calendar能够与Trigger进行关联,对于排除Trigger中的时间区间是很有用的。Calendar_是一个接口,下表列出了它的实现类。

Calendar名称

作  用

WeeklyCalendar

用于排除星期中的一天或多天

MonthlYCalendar

用于排除月份中的数天

AnnualCalendar

用于排除年份中的一天或多天

HolidayCalendar

用于排除节假日

要使用Quartz的Calendar,首先进行类实例化,并加入你要排除的日期,然后用Scheduler注册它,最后将Calendar和对应的Trigger进行关联。这样就实现了上述OA系统的要求,如下所示。

package com.obtk.test;

import java.util.*;

import org.quartz.CronTrigger;
import org.quartz.JobDataMap;
import org.quartz.JobDetail;
import org.quartz.Scheduler;
import org.quartz.SchedulerFactory;
import org.quartz.impl.StdSchedulerFactory;
import org.quartz.impl.calendar.AnnualCalendar;

import com.obtk.jobs.RemindJob;

public class SheduleTest2 {
	public static void main(String[] args) {
		//创建一个任务
		JobDetail job=new JobDetail("myjob", "wxgroup", RemindJob.class);
		JobDataMap dataMap=job.getJobDataMap();
		dataMap.put("userName", "麻子");
		CronTrigger cronTrig=null;
		//创建调度者
		SchedulerFactory sfc=new StdSchedulerFactory();
		Scheduler schedul=null;
		try {
			cronTrig=new CronTrigger("myjobTri", "cronTrigs", "0/3 * * * * ?");
			//构建排除日期的对象
			AnnualCalendar exculteCal=new AnnualCalendar();
			//java.uitl包下面的,mycal表示要排除的日期
			Calendar mycal=GregorianCalendar.getInstance();
			mycal.set(Calendar.MONTH, Calendar.DECEMBER);
			mycal.set(Calendar.DAY_OF_MONTH, 23);
			//进行排除
			exculteCal.setDayExcluded(mycal, true);
			schedul=sfc.getScheduler();
			//调度对象排除日期
			schedul.addCalendar("test", exculteCal, true, true);
			cronTrig.setCalendarName("test");
			
			
			//调度者把任务和触发器结合起来
			schedul.scheduleJob(job, cronTrig);
			//启动调度
			schedul.start();
			//多少秒之后停止
			Thread.sleep(10000);
			//停止调度
			schedul.shutdown();
		} catch (Exception e) {
			e.printStackTrace();
		}
		
		
	}
}








  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

御前两把刀刀

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值