傻瓜式php网站开发/yandere搜索引擎入口
背景:
定时任务,在企业开发中尤其重要,很多业务都是需要定时任务去做的。比如说10点开售某件东西,凌晨0点统计注册人数,统计其他各种等等。这个时候不可能说让人为的去开启某个开关或者怎么怎么样的,如果这样的话,估计都要崩溃了。今天给大家介绍如何在项目中使用Quartz并且在后台动态配置定时任务的启动,暂停,重启,停止,还有修改启动的时间,修改执行的任务等。
你想要的官方文档都有介绍:
http://www.quartz-scheduler.org/api/2.4.0-SNAPSHOT/index.html
正则、实现说明统统都有等。
这里有你需要的各种配置方式:https://www.w3cschool.cn/quartz_doc/。(配置内容不搬运过来)
这里特别说明以下都是参考至这里。
我们需要明白 Quartz 的几个核心概念,这样理解起 Quartz 的原理就会变得简单了。
1,Job 表示一个工作,要执行的具体内容。此接口中只有一个方法
2,JobDetail 表示一个具体的可执行的调度程序,Job 是这个可执行程调度程序所要执行的内容,另外 JobDetail 还包含了这个任务调度的方案和策略。
3,Trigger 代表一个调度参数的配置,什么时候去调。
4,Scheduler 代表一个调度容器,一个调度容器中可以注册多个 JobDetail 和 Trigger。当 Trigger 与 JobDetail 组合,就可以被 Scheduler 容器调度了。
一,在使用Scheduler之前,需要实例化(这点你是否知道)。
scheduler实例化后,可以启动(start)、暂停(stand-by)、停止(shutdown)。注意:scheduler被停止后,除非重新实例化,否则不能重新启动;只有当scheduler启动后,即使处于暂停状态也不行,trigger才会被触发(job才会被执行)。
下面的代码片段,实例化并启动一个scheduler,调度执行一个job:
SchedulerFactory schedFact = new org.quartz.impl.StdSchedulerFactory();Scheduler sched = schedFact.getScheduler();sched.start();// define the job and tie it to our HelloJob classJobDetail job = newJob(HelloJob.class).withIdentity("myJob", "group1").build();// Trigger the job to run now, and then every 40 secondsTrigger trigger = newTrigger().withIdentity("myTrigger", "group1").startNow().withSchedule(simpleSchedule().withIntervalInSeconds(40).repeatForever()).build();// Tell quartz to schedule the job using our triggersched.scheduleJob(job, trigger);
二,Quartz API,Jobs和Triggers
Quartz API的关键接口是:
Scheduler - 与调度程序交互的主要API。
Job - 由希望由调度程序执行的组件实现的接口。
JobDetail - 用于定义作业的实例。
Trigger(即触发器) - 定义执行给定作业的计划的组件。
JobBuilder - 用于定义/构建JobDetail实例,用于定义作业的实例。
TriggerBuilder - 用于定义/构建触发器实例。
Scheduler的生命期,从SchedulerFactory创建它时开始,到Scheduler调用shutdown()方法时结束;Scheduler被创建后,可以增加、删除和列举Job和Trigger,以及执行其它与调度相关的操作(如暂停Trigger)。但是,Scheduler只有在调用start()方法后,才会真正地触发trigger(即执行job),见教程一。
Quartz提供的“builder”类,可以认为是一种领域特定语言(DSL,Domain Specific Language)。教程一中有相关示例,这里是其中的代码片段:(校对注:这种级联的API非常方便用户使用,大家以后写对外接口时也可以使用这种方式,这种方式前面我已经介绍过了)
// define the job and tie it to our HelloJob classJobDetail job = newJob(HelloJob.class).withIdentity("myJob", "group1") // name "myJob", group "group1".build();// Trigger the job to run now, and then every 40 secondsTrigger trigger = newTrigger().withIdentity("myTrigger", "group1").startNow().withSchedule(simpleSchedule().withIntervalInSeconds(40).repeatForever()) .build();// Tell quartz to schedule the job using our triggersched.scheduleJob(job, trigger);
定义job的代码使用的是从JobBuilder静态导入的方法。同样,定义trigger的代码使用的是从TriggerBuilder静态导入的方法 。 另外,也导入了SimpleSchedulerBuilder类的静态方法;
SchedulerBuilder接口的各种实现类,可以定义不同类型的调度计划(schedule);
DateBuilder类包含很多方法,可以很方便地构造表示不同时间点的java.util.Date实例(如定义下一个小时为偶数的时间点,如果当前时间为9:43:27,则定义的时间为10:00:00)。
Job和Trigger
Job接口:
一个job就是一个实现了Job接口的类
package org.quartz;public interface Job {public void execute(JobExecutionContext context)throws JobExecutionException;}
当Job的一个trigger被触发(稍后会讲到)时,execute()方法由调度程序的一个工作线程调用。传递给execute()方法的JobExecutionContext对象向作业实例提供有关其“运行时”环job的一个trigger被触发后(稍后会讲到),execute()方法会被scheduler的一个工作线程调用;传递给execute()方法的JobExecutionContext对象中保存着该job运行时的一些信息 ,执行job的scheduler的引用,触发job的trigger的引用,JobDetail对象引用,以及一些其它信息。
JobDetail对象是在将job加入scheduler时,由客户端程序(你的程序)创建的。它包含job的各种属性设置,以及用于存储job实例状态信息的JobDataMap。
Trigger用于触发Job的执行。当你准备调度一个job时,你创建一个Trigger的实例,然后设置调度相关的属性。Trigger也有一个相关联的JobDataMap,用于给Job传递一些触发相关的参数。Quartz自带了各种不同类型的Trigger,最常用的主要是SimpleTrigger和CronTrigger。
SimpleTrigger主要用于一次性执行的Job(只在某个特定的时间点执行一次),或者Job在特定的时间点执行,重复执行N次,每次执行间隔T个时间单位。CronTrigger在基于日历的调度上非常有用,如“每个星期五的正午”,或者“每月的第十天的上午10:15”等。
为什么既有Job,又有Trigger呢?很多任务调度器并不区分Job和Trigger。有些调度器只是简单地通过一个执行时间和一些job标识符来定义一个Job;其它的一些调度器将Quartz的Job和Trigger对象合二为一。在开发Quartz的时候,我们认为将调度和要调度的任务分离是合理的。在我们看来,这可以带来很多好处。
Key(设置后便于管理)
将Job和Trigger注册到Scheduler时,可以为它们设置key,配置其身份属性。Job和Trigger的key(JobKey和TriggerKey)可以用于将Job和Trigger放到不同的分组(group)里,然后基于分组进行操作。同一个分组下的Job或Trigger的名称必须唯一,即一个Job或Trigger的key由名称(name)和分组(group)组成。
Job的特点、Job接口的execute方法:
你定义了一个实现Job接口的类,这个类仅仅表明该job需要完成什么类型的任务,除此之外,Quartz还需要知道该Job实例所包含的属性;这将由JobDetail类来完成。
JobDetail实例:
JobDetail实例是通过JobBuilder类创建的,导入该类下的所有静态方法,会让你编码时有DSL的感觉:
现在考虑这样定义的作业类“HelloJob”:
public class HelloJob implements Job {public HelloJob() {
}public void execute(JobExecutionContext context)throws JobExecutionException
{System.err.println("Hello! HelloJob is executing.");
}}
可以看到,我们传给scheduler一个JobDetail实例,因为我们在创建JobDetail时,将要执行的job的类名传给了JobDetail,所以scheduler就知道了要执行何种类型的job;每次当scheduler执行job时,在调用其execute(…)方法之前会创建该类的一个新的实例;执行完毕,对该实例的引用就被丢弃了,实例会被垃圾回收;这种执行策略带来的一个后果是,job必须有一个无参的构造函数(当使用默认的JobFactory时);另一个后果是,在job类中,不应该定义有状态的数据属性,因为在job的多次执行中,这些属性的值不会保留。
那么如何给job实例增加属性或配置呢?如何在job的多次执行中,跟踪job的状态呢?答案就是:JobDataMap,JobDetail对象的一部分。
在Job执行时,JobExecutionContext中的JobDataMap为我们提供了很多的便利。它是JobDetail中的JobDataMap和Trigger中的JobDataMap的并集,但是如果存在相同的数据,则后者会覆盖前者的值。
如果你希望使用JobFactory实现数据的自动“注入”,则示例代码为:(个人比较推荐何种方式)
public class DumbJob implements Job {String jobSays;
float myFloatValue;
ArrayList state;public DumbJob() {
}public void execute(JobExecutionContext context)throws JobExecutionException
{JobKey key = context.getJobDetail().getKey();JobDataMap dataMap = context.getMergedJobDataMap(); // Note the difference from the previous examplestate.add(new Date());System.err.println("Instance " + key + " of DumbJob says: " + jobSays + ", and val is: " + myFloatValue);
}public void setJobSays(String jobSays) {this.jobSays = jobSays;
}public void setMyFloatValue(float myFloatValue) {myFloatValue = myFloatValue;
}public void setState(ArrayList state) {state = state;
}}
你也许发现,整体上看代码更多了,但是execute()方法中的代码更简洁了。而且,虽然代码更多了,但如果你的IDE可以自动生成setter方法,你就不需要写代码调用相应的方法从JobDataMap中获取数据了,所以你实际需要编写的代码更少了。当前,如何选择,由你决定。
Job实例(这个概念很重要)
很多用户对于Job实例到底由什么构成感到很迷惑。我们在这里解释一下,并在接下来的小节介绍job状态和并发。
你可以只创建一个job类,然后创建多个与该job关联的JobDetail实例,每一个实例都有自己的属性集和JobDataMap,最后,将所有的实例都加到scheduler中。
比如,你创建了一个实现Job接口的类“SalesReportJob”。该job需要一个参数(通过JobdataMap传入),表示负责该销售报告的销售员的名字。因此,你可以创建该job的多个实例(JobDetail),比如“SalesReportForJoe”、“SalesReportForMike”,将“joe”和“mike”作为JobDataMap的数据传给对应的job实例。
当一个trigger被触发时,与之关联的JobDetail实例会被加载,JobDetail引用的job类通过配置在Scheduler上的JobFactory进行初始化。默认的JobFactory实现,仅仅是调用job类的newInstance()方法,然后尝试调用JobDataMap中的key的setter方法。你也可以创建自己的JobFactory实现,比如让你的IOC或DI容器可以创建/初始化job实例。
在Quartz的描述语言中,我们将保存后的JobDetail称为“job定义”或者“JobDetail实例”,将一个正在执行的job称为“job实例”或者“job定义的实例”。当我们使用“job”时,一般指代的是job定义,或者JobDetail;当我们提到实现Job接口的类时,通常使用“job类”。
Job状态与并发
关于job的状态数据(即JobDataMap)和并发性,还有一些地方需要注意。在job类上可以加入一些注解,这些注解会影响job的状态和并发性。
@DisallowConcurrentExecution:将该注解加到job类上,告诉Quartz不要并发地执行同一个job定义(这里指特定的job类)的多个实例。请注意这里的用词。拿前一小节的例子来说,如果“SalesReportJob”类上有该注解,则同一时刻仅允许执行一个“SalesReportForJoe”实例,但可以并发地执行“SalesReportForMike”类的一个实例。所以该限制是针对JobDetail的,而不是job类的。但是我们认为(在设计Quartz的时候)应该将该注解放在job类上,因为job类的改变经常会导致其行为发生变化。
@PersistJobDataAfterExecution:将该注解加在job类上,告诉Quartz在成功执行了job类的execute方法后(没有发生任何异常),更新JobDetail中JobDataMap的数据,使得该job(即JobDetail)在下一次执行的时候,JobDataMap中是更新后的数据,而不是更新前的旧数据。和 @DisallowConcurrentExecution注解一样,尽管注解是加在job类上的,但其限制作用是针对job实例的,而不是job类的。由job类来承载注解,是因为job类的内容经常会影响其行为状态(比如,job类的execute方法需要显式地“理解”其”状态“)。
如果你使用了@PersistJobDataAfterExecution注解,我们强烈建议你同时使用@DisallowConcurrentExecution注解,因为当同一个job(JobDetail)的两个实例被并发执行时,由于竞争,JobDataMap中存储的数据很可能是不确定的。
Quartz中Triggers介绍
最常用的两种trigger:SimpleTrigger和CronTrigger。
Trigger的公共属性
所有类型的trigger都有TriggerKey这个属性,表示trigger的身份;除此之外,trigger还有很多其它的公共属性。这些属性,在构建trigger的时候可以通过TriggerBuilder设置。
trigger的公共属性有:
jobKey属性:当trigger触发时被执行的job的身份;
startTime属性:设置trigger第一次触发的时间;该属性的值是java.util.Date类型,表示某个指定的时间点;有些类型的trigger,会在设置的startTime时立即触发,有些类型的trigger,表示其触发是在startTime之后开始生效。比如,现在是1月份,你设置了一个trigger–“在每个月的第5天执行”,然后你将startTime属性设置为4月1号,则该trigger第一次触发会是在几个月以后了(即4月5号)。
endTime属性:表示trigger失效的时间点。比如,”每月第5天执行”的trigger,如果其endTime是7月1号,则其最后一次执行时间是6月5号。
SimpleTrigger可以满足的调度需求是:在具体的时间点执行一次,或者在具体的时间点执行,并且以指定的间隔重复执行若干次。比如,你有一个trigger,你可以设置它在2015年1月13日的上午11:23:54准时触发,或者在这个时间点触发,并且每隔2秒触发一次,一共重复5次。
指定时间开始触发,不重复:
SimpleTrigger trigger = (SimpleTrigger) newTrigger() .withIdentity("trigger1", "group1").startAt(myStartTime) // some Date .forJob("job1", "group1") // identify job with name, group strings.build();
指定时间触发,每隔10秒执行一次,重复10次:
trigger = newTrigger().withIdentity("trigger3", "group1").startAt(myTimeToStartFiring) // if a start time is not given (if this line were omitted), "now" is implied.withSchedule(simpleSchedule().withIntervalInSeconds(10).withRepeatCount(10)) // note that 10 repeats will give a total of 11 firings.forJob(myJob) // identify job with handle to its JobDetail itself .build();
5分钟以后开始触发,仅执行一次:
trigger = (SimpleTrigger) newTrigger() .withIdentity("trigger5", "group1").startAt(futureDate(5, IntervalUnit.MINUTE)) // use DateBuilder to create a date in the future.forJob(myJobKey) // identify job with its JobKey.build();
立即触发,每个5分钟执行一次,直到22:00:
trigger = newTrigger().withIdentity("trigger7", "group1").withSchedule(simpleSchedule().withIntervalInMinutes(5).repeatForever()).endAt(dateOf(22, 0, 0)).build();
建立一个触发器,将在下一个小时的整点触发,然后每2小时重复一次:
trigger = newTrigger().withIdentity("trigger8") // because group is not specified, "trigger8" will be in the default group.startAt(evenHourDate(null)) // get the next even-hour (minutes and seconds zero ("00:00")).withSchedule(simpleSchedule().withIntervalInHours(2).repeatForever())// note that in this example, 'forJob(..)' is not called which is valid // if the trigger is passed to the scheduler along with the job .build();scheduler.scheduleJob(trigger, job);
CronTrigger通常比Simple Trigger更有用,如果您需要基于日历的概念而不是按照SimpleTrigger的精确指定间隔进行重新启动的作业启动计划。
使用CronTrigger,您可以指定号时间表,例如“每周五中午”或“每个工作日和上午9:30”,甚至“每周一至周五上午9:00至10点之间每5分钟”和1月份的星期五“。
即使如此,和SimpleTrigger一样,CronTrigger有一个startTime,它指定何时生效,以及一个(可选的)endTime,用于指定何时停止计划。
建立一个触发器,每隔一分钟,每天上午8点至下午5点之间:
trigger = newTrigger()
.withIdentity("trigger3", "group1")
.withSchedule(cronSchedule("0 0/2 8-17 * * ?"))
.forJob("myJob", "group1")
.build();
建立一个触发器,将在上午10:42每天发射:
trigger = newTrigger()
.withIdentity("trigger3", "group1")
.withSchedule(dailyAtHourAndMinute(10, 42))
.forJob(myJobKey)
.build();
或者:
trigger = newTrigger()
.withIdentity("trigger3", "group1")
.withSchedule(cronSchedule("0 42 10 * * ?"))
.forJob(myJobKey)
.build();
建立一个触发器,将在星期三上午10:42在TimeZone(系统默认值)之外触发:
trigger = newTrigger()
.withIdentity("trigger3", "group1")
.withSchedule(weeklyOnDayAndHourAndMinute(DateBuilder.WEDNESDAY, 10, 42))
.forJob(myJobKey)
.inTimeZone(TimeZone.getTimeZone("America/Los_Angeles"))
.build();
或者:
trigger = newTrigger()
.withIdentity("trigger3", "group1")
.withSchedule(cronSchedule("0 42 10 ? * WED"))
.inTimeZone(TimeZone.getTimeZone("America/Los_Angeles"))
.forJob(myJobKey)
.build();
TriggerListeners和JobListeners:
Listeners是您创建的对象,用于根据调度程序中发生的事件执行操作。您可能猜到,TriggerListeners接收到与触发器(trigger)相关的事件,JobListeners 接收与jobs相关的事件。
与触发相关的事件包括:触发器触发,触发失灵(在本文档的“触发器”部分中讨论),并触发完成(触发器关闭的作业完成)。
org.quartz.TriggerListener接口public interface TriggerListener {public String getName();public void triggerFired(Trigger trigger, JobExecutionContext context);public boolean vetoJobExecution(Trigger trigger, JobExecutionContext context);public void triggerMisfired(Trigger trigger);public void triggerComplete(Trigger trigger, JobExecutionContext context,int triggerInstructionCode);
}
job相关事件包括:job即将执行的通知,以及job完成执行时的通知。
org.quartz.JobListener接口public interface JobListener {public String getName();public void jobToBeExecuted(JobExecutionContext context);public void jobExecutionVetoed(JobExecutionContext context);public void jobWasExecuted(JobExecutionContext context,JobExecutionException jobException);}
SchedulerListeners:
SchedulerListeners非常类似于TriggerListeners和JobListeners,除了它们在Scheduler本身中接收到事件的通知 - 不一定与特定触发器(trigger)或job相关的事件。
与计划程序相关的事件包括:添加job/触发器,删除job/触发器,调度程序中的严重错误,关闭调度程序的通知等
quartz的架构是模块化的,因此要运行几个组件需要“快速”在一起。幸运的是,有一些帮手出现这种情况。
在Quartz可以完成其工作之前需要配置的主要组件有:
线程池
JobStore
DataSources(如有必要)
计划程序本身
StdSchedulerFactory
StdSchedulerFactory是org.quartz.SchedulerFactory接口的一个实现。它使用一组属性(java.util.Properties)来创建和初始化Quartz Scheduler。属性通常存储在文件中并从文件中加载,但也可以由程序创建并直接传递到工厂。简单地调用工厂中的getScheduler()将生成调度程序,并初始化它(和它的ThreadPool,JobStore和DataSources)并返回一个句柄到它的公共接口。
Quartz高级(企业)功能
Clustering
Clustering目前与JDBC-Jobstore(JobStoreTX或JobStoreCMT)和TerracottaJobStore一起使用。功能包括负载平衡和 job故障转移(如果JobDetail的“请求恢复”标志设置为true)。
####使用JobStoreTX或JobStoreCMT进行聚类通过将“org.quartz.jobStore.isClustered”属性设置为“true”来启用Clustering。Clustering中的每个实例都应该使用相同的quartz.properties文件。这样做的例外是使用相同的属性文件,具有以下允许的异常:不同的线程池大小,以及“org.quartz.scheduler.instanceId”属性的不同值。Clustering中的每个节点必须具有唯一的instanceId,通过将“AUTO”作为此属性的值,可以轻松完成(不需要不同的属性文件)。
不要在单独的机器上运行Clustering,除非它们的时钟使用某种形式的时间同步服务(守护进程)进行同步,而这些时间同步服务(守护进程)运行非常有限(时钟必须在彼此之间)。 如果您不熟悉如何执行此操作, 请参阅http://www.boulder.nist.gov/timefreq/service/its.htm。
不要针对任何其他实例运行的相同的一组表来启动非群集实例。您可能会收到严重的数据损坏,一定会遇到不正常的行为。
每次触发只能有一个节点有效。我的意思是,如果job有一个重复的trigger,告诉它每10秒钟发射一次,那么在12:00:00,正好一个节点将运行这个job,在12:00:10,一个节点将运行job等。它不一定是每次相同的节点 - 它或多或少是随机的,哪个节点运行它。负载平衡机制对于繁忙的调度程序(大量的trigger)来说是近乎随机的,但是有利于于non-busy(例如一个或两个trigger)调度程序活动的同一个节点。