[译]Quartz.Net 框架 教程(中文版)2.2.x 之第三课 更多关于Jobs和JobDetails

第三课 更多关于Jobs和JobDetails

在这二课我们已经学习到,Jobs接口非常容易实现,只有一个execute方法。我们需要再学习一些知识去理解jobs的本质,Job接口的execute方法以及JobDetails接口。

当你实现Job接口类,Quartz需要你提供job实例的各种参数,Job接口实现类中的代码才知道如何去完成指定类型Job的具体工作。这个过程是通过JobDetail类来完成的,该类会在下一个章节作简要介绍。
        JobDetail的实例是调用JobBuilder类创建的。通常你可以使用静态导入方式获得该类所有方法的调用,这样可以在你的代码体验DSL的感觉。

 1 // define the job and tie it to our HelloJob class
 2 IJobDetail job = JobBuilder.Create<HelloJob>()
 3     .WithIdentity("myJob", "group1")
 4     .Build();
 5
 6 // Trigger the job to run now, and then every 40 seconds
 7 ITrigger trigger = TriggerBuilder.Create()
 8   .WithIdentity("myTrigger", "group1")
 9   .StartNow()
10   .WithSimpleSchedule(x => x
11       .WithIntervalInSeconds(40)
12       .RepeatForever())
13   .Build();
14
15 sched.ScheduleJob(job, trigger);

Job接口的实现类HelloJob可以这样定义

1 public class HelloJob : IJob
2 {
3     public void Execute(IJobExecutionContext context)
4     {
5         Console.WriteLine("HelloJob is executing.");
6     }
7 }

请注意我们给调度器提供了一个JobDetail实例对象,我们构建JobDetail对象时仅提供了job的class对象,调度器就知道它要执行的job类型。每次调度器执行job时,它在调用excecute方法前会创建一个新的job实例。当调用完成后,关联的job对象实例会被释放,释放的实例会被垃圾回收机制回收。这种调用过程导致的其中一个结果是jobs对象必须要有一个无参数构造器(使用默认的JobFacotry实现时),另外一个结果jobs实现类不能定义状态数据字段,因为这些状态数据字段的值在调用job任务时不会被保留。

你现在可能想问“我要怎样才能为Job实例提供配置参数?在执行任务时我要如何跟踪job对象的状态?”这两个问题的答案都一样:使用JobDataMap,它是JobDetail对象的一部分。

JobDataMap

JobDataMap可以用来装载任何可序列化的数据对象,当job实例对象被执行时这些参数对象会传递给它。JobDataMap实现IDictionary 接口,并且添加了一些非常方便的方法用来存取基本数据类型。

下面的代码片断演示了在定义/构建JobDetail对象时,job对象添加到调度器之前,如何将数据存放至JobDataMap中:

1 IJobDetail job = JobBuilder.Create<DumbJob>()
2     .WithIdentity("myJob", "group1") // name "myJob", group "group1"
3     .UsingJobData("jobSays", "Hello World!")
4     .UsingJobData("myFloatValue", 3.141f)
5     .Build();

下面的例子显示了在job执行过程中如何从JobDataMap取数据:

 1 public class DumbJob : IJob
 2 {
 3     public void Execute(JobExecutionContext context)
 4     {
 5       JobKey key = context.JobDetail.Key;
 6
 7       JobDataMap dataMap = context.JobDetail.JobDataMap;
 8
 9       string jobSays = dataMap.GetString("jobSays");
10       float myFloatValue = dataMap.GetFloat("myFloatValue");
11
12       Console.Error.WriteLine("Instance " + key + " of DumbJob says: " + jobSays + ", and val is: " + myFloatValue);
13     }
14 } 

如果你使用持久化的JobStore(将会在教程JobStore部分讨论),你应该要多考虑放在JobDataMap中的数据对象,因为此时的对象会被序列化,因此这更容易出现版本问题。显然官方版本的类很安全,但是非官方的版本,任何时候有人变更你序例化实例的类的定义,都要注意不要破坏兼容性。更多关于此主题讨论的信息都能在Java Developer Connection 技术贴士中找到:Serialization In The Real World.当然,你可以选择将JDBC-JobStore和JobDataMap设计成只有基本数据类型和String类型才允许存储的map对象,从而从根本上消除序列化问题

如果你在job类中添加setter方法对应JobDataMap的键值(例如setJobSays(String val)方法对应上面例子里的jobSays数据),Quartz框架默认的JobFactory实现类在初始化job实例对象时会自动地调用这些setter方法,从而防止在调用执行方法时需要从map对象取指定的属性值。

触发器也可以关联JobDataMap对象,当存储在调度器中的job对象需要定期/重复执行,被多个触发器共用时,这种场景下使用JobDataMap将非常方便,然而每个独立的触发器,你都可以为job对象提供不同的输入参数。

在进行任务调度时JobDataMap存储在JobExecutionContext中非常方便获取。它整合了JobDetail和Trigger里的JobDataMap数据对象,后面的对象会把前面对象相同键值对象的值覆盖。

接下来的例子展示了任务执行过程中从JobExecutionContext取合并的JobDataMap数据:

 1 public class DumbJob : IJob
 2 {
 3     public void Execute(IJobExecutionContext context)
 4     {
 5         JobKey key = context.JobDetail.Key;
 6
 7         JobDataMap dataMap = context.MergedJobDataMap;  // Note the difference from the previous example
 8
 9         string jobSays = dataMap.GetString("jobSays");
10         float myFloatValue = dataMap.GetFloat("myFloatValue");
11         IList<DateTimeOffset> state = (IList<DateTimeOffset>) dataMap["myStateData"];
12         state.Add(DateTimeOffset.UtcNow);
13
14         Console.Error.WriteLine("Instance " + key + " of DumbJob says: " + jobSays + ", and val is: " + myFloatValue);
15     }
16 }

或者如果你想在类中依赖JobFactory注入map数据,可以参照如下代码:

 1 public class DumbJob : IJob
 2 {
 3     public string JobSays { private get; set; }
 4     public float FloatValue { private get; set; }
 5
 6     public void Execute(IJobExecutionContext context)
 7     {
 8         JobKey key = context.JobDetail.Key;
 9
10         JobDataMap dataMap = context.MergedJobDataMap;  // Note the difference from the previous example
11
12         IList<DateTimeOffset> state = (IList<DateTimeOffset>) dataMap["myStateData"];
13         state.Add(DateTimeOffset.UtcNow);
14
15         Console.Error.WriteLine("Instance " + key + " of DumbJob says: " + JobSays + ", and val is: " + FloatValue);
16     }
17 }

你可能会注意到类的整体代码比较长,但execute方法很简洁。有人会认为虽然代码比较长,如果程序员的集成开发平台(IDE)自动生成setter方法的话,可以编写更少的代码,而不必手工编写那些单独的调用方法从JobDataMap中取值。你可以自主选择编写代码的方式。

Job "Instances"

许多用户对Job实例对象确切的结构是什么疑惑了很长时间,我们将尝试在这为大家解答,并且在下一个板块讲述Job状态和并发机制。

你可以创建一个单独的Job实现类,创建多个不同的JobDetails实例,将不同Job实例定义存储在调度器中,每个JobDetails实例都有各自的参数和JobDataMap,并且把这些JobDetails添加到调度器中。

例如:你创建一个Job接口的实现类,类名为“SalesReportJob”,Job类可以预先传入一些假想的参数(通过JobDataMap)来指定销售报表中业务员的名字。接下来创建多个Job实例的定义(即JobDetails),如“SalesReportForJoe”和“SalesReportForMike”通过“Joe”和“Mike”指定到相应的JobDataMaps中作为参数输入到各自的Job对象中。

当触发器被触发时,相关的JobDetail实例会被加载,通过在调度器中配置的JobFactory会将关联的Job类实例化,默认的JobFactory只是在Job类中调用newInstance方法,然后尝试调用匹配JobDataMap键值的setter方法。你可以开发自己的JobFactory实现类通过应用IOC或DI机制完成Job实例的创建和初始化。

用Quartz框架的话来说,我们将每个存储的JobDetail称为Job定义或JobDetail实例,将每个执行的作业任务(Job)称为Job实例或Job定义实例。通常我们只用“job”单词来对应命名的Job定义或是JobDetail。当我们指Job接口的实现类时,一般使用“job class”术语。
Job状态和并发机制

现在介绍一些关于Job状态值和并发的额外信息。有一对加在Job类上面的注解,可以影响Quartz框架的这些方面的行为。

@DisallowConcurrentExecution注解添加到Job类中,会告知Quartz不要并发执行相同Job定义创建的多个实例对象。注意这里的措辞,要慎重地选择。引用上一章节的例子,如果SalesReportJob添加这个注解,在给定的时间段内只能执行一个SalesReportJobForJoe实例对象,但是可以并发执行一个SalesReportJobForMike实例。然而,在Quartz设计阶段决定在该类中携带注解,因为该注解会影响JobDetail类的编码。

@PersistJobDataAfterExecution注解添加到Job类中,会告知Quartz成功执行完execute方法后(有异常抛出的情况除外)更新JobDetail的JobDataMap中存储的数据。例如同一个JobDetail下一次执行时将接收更新的值而不是初始值。跟@DisallowConcurrentExecution注解类似,@PersistJobDataAfterExecution注解适用于Job定义实例,而不是Job类实例。只是该注解是附着在Job类的成员变量中,因为它不会影响整个类的编码(例如statefulness只需要在execute方法代码内正确使用即可)。

如果你使用@PersistJobDataAfterExecution注解,强烈建议你也应该考虑使用@DisallowConcurrentExecution注解,为了避免当两个相同JobDetail实例并发执行时可能由于最后存储状态数据不一致导致执行混乱。

Jobs的其它属性

接下来浏览Job实例的其它属性,这些属性是通过JobDetail对象传递给Job实例的。

Durability-如果一个Job是非持久化的,一旦没有任何活跃的触发器关联这个Job实例时,这个实例会自动地从调度器中移除。换句话说,非持久化的jobs的生命周期是以存在的触发器为界限的。

RequestsRecovery-如果一个Job设置了请求恢复参数,并且在调度器强制关闭过程中恰好在执行(强制关闭的情况例如:运行的线程崩溃,或者服务器宕机),当调度器重启时,它会重新被执行。这种情况下,JobExecutionContext的isRecovery方法会返回true。

JobExecutioinException

最后,我们需要告知你Job.execute方法的一些细节。允许你从execute方法抛出的唯一一种异常类型是JobExecutionException(运行时异常除外,可以正常抛出),由于这个限制,你应该在execute方法内的try-catch代码块中包装好要处理的异常。你也可以花些时间查阅一下JobExecutionException的文档,便于你在开发的Job类中需要捕获处理异常时,为调度器提供各种信息。

时间: 2024-10-24 19:03:19

[译]Quartz.Net 框架 教程(中文版)2.2.x 之第三课 更多关于Jobs和JobDetails的相关文章

[译]Quartz.NET 框架 教程(中文版)2.2.x 之第四课 更多关于Triggers

第四课 更多关于Triggers 跟作业任务类似,触发器也非常容易使用,但是在你能够充分掌握Quartz之前,你需要知道并理解许多触发器的客户化的参数.前面已经提到过,有许多不同类型的触发器供你选择,适用不同的调度需求. 你将会在第五课 Simple Trigger和第六课 Triggers学到这两种常用的触发器类型. 触发器通用属性 所有类型的触发器都有TriggerKey属性去跟踪触发器标识,除了这一个事实之外,还有许多其他的属性,对所有触发器类型都适用.这些通用属性在创建触发器定义时通过T

[译]Quartz.NET 框架 教程(中文版)2.2.x 之第六课 CronTrigger

第六课 CronTrigger CronTrigger比SimpleTrigger更常用,当你需要一个基于日历般概念的作业调度器,而不是像SimpleTrigger那样精确指定间隔时间. 使用SimpleTrigger,你可以这样指定触发时间表例如“每周五的中午”,或是“每周末的上午9:30”,甚至是“一月份每周一.三.五上午9:00到10:00之间每5分钟”. 虽然如此,跟SimpleTrigger一样,CronTrigger也需要指定startTime让调度器生效,指定endTime让调度器

[译]Quartz.NET 框架 教程(中文版)2.2.x 之第五课 SimpleTrigger

第五课 SimpleTrigger 如果你需要在一个指定时间段内执行一次作业任务或是在指定的时间间隔内多次执行作业任务,SimpleTrigger应该能满足你的调度需求.例如,你希望触发器在2015年1月13日上午11:23:54准时触发,或是希望在那个时间点触发,然后再重复触发5次,每隔10秒一次.有了这样的描述,你就不会对SimpleTrigger包含的参数感到奇怪:开始执行时间,结束执行时间,重复次数和重复执行间隔时间.所有的参数都是你期望的那样,只是关于结束执行时间参数有两条特别的提示.

[译]Quartz.NET 框架 教程(中文版)2.2.x 之第七课 触发监听器和作业任务监听器

第七课:触发监听器和作业任务监听器 监听器是在调度器中基于事件机制执行操作的对象.你大概可以猜到,触发监听器接收响应跟触发器有关的事件,作业任务监听器接收响应跟作业任务有关的事件. 跟触发器有关的事件包括:触发器被触发,触发器触发失败(在触发器课程中讨论过),以及触发器触发完成(触发器完成后作业任务开始运行). 1 public interface ITriggerListener 2 { 3 string Name { get; } 4 5 void TriggerFired(ITrigger

[译]Quartz.NET 框架 教程(中文版)2.2.x 之第八课 调度监听器

第八课 调度监听器 调度监听器和触发监听器和触发监听器.作业任务监听器非常相似,只是调度监听器在调度器内接收通知事件,而不需要关联具体的触发器或作业任务事件. 跟调度监听器相关的事件,添加作业任务/触发器,移除作业任务/触发器,调度器发生严重错误,调度器关闭等. The ISchedulerListener Interface public interface ISchedulerListener { void JobScheduled(Trigger trigger); void JobUns

Quartz 第三课 More About Jobs &amp; JobDetails(官方文档翻译)

当学完第二课之后,你欣喜的发现,让jobs工作起来是还是相当简单的.虽然让jobs运行起来很简单,对于其执行的关键内容还是需要知道的.它们是IJob接口中的Execute和JobDetails. 当你定义一个实现IJob接口的类的时候,你需要在里面实现实际需要执行的代码.Quartz.NET需要知道关于这代码的各种信息,这样 Quartz.NET才能像你期望的那样工作.这些细节是在JobDetail类中进行了描述,在上一节以及进行了简单的描述. JobDetail由JobBuilder进行实例化

Quartz 框架 教程(中文版)2.2.x

Quartz 框架 教程(中文版)2.2.x 之第一课 开始使用Quartz框架 Quartz 框架 教程(中文版)2.2.x 之第二课 Quartz API,Jobs和Triggers简介 Quartz 框架 教程(中文版)2.2.x 之第三课 更多关于Jobs和JobDetails Quartz 框架 教程(中文版)2.2.x 之第四课 更多关于Triggers Quartz 框架 教程(中文版)2.2.x 之第五课 SimpleTrigger Quartz 框架 教程(中文版)2.2.x

(译)Windsor入门教程---第五部分 添加日志功能

介绍 现在我们已经有了基础的框架了,是时候添加内容了,那么我们首先应该考虑的就是在应用程序中添加日志功能.我们会使用Windsor来配置,在这一部分,你将学习Windsor之外的其他功能. Logging Facility 在上一部分说过,Windsor有很多自带的可选的功能组件,他们扩展了Windsor的即用性.在这一部分,我们将在程序中添加日志功能. Logging Facility提供了一些常用的日志框架比如Log4net.Nlog,以及mvc内置的日志框架Trace.Logging Fa

(译)Windsor入门教程---第三部分 编写第一个Installer

简介 在第二部分我们创建了控制器工厂.现在我们要把我们的控制器交给Windsor来管理. Installer Windsor有一个专门的类installer.cs,用来向容器注册组件.在你的应用程序中至少会有几个这样的installer类,所以要保持他们的代码整洁以及可见性.接下来在我们的应用程序中新建一个专门的文件夹"Installer"来存放这些类.我们首先要注册到容器中的就是控制器,所以我们先来新建一个ControllersInstaller.cs类. Controller in