Quartz之Job与JobDetail深入解析
quartz可以用来做什么?
quartz是一个任务调度框架。比如你遇到这样的问题
想每月25号,信用卡自动还款
想每年4月1日自己给当年暗恋女神发一封匿名贺卡
想每隔1小时,备份一下自己的爱情动作片 学习笔记到云盘
这些问题总结起来就是:在某一个有规律的时间点干某件事。并且时间的触发的条件可以非常复杂(比如每月最后一个工作日的17:50),复杂到需要一个专门的框架来干这个事。 quartz就是来干这样的事,你给它一个触发条件的定义,它负责到了时间点,触发相应的job起来干活。
废话不多说,代码杠杠的。。。
public static void main(string[] args) { try { //创建scheduler scheduler scheduler = stdschedulerfactory.getdefaultscheduler(); //定义一个trigger trigger trigger =triggerbuilder.newtrigger().withidentity("trigger1", "group1") //定义name/group .startnow()//一旦加入scheduler,立即生效 .withschedule(simpleschedulebuilder.simpleschedule() //使用simpletrigger .withintervalinseconds(1) //每隔一秒执行一次 .repeatforever()) //一直执行 .build(); //定义一个jobdetail jobdetail job =jobbuilder.newjob(helloquartz.class) //定义job类为helloquartz类,这是真正的执行逻辑所在 .withidentity("job1", "group1") //定义name/group .usingjobdata("name", "quartz") //定义属性 .build(); //加入这个调度 scheduler.schedulejob(job, trigger); //启动之 scheduler.start(); //运行一段时间后关闭 thread.sleep(10000); scheduler.shutdown(true); } catch (exception e) { e.printstacktrace(); } }
helloquartz类
public class helloquartz implements job { public void execute(jobexecutioncontext context) throws jobexecutionexception { jobdetail detail = context.getjobdetail(); string name = detail.getjobdatamap().getstring("name"); system.out.println("say hello to " + name + " at " + new date()); } }
jar包:
这个例子很好的覆盖了quartz最重要的3个基本要素:
scheduler:调度器。所有的调度都是由它控制。
trigger: 定义触发的条件。例子中,它的类型是simpletrigger,每隔1秒中执行一次(什么是simpletrigger下面会有详述)。
jobdetail & job: jobdetail 定义的是任务数据,而真正的执行逻辑是在job中,例子中是helloquartz。 为什么设计成jobdetail + job,不直接使用job?这是因为任务是有可能并发执行,如果scheduler直接使用job,就会存在对同一个job实例并发访问的问题。而jobdetail & job 方式,sheduler每次执行,都会根据jobdetail创建一个新的job实例,这样就可以规避并发访问的问题。
scheduler
scheduler就是quartz的大脑,所有任务都是由它来设施。
schduelr包含一个两个重要组件: jobstore和threadpool。
jobstore是会来存储运行时信息的,包括trigger,schduler,jobdetail,业务锁等。它有多种实现ramjob(内存实现),jobstoretx(jdbc,事务由quartz管理),jobstorecmt(jdbc,使用容器事务),clusteredjobstore(集群实现)、terracottajobstore(什么是terractta)。
threadpool就是线程池,quartz有自己的线程池实现。所有任务的都会由线程池执行。
schedulerfactory
schdulerfactory,顾名思义就是来用创建schduler了,有两个实现:directschedulerfactory和 stdschdulerfactory。前者可以用来在代码里定制你自己的schduler参数。后者是直接读取classpath下的quartz.properties(不存在就都使用默认值)配置来实例化schduler。通常来讲,我们使用stdschdulerfactory也就足够了。
schdulerfactory本身是支持创建rmi stub的,可以用来管理远程的scheduler,功能与本地一样,可以远程提交个job什么的。
1.job
实现类jobdetail
jobdetail job = jobbuilder.newjob(remindjob.class) .withidentity("job1", "group1").build();//创建一个任务 /** * 创建触发器 * 第一种方式 不太好 */ simpletrigger trigger = triggerbuilder.newtrigger().withidentity("mytrigger", "mytriggergroup"). withschedule(simpleschedulebuilder.simpleschedule(). withintervalinseconds(3). repeatforever()). startat(new date(system.currenttimemillis()+1000)).build(); /** * 创建触发器 * 第二种 方式 非常好 * 可以 好用 2013年每月的第三个星期五上午10:30触发 0 30 10 ? * 6#3 2013 * 2016年每月的第一个星期四下午16:17触发 0 17 16 ? * 5#1 2016 * 每天15点到16点每5分钟运行一次,此外,每天17点到18点每5分钟运行一次 */ /*crontrigger trigger=triggerbuilder.newtrigger() .withidentity("mytrigger", "mytriggergroup") .withschedule(cronschedulebuilder.cronschedule("0 18 16 ? * 5#1 2016")).build();*/ schedulerfactory sf=new stdschedulerfactory();//创建调度者工厂 scheduler scheduler = sf.getscheduler();//创建一个调度者 scheduler.schedulejob(job,trigger);//注册并进行调度 scheduler.start();//启动调度 //thread.sleep(millis) //scheduler.shutdown();//关闭调度
remindjob 类的定义
*/ public class remindjob implements job { private remindservice service=new remindservice(); @override public void execute(jobexecutioncontext context) throws jobexecutionexception { service.printplan("你好!"); date date=new date(); string time = date.tostring(); system.out.println(time+"job is starting"); }
可以看到,我们传给scheduler一个jobdetail实例,因为我们在创建jobdetail时,将要执行的job的类名传给了jobdetail,所以scheduler就知道了要执行何种类型的job;每次当scheduler执行job时,在调用其execute(…)方法之前会创建该类的一个新的实例;执行完毕,对该实例的引用就被丢弃了,实例会被垃圾回收;这种执行策略带来的一个后果是,job必须有一个无参的构造函数(当使用默认的jobfactory时);另一个后果是,在job类中,不应该定义有状态的数据属性,因为在job的多次执行中,这些属性的值不会保留。
那么如何给job实例增加属性或配置呢?如何在job的多次执行中,跟踪job的状态呢?答案就是:jobdatamap,jobdetail对象的一部分。
jobdatamap
jobdatamap中可以包含不限量的(序列化的)数据对象,在job实例执行的时候,可以使用其中的数据;jobdatamap是java map接口的一个实现,额外增加了一些便于存取基本类型的数据的方法。
将job加入到scheduler之前,在构建jobdetail时,可以将数据放入jobdatamap,如下示例:
jobdetail job=jobbuilder.newjob(remindjob.class) .withidentity("job1", "group1") .usingjobdata("hello", "we are family") .build();
在job的执行过程中,可以从jobdatamap中取出数据,如下示例:
@override public void execute(jobexecutioncontext context) throws jobexecutionexception { service.printplan("你好!"); jobkey key=context.getjobdetail().getkey(); jobdatamap map = context.getjobdetail().getjobdatamap(); string string = map.getstring("hello"); system.out.println(key+"==========="+string); date date=new date(); string time = date.tostring(); system.out.println(time+"job is starting"); }
如果你使用的是持久化的存储机制(本教程的jobstore部分会讲到),在决定jobdatamap中存放什么数据的时候需要小心,因为jobdatamap中存储的对象都会被序列化,因此很可能会导致类的版本不一致的问题;java的标准类型都很安全,如果你已经有了一个类的序列化后的实例,某个时候,别人修改了该类的定义,此时你需要确保对类的修改没有破坏兼容性;更多细节,参考现实中的序列化问题。另外,你也可以配置jdbc-jobstore和jobdatamap,使得map中仅允许存储基本类型和string类型的数据,这样可以避免后续的序列化问题。
如果你在job类中,为jobdatamap中存储的数据的key增加set方法(如在上面示例中,增加setjobsays(string val)方法),那么quartz的默认jobfactory实现在job被实例化的时候会自动调用这些set方法,这样你就不需要在execute()方法中显式地从map中取数据了。
在job执行时,jobexecutioncontext中的jobdatamap为我们提供了很多的便利。它是jobdetail中的jobdatamap和trigger中的jobdatamap的并集,但是如果存在相同的数据,则后者会覆盖前者的值。
下面的示例,在job执行时,从jobexecutioncontext中获取合并后的jobdatamap:
@override public void execute(jobexecutioncontext context) throws jobexecutionexception { service.printplan("你好!"); jobkey key=context.getjobdetail().getkey(); /* jobdatamap map = context.getjobdetail().getjobdatamap(); string string = map.getstring("hello"); system.out.println(key+"==========="+string);*/ jobdatamap map = context.getmergedjobdatamap(); string string = map.getstring("hello"); system.out.println(key+"--------------------- "+string);
以上这篇quartz之job与jobdetail深入解析就是小编分享给大家的全部内容了,希望能给大家一个参考,也希望大家多多支持。