BOS金蝶后台事务开发文档
- 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
- 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
- 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。
后台事务开发文档
目录
1.简单后台事务示例 (3)
2.后台事务执行过程及原理 (13)
2.1.后台事务定义 (13)
2.2.后台事务发布 (14)
2.3.后台事务执行 (15)
3.代码实现后台事务定义 (22)
3.1.非持久化后台事务 (22)
3.2.错过策略 (25)
3.3.人工干预(撤销,挂起,唤醒) (28)
3.4.调度计划时间 (33)
1.简单后台事务示例
在进行后台事务示例测试之前,我们需要做一些准备工作,首先,我们要在BOS Studio的BIM视图下新建几个元数据,如图1所示:
如上图所示,新建了四个元数据(其中T_BO_BandOffice.table是根据实体右键导出表直接导出得到的),其实这四个元数据很简单,FileLogFacade.facade只有一个方法logWriter(String str);该方法实现了向服务器上的C:/file.log输入日志。
BandOffice.entity上新建了一个方法,该方法和FileLogFacade.facade的方法logWriter(String str)作用一样,名字为testLog(String str),只是为了示范两个不同的调用。
实体BandOffice.entity还新建了一个TestLogEvent的事件,该事件可以引用实体本身的方法logWriter,也可以引用功能FileLogFacade的testLog方法。
再定义了一个业务功能FileLogFunction,在这个业务功能中定义了一个操作fileLog;通过应用,与实体上的事件关联起来。
在定义好这些元数据后,我们需要把这些元数据进行发布,使服务器端在运行时能够加载这些元数据。
上面是我们对解决方案的发布方案设置,如红框标识的,我把元数据发布后生成的代码放在W:\workspace\bs_job\dev\test目录中,如下图所示,元数据发布后在这个目录下生成的代码,这是实体和功能(facade)发布时生成的代码,其他元数据发布时不生成代码。
在FileLogFacadeControllerBean中会生成功能定义的_logWriter方法,通过在这个方法中写逻辑即可实现,而实体定义的方法会在BandOfficeControllerBean中生成_testLog方法。
元数据发布后,我们需要重新生成系统子树,这样我们在重启服务器后可以在事务任务选择树中看到我们定义的功能。
具体生成系统子树的方法,请参照《生成系统子树的两种方法.doc》。
登陆EAS后,我们选择系统平台——>后台事务定义,进入后台事务定义界面:
接着我们就按要求进行定义,下面我们定义调度计划设置:
定义好后,我们进行保存,这个时候我们可以进行发布了,如下图:
我们再进入:系统平台——>后台事务定义表,我们可以看到我们刚才定义的那个事务了我们设置好了调度计划,当时间到达时,触发该事务,就会执行一个事务实例,我们可以查
看该事务实例,我们也可以直接点击工具栏中的启动,这样会立刻启动一个事务实例。
从上面可以看出,事务执行成功。
我们再来看看我们实现的功能,就是在服务器目录下写入一条时间信息记录:
2.后台事务执行过程及原理
首先我们要弄清楚,在EAS系统的后台事务管理平台中定义一个后台事务的过程是怎样的,你可以通过对代码的跟踪进行了解,
2.1.后台事务定义
首先我们在后台事务定义界面上新建一个后台事务(如下图所示),这些后台事务定义信息会被保存在T_WFD_PROCESSDEF中,可以在这个界面对已经定义好的后台事务定义进行修改和删除,当我们选择左边系统子树的某个后台事务定义节点,它对应的信息就会从该表中获得并显示在右边的界面上。
现在我想描述一下右边这颗系统子树的生成过程。
这可系统子树是在该界面的构造函数的initializeUI()方法中进行初始化的
final BusinessTreeUtil tempPTU = new BusinessTreeUtil(
BusinessTreeUtil.PROCESS);
((KingdeeTreeModel) this.packagekDTree.getModel()).setRoot(null);
JobUtils.resetStartTimeMillis();
tempPTU.initialBusinessTreeView(this.packagekDTree,true);
JobUtils.printTimeMillis("initialBusinessTreeView");
this.packagekDTree.addTreeSelectionListener(new
InnerTreeSelectionListener());
BusinessTreeUtil中有三个静态变量,分别为PROCESS,FUNCTION,ENTITY,分别用来生成不同的系统子树,在对系统子树进行初始化调用了两个方法,第一个是initialPackageTree(tree ,flag);这个方法是获得标准包结构的树,第二个是initalFunctionOrJobProcessTree(tree,flag);这个是获得客户化的树形结构,通过该方法可以在标准化得包结构下添加功能节点或后台事务定义节点。
如果是生成功能(function)子树或实体(entity)子树,这就得获取这些元数据的集合,而它们对应的集合类:FunctionObjectCollection和EntityObjectCollection,获取这些集合的方法为:
IMetaDataLoader loader = MetaDataLoaderFactory.
getRemoteMetaDataLoader()
FunctionObjectCollection funcol = loader.getFunctions()
EntityObjectCollection entcol = loader.getEntityCollection();
2.2.后台事务发布
后台事务定义完成以后,通过发布,我们就可以在后台事务定义报表看到我们定义的后台事务了,已发布的后台事务不允许进行删除。
public void release(ProcessDef process) throws BOSException { JobDef def=JobServiceUtil.toJobDef(ctx, process);
Trigger trigger=JobServiceUtil.toTrigger(def, process);
IJobService svc=JobServiceFactory.getLocalInstance(ctx);
svc.createJobDef(def, trigger, true);
}
后台事务定义被发布以后,产生了一个新的对象JobDef,对用在数据库中的表是T_JOB_DEF。
后台事务实例(Job)就是根据这个定义来产生的。
由上面的代码可知,后台事务定义(JobDef)和触发器(Trigger)是由流程定义(ProcessDef)转换过来的,这是因为在以前的后台事务是按照工作流的方式进行处理的,每一个后台事务定义都会生成一个流程定义保存在数据库表(T_WFD_PROCESSDEF)的一个字段中,我们用代码实现后台事务定义就不必通过这样获取流程定义(JobDef)。
有上面代码可知,通过调用JobService的createJobDef(JobDef def, Trigger trigger, boolean enable);方法创建后台事务定义(实际上是通过反射调用了JobManager的createJobDef(JobDef def, Trigger trigger, boolean enable)方法),通过该方法将JobDef,Trigger对象保存到数据库中的T_JOB_DEF和T_TRIGGER表中。
从而完成了后台事务定义的发布功能。
2.3.后台事务执行
后台事务每执行一次就会产出一个后台事务实例(Job),持久化后台事务会将后台事务实例信息保存在T_JOB_INST表中。
我们可以通过两种方式来触发后台事务执行,第一种是通过后台事务界面上的“启动”按钮直接触发产生一个后台事务实例,第二种是通过定义一个触发器(Trigger)来与该后台事务定义关联起来,用触发器来触发产生一个后台事务实例。
下面我们分别来讨论通过这两种方法是怎样触发产生后台事务实例的。
“启动”按钮触发后台事务。
当我们点击“启动”按钮会触发该按钮的事件,会调用到该界面JobProcessDefineListUI的actionStart_actionPerformed方法,通过方法调用,我们可以知道它调用了JobServiceUIFacade的createJobInstance(jobDefId, null, null),在这个类中,通过获取到了IjobService接口,调用了jobservice的createJobInstance方法;
public String createJobInstance(String jobDefId,
Object param, Timestamp scheduledTime) throws BOSException { JobDef def=JobDefCache.getJobDef(ctx.getAIS(), jobDefId);
if(def==null)throw new BOSException("job def doesn't exist, its id is {"+jobDefId+"}, datacenter: "+ctx.getAIS());
if(scheduledTime==null)
scheduledTime=new Timestamp(System.currentTimeMillis()+1000);
Job job=def.newInstance(ctx, param, scheduledTime);
manager.add(job);
return job.getTitle();
}
从上面这个方法可以看出,首先由jobDefId获取到JobDef对象def,在设置这个产生的任务实例的计划执行时间为当前时间的下一秒。
然后由后台事务定义对象def的newInstance(ctx, param, scheduledTime)产生一个后台事务实例对象job,再由后台事务管理器JobManager(单子模式)调用add(job)方法把这个任务实例加载到等待队列或者就需队列中去;
触发器触发后台事务。
如果我们在后台事务定义的时候对其关联了相应的触发器,则当下一次触发时间到达时,触发器就会触发与之关联的后台事务定义(JobDef)产生一个后台事务实例(Job)。
触发器触发后台事务的过程是怎样的呢?首先:服务器启动时会初始化触发器加载类(TriggerLoader),该类实现了IJobHandler, IcoreJobHandler两个接口,本身就是一个任务处理器,这个类的作用是:触发器加载者,定期从数据库加载新增的触发器。
其load()方法会定期被调用执行
/**加载触发器*/
private int load()throws Exception{
StringBuffer sql=new StringBuffer();
sql.append("select * from T_JOB_TRIGGER where fisvalid='Y' AND fisAutoLoad='Y' AND ");
sql.append("(").append(TriggerIsolationLevel.sqlWhere(dc)) .append(")");//符合隔离边界
sql.append(" and fholderid is null");//无持有者
//只加载已生效的触发器
sql.append(" and fvalidateTime<=?");
int[] types=new int[]{Types.TIMESTAMP};
Timestamp now=new Timestamp(System.currentTimeMillis());
Object[] values=new Object[]{now};
ArrayList items=SQL.executeQuery(dc, sql.toString(), types,
values);
Trigger tr;
for(int i=0; i<items.size();i++){
tr=null;
try{
tr=Trigger.from(dc, (HashMap)items.get(i));
PulseSource ps=tr.getPulseSource();
if(ps==null){
throw new BOSException
("couldn't find pulse source for this trigger!");
}
ps.addTrigger(tr);
}catch(Throwable t){。
(省略)
}
}。
(省略)
//触发器加载间隔为任务加载间隔的5倍
return Configuration.persistantJobCheckInterval()*5;
}
这个方法实现了从数据库表T_JOB_TRIGGER中加载新添加的触发器,并返回触发器加载时间间隔(上面设置的是为持久化任务加载间隔的5倍)
protected synchronized void addTrigger(Trigger tr)
throws Exception{
TimerPulse pulse=new TimerPulse(tr);
if(pulse.nextFireTime()==null){
tr.inValid();//禁用TRIGGER
throw new ng.IllegalArgumentException("trigger {"+tr.getKey()+"} has no fire time!");
}
if(pulse.setLoaded())add(pulse);
}
TimerPulseSource的protected synchronized void addTrigger(Trigger tr)方法是通过一个触发器新建一个时间触发发生器(TimerPulse),如果获取不到这个TimerPulse下一次触发时间,就禁用这个触发器(Trigger),否则设置这个TimerPulse对应的触发器(Trigger)的状态是被加载的,如果设置成功,则把这个时间触发发生器(TimerPulse)加入到触发源(TimerPulseSource)中用来放置TimerPulse的堆结构(Heap)中去。
当时间出发发生器(TimerPulse)放到Heap中去以后(会根据下一次触发事件进行排序),当下一次触发时间快到的时候,我们就需要把它从Heap中取出来,去生成其对应的触发器关联的后台事务实例(Job)。
这是通过TimerHandler类来实现的,这个类也实现IJobHandler, IcoreJobHandler接口,是专门用来处理时间触发发生器的,在服务器启动初始化TimerPulseSource 的时候,就新建了一个后台事务实例,并添加到了就绪队列或等待队列中去了。
JobInstanceConfig cfg=new JobInstanceConfig(new TimerHandler(this), false, true, 0);
Job job=new Job(null,"Timer pulse source!", cfg, Boolean.TRUE);
J obManager.instance().add(job);
而且这个任务每隔一秒会被执行一次,因为execute方法返回值为Delay(1)。
而且在execute 方法中调用的是schedule()方法。
private void schedule(){
Date now=new Timestamp(System.currentTimeMillis()
+Configuration.persistantJobCheckInterval()*1000);
while(true){
TimerPulse pulse=pulseSource.top();
if(pulse==null)return;
if(pulse.nextFireTime().after(now))return;
pulse=pulseSource.pop();
if(pulse.fire())
pulseSource.add(pulse);
}
}
该方法的作用是:首先设定一个时间,该时间是距离现在还有一个持久化任务加载间隔时间的未来时间,先读取堆中的堆顶元素,看下一次触发时间是否晚于设定的那个时间,如果是,就直接返回,如果不是,就把堆顶元素pop出来,并触发时间触发发生器(TimerPulse),即调用了fire()方法。
p ublic boolean fire(){
if(!trigger.triggered(nextFireTime))
return false;//触发失败,触发发生器应失效
lastTriggeredTime=nextFireTime;
nextFireTime=quartzTrigger.getFireTimeAfter(lastTriggeredTime);
boolean b=nextFireTime!=null;
if(!b)trigger.inValid();//以后不需触发,触发发生器应失效
return b;
}
public boolean triggered(Date lastTriggeredTime){
try{
Job job=newJob(lastTriggeredTime);
if(!modifyState(lastTriggeredTime))return false;
JobManager.instance().add(job);
return true;
}catch(Throwable t){。
(省略)
}
}
从上面两个方法知,TimerPulse雷中的fire()方法调用了Trigger类中triggered(Date lastTriggeredTime)方法,在triggered方法中,由下一次触发时间得到了这个触发器关联的后台事务实例对象(Job),modifyState()方法是同步数据库,更新该触发器的触发次数和最后一次触发时间等信息。
如果更新成功则把得到的后台事务实例加入到等待或就绪队列中去。
fire()在执行完triggered方法后,会更新一下自身(TimerPulse)的最后一次触发时间和下一次触发时间。
如果fire()方法返回值为true,则把这个更新好的TimerPulse又放入到堆(Heap)结构中去,等待下一次调度。
注意:在使用任务管理器JobManage提交后台事务实例(Job)中,会调用job.setState(JobState.Created)语句,该语句会根据后台事务实例是否是可持久化事物进行判断,如果是持久化任务就把它保存到数据库中去,并设置为“创建”的状态,如果是非持久化任务就直接设置其状态为“创建”,不做其他任何操作。
接着再一次进行判断,如果是持久化任务则返回,因为持久化任务实例已经保存到数据库中去了,此时需要通过任务加载器(JobLoader)从数据库进行加载。
非持久化任务就可以被加入到等待或就绪队列中去了。
if(!job.setState(JobState.Created))。
(省略)
//持久化任务由任务加载器从数据库加载
i f(job.isPersistent())return;
if(job.getScheduledTime().after(new Date())){
b=WaitingJobs.in(job);
}else{
b=ReadyJobs.in(job);
}
持久化任务加载器(JobLoader)实现了IJobHandler,IcoreJobHandler接口,该类在启动服务器的时候就被初始化了,开启了一个加载后台事务实例加载事务,该事务会不行地执行,因为该任务处理器对应的execuse()方法返回值为new Delay(t);在execuse()方法中调用了load()方法,该方法就是从数据库表T_JOB_INST中取得计划执行时间快到得后台事务实例(Job)。
并直接放到等待队列或就绪队列中去。
到现在为止,后台事务实例(Job)已经都已经放到后台事务等待队列或就绪队列中去了,还要完成的工作就是按照后台事务实例的计划执行时间(scheduledTime)来从就绪队列中取出,并执行这个后台事务实例(Job)了。
处理这项工作的是负责后台事务执行的工作线程类ThreadWorker,在服务器启动的时候会进行初始化,新建配置文件上指定个数的工作线程,然后调用start方法启动这些线程。
下面是线程对象的run()方法。
p ublic void run() {
Job job;
boolean executed;
while(true){
//服务停止则退出线程
if(!BOSSchedulerService.isRunning())break;
job=null;
executed=false;
try{
//获取下一个任务
job=ReadyJobs.out();
if(!RunningJobs.put(job)){
job=null;
continue;
}
//检查是否错过最后执行期限
Date e=job.getExpiredTime();
if(e!=null){
if(System.currentTimeMillis()>e.getTime()){
job.setState(JobState.Missed);
continue;
}
}
//执行
executed=execute(job);
}catch(Throwable t){
log.error("execute job "+job+" failed!",t);
}finally{
//释放任务
RunningJobs.dispose(job);
}
if(executed){//如果任务被成功执行
try{
//调整任务定义属性
adjustJobDef(job);
//执行后处理
handleReturns(job);
}catch(Throwable t){
log.error("handler job "+job+"'s returns failed!",t);
}
}
}
}
从这个方法可以看出,首先从就绪队列中取出一个任务,然后放入到放到运行时任务中,检查是否错过最后期限,这个值跟错过策略设置有关,如果设置为错过“立刻执行”,则这个值为null,如果为“忽略不计”则是在程序中设置的一个时间。
如果设置为“忽略不计”,且执行最后期限已过,则设置为“已错过”的状态。
接着就执行这个任务,在这个过程中要完成设置任务执行状态,触发一些事件,以及调用该任务的任务处理器对该任务进行处理。
任务执行完以后,需要释放这个任务,并且调整任务定义的属性和执行一些后续处理。
这样,就整个任务就执行完毕了。
对持久化后台事务会产生一个后台事务实例,我们可以通过“后台事务实例”按钮进行查看,执行失败的后台事务实例会产生日志信息,我们通过日志信息可以很快定位到执行后台事务失败的原因。
3.代码实现后台事务定义
3.1.非持久化后台事务
在登陆EAS系统后,进入后台事务管理,在台事务定义的界面上我们是不能选择是定义持久化事务和非持久化事务的,通过在EAS系统中定义的后台事务默认都是持久化事务的。
如果我们要定义非持久化后台事务,就必须通过代码来实现。
通过对前一章的学习,我们已经熟悉后台事务执行的过程,我们可以在服务器端程序代码中新建一个后台事务定义(JobDef)的对象,再调用JobService的createJobDef(JobDef def)方法就可以创建一个后台事务定义了。
下面是我在后台事务客户端Façade的JobServiceUIFacade类中定义的一个方法。
其中TestJob是一个任务处理类,实现了IJobHandler接口,可以通过在这个类中的execute()方法中实现自己的测试业务逻辑。
public void createLogJobDef() throws BOSException{
String packageName = null;
String title= "后台事务日志记录测试";
String description = "测试要求";
String proxyUserID=null;
String proxyOrgID=null;
boolean isPersistent=false;
Wrapper wrapper=new InterfaceWrapper(new TestJob());
JobDef def=new JobDef(ctx,packageName, title, description,
proxyUserID, proxyOrgID, isPersistent, wrapper);
JobServiceFactory.getLocalInstance(ctx).createJobDef(def);
}
通过设置其isPersistent属性为false,这样创建的后台事务定义是一个非持久化后台事务定义。
调用了JobService的createJobDef(def)方法,而这个方法又是通过反射调用了JobManager的createJobDef(JobDef def, Trigger trigger, boolean enable)方法,在这个方法会调用def.save(),即对我定义的后台事务定义JobDef进行数据库保存。
这里面的trigger为空,即该后台事务没有关联触发器。
我们需要执行这个方法,使之产生的后台事务定义可以在后台事务定义报表中显示出来,在这里,我是放在这个类的testJobDef 方法中让它执行的。
public String testJobDef(ProcessDef process) throws BOSException { JobDef def=JobServiceUtil.toJobDef(ctx, process);
createLogJobDef();//测试日志记录后台事务
return JobServiceFactory.getLocalInstance(ctx).testJobDef(def, null);
}
这个方法关联到后台事务定义界面上的“测试”按钮,所以只要点击那个按钮,就会执行我这个方法,从而产生一个后台事务定义对象并保存到数据库中,我们就可以在后台事务定义报表中看到我们定义的这条记录。
由于这个后台事务没有关联触发器,所以要通过点击“启动”按钮触发后台事务,提示后台事务启动成功后,点“后台事务实例”按钮,可以看到不会产生任务实例。
这个后台事务完成的工作仍然是向D盘目录下的fileLog.txt文件中写时间,在测试之前可以先删除这个文件,测试后如果产生这个文件并记录下了当前时间则说明执行成功。
3.2.错过策略
在后台事务定义的时候,可以进行设置后台事务的错过策略,有两种情况,一种是“立即执行”,另一种是“忽略不计”,其中这两种是怎样工作的呢?
在后台事务定义的时候,后台事务错过策略信息是保存在T_JOB_DEF的fmissedtimeout字段中,设置后台事务的错过策略为立即执行,这个字段保存的值为-1,设置为忽略不计,这个字段保存的值为0;在代码中是如何设置其错过的策略呢?通过下面这段代码你就可以明白系统是怎样进行处理的。
if(scheduledTime==null)scheduledTime=new Timestamp(System.currentTimeMillis());
if(expiredTime==null){
int timeout=this.missedTimeout;
int t=Configuration.persistantJobCheckInterval()*10;
if(timeout==0){//默认错过超时, 取任务加载间隔的10倍, 且该值不得小于180秒timeout=t;
if(timeout<180)timeout=180;
}
if(timeout>0){
if(timeout<t)timeout=t;
expiredTime=new Timestamp(scheduledTime.getTime()+timeout*1000);
}
}
从上面可以看出,如果计划执行时间为空,则设置计划执行时间为当前时间,如果过期时间为空,则首先判断JobDef的missedTimeout是否为0,如果为零,表示忽略不计,对忽略不计这种情况,系统会默认设置它的过期时间,如果超过这个过期时间,就设置这个后台事务实例的状态为“已错过”,对于立即执行这种情况,就不会去设置它的过期时间,expiredTime这个值为null,表示永不过期。
在后台事务执行的时候,会检查后台事务实例的expiredTime值,如果不为空且现在时间已经过了这个时间就会设置其状态为“已错过”。
其判断代码如下(在ThreadWorker的run()方法中):
Date e=job.getExpiredTime();
i f(e!=null){
if(System.currentTimeMillis()>e.getTime()){
job.setState(JobState.Missed);
continue;
}
通常在多个线程同时运行的情况下,我们运行的后台事务实例不会因为阻塞而产生“已错过的情况”,如果我们需要对这种情况进行测试,这就需要我们在程序代码中进行控制,我们可以在后台任务管理器JobManager中的if(job.getScheduledTime().after(new Date()))语句上设置一个断点,阻塞其运行,待产生的后台事务实例的最后期限已经过去,再去掉断点,让它们继续运行,这样我们就可以看到我们产生的后台实例出现“已错过”的情况。
同样,我们可以进行错过策略为“立刻执行”的测试,不管我们的断点阻塞线程运行多久,只要我们一让它运行,后台事务仍然会执行,如下图所示,开始执行时间比计划执行时间晚了3分多种,总而言之,选择“立刻执行”的后台事务实例,只要让它获得执行的机会就会直接执行。
3.3.人工干预(撤销,挂起,唤醒)
对已接收,重新调度,已就绪,等待中的后台事务实例可进行撤销和挂起操作,对已挂起的后台事务实例可以进行唤醒操作,已错过的后台事务可以进行修复操作(其实质也就是重新调度),对运行中的后台事务不能够做任何操作,这些情况在实际运行中可能不是很好把握,因为从一个转台转换到另一个状态转换的时间间隔很少,所以要出现下面这些情况,最好还是通过设置断点,在不同的时机进行阻塞操作。
3.4.调度计划时间
后台事务触发器的调度计划时间表达式是保存在数据库表T_JOB_TRIGGER的fschedulePlan字段中,String schedulePlan=schedule.getType().toString()+":"+schedule.getDefine();由这条语句可知,其值由两部分组成。
分别为调度类别和调度的定义,其中调度定义是Quartz 的cron 表达式的格式,后台事务获取下一次触发时间就是根据这个表达式来得到的,首先根据这个表达式可以新建一个CronTrigger对象,通过这个对象及上一次的触发时间就可以得到下一次触发时间了:quartzTrigger.getFireTimeAfter(lastTriggeredTime)。
下面是对Quartz的cron 表达式的格式的说明:
Quartz 用cron 表达式存放执行计划。
引用了cron 表达式的CronTrigger 在计划的时间里会与job 关联上。
Quartz 提供七个域。
下表列出了Quartz cron 表达式支持的七个域。
名称是否必须允许值特殊字符
秒是0-59 , - * /
分是0-59 , - * /
时是0-23 , - * /
日是1-31 , - * ? / L W C
月是1-12 或JAN-DEC , - * /
周是1-7 或SUN-SAT , - * ? / L C #
年否空或1970-2099 , - * /
月份和星期的名称是不区分大小写的。
FRI 和fri 是一样的。
域之间有空格分隔,这和UNIX cron 一样。
无可争辩的,我们能写的最简单的表达式看起来就是这个了:* * * ? * *,这个表达会每秒钟(每分种的、每小时的、每天的)激发一个部署的job。
Quartz cron 表达式支持用特殊字符来创建更为复杂的执行计划:
*星号:使用星号(*) 指示着你想在这个域上包含所有合法的值。
例如,在月份域上使用星号意味着每个月都会触发这个trigger。
表达式样例:0 * 17 * * ?
意义:每天从下午5点到下午5:59中的每分钟激发一次trigger。
它停在下午5:59 是因为值17 在小时域上,在下午 6 点时,小时变为18 了,也就不再理会这个trigger,直到下一天的下午5点,在你希望trigger 在该域的所有有效值上被激发时使用* 字符。
问号:? 号只能用在日和周域上,但是不能在这两个域上同时使用。
你可以认为? 字符是"我并不关心在该域上是什么值。
" 这不同于星号,星号是指示着该域上的每一个值。
? 是说不为该域指定值。
不能同时这两个域上指定值的理由是难以解释甚至是难以理解的。
基本上,假定同时指定值的话,意义就会变得含混不清了:考虑一下,如果一个表达式在日域上有值11,同时在周域上指定了WED。
那么是要trigger 仅在每个月的11号,且正好又是星期三那天被激发?还是在每个星期三的11号被激发呢?要去除这种不明确性的办法就是不能同时在这两个域上指定值。
只要记住,假如你为这两域的其中一个指定了值,那就必须在另一个字值上放一个?。
表达式样例:0 10,44 14 ? 3 WEB
意义:在三月中的每个星期三的下午2:10 和下午2:44 被触发。
, 逗号:逗号(,) 是用来在给某个域上指定一个值列表的。
例如,使用值0,15,30,45 在秒域上意味着每15秒触发一个trigger。
表达式样例:0 0,15,30,45 * * * ?
意义:每刻钟触发一次trigger。
/ 斜杠:斜杠(/) 是用于时间表的递增的。
我们刚刚用了逗号来表示每15分钟的递增,但是我们也能写成这样0/15。
表达式样例:0/15 0/30 * * * ?
意义:在整点和半点时每15秒触发trigger。
- 中划线:中划线(-) 用于指定一个范围。
例如,在小时域上的3-8 意味着"3,4,5,6,7 和8 点。
" 域的值不允许回卷,所以像50-10 这样的值是不允许的。
表达式样例:0 45 3-8 ? * *
意义:在上午的3点至上午的8点的45分时触发trigger。
L 字母:L 说明了某域上允许的最后一个值。
它仅被日和周域支持。
当用在日域上,表示的是在月域上指定的月份的最后一天。
例如,当月域上指定了JAN 时,在日域上的L 会促使trigger 在1月31号被触发。
假如月域上是SEP,那么L 会预示着在9月30号触发。
换句话说,就是不管指定了哪个月,都是在相应月份的时最后一天触发trigger。
表达式样例:0 0 8 L * ?
意义:在每个月最后一天的上午8:00 触发trigger。
在月域上的* 说明是"每个月"。
当L 字母用于周域上,指示着周的最后一天,就是星期六(或者数字7)。
所以如果你需要在每个月的最后一个星期六下午的11:59 触发trigger,你可以用这样的表达式0 59 23 ? * L。
当使用于周
域上,你可以用一个数字与L 连起来表示月份的最后一个星期X。
例如,表达式0 0 12 ? * 2L 说的是在每个月的最后一个星期一触发trigger。
注意:不要让范围和列表值与L 连用,虽然你能用星期数(1-7)与L 连用,但是不允许你用一个范围值和列表值与L 连用。
这会产生不可预知的结果。
W 字母:W 字符代表着平日(Mon-Fri),并且仅能用于日域中。
它用来指定离指定日的最近的一个平日。
大部分的商业处理都是基于工作周的,所以W 字符可能是非常重要的。
例如,日域中的15W 意味着"离该月15号的最近一个平日。
" 假如15号是星期六,那么trigger 会在14号(星期四)触发,因为距15号最近的是星期一,这个例子中也会是17号(译者Unmi注:不会在17号触发的,如果是15W,可能会是在14号(15号是星期六)或者15号(15号是星期天)触发,也就是只能出现在邻近的一天,如果15号当天为平日直接就会当日执行)。
W 只能用在指定的日域为单天,不能是范围或列表值。
# 井号:# 字符仅能用于周域中。
它用于指定月份中的第几周的哪一天。
例如,如果你指定周域的值为6#3,它意思是某月的第三个周五(6=星期五,#3意味着月份中的第三周)。
另一个例子2#1 意思是某月的第一个星期一(2=星期一,#1意味着月份中的第一周)。
注意,假如你指定#5,然而月份中没有第 5 周,那么该月不会触发。