Lesson 3: More About Jobs & JobDetails   Leave a comment

As you’ve seen, Jobs are rather easy to implement. There are just a
few more things that you need to understand about the nature of jobs,
about the execute(..) method of the Job interface, and about JobDetails.

While a class that you implement is the actual "job", Quartz needs
to be informed about various attributes that you may wish the job to
have. This is done via the JobDetail class, which was mentioned briefly
in the previous section. Software ‘archaeologists’ may be interested to
know that in an older incarnation of Quartz, the implementation of the
functionality of JobDetail was imposed upon the implementor of each Job
class by having all of JobDetail’s ‘getter’ methods on the Job
interface itself. This forced a cumbersome job of re-implementing
virtually identical code on every Job class – which was really dumb…
thus we created the JobDetail class.

Let’s take a moment now to discuss a bit about the ‘nature’ of Jobs
and the life-cycle of job instances within Quartz. First lets take a
look back at some of that snippet of code we saw in Lesson 1:

Using Quartz
JobDetail jobDetail = new JobDetail("myJob",               // job name
sched.DEFAULT_GROUP, // job group (you can also specify 'null' to use the default group)
DumbJob.class); // the java class to execute

Trigger trigger = TriggerUtils.makeDailyTrigger(8, 30);
trigger.setStartTime(new Date());
trigger.setName("myTrigger");

sched.scheduleJob(jobDetail, trigger);

Now consider the job class "DumbJob" defined as such:

DumbJob
public class DumbJob implements Job {

public DumbJob() {
}

public void execute(JobExecutionContext context)
throws JobExecutionException
{
System.err.println("DumbJob is executing.");
}
}

Notice that we ‘feed’ the scheduler a JobDetail instance, and that
it refers to the job to be executed by simply providing the job’s
class. Each (and every) time the scheduler executes the job, it creates
a new instance of the class before calling its execute(..) method. One
of the ramifications of this behavior is the fact that jobs must have a
no-arguement constructor. Another ramification is that it does not make
sense to have data-members defined on the job class – as their values
would be ‘cleared’ every time the job executes.

You may now be wanting to ask "how can I provide
properties/configuration for a Job instance?" and "how can I keep track
of a job’s state between executions?" The answer to these questions are
the same: the key is the JobDataMap, which is part of the JobDetail
object.

JobDataMap

The JobDataMap can be used to hold any number of (serializable)
objects which you wish to have made available to the job instance when
it executes. JobDataMap is an implementation of the Java Map interface,
and has some added convenience methods for storing and retreiving data
of primitive types.

Here’s some quick snippets of putting data into the JobDataMap prior to adding the job to the scheduler:

Setting Values in a JobDataMap
jobDetail.getJobDataMap().put("jobSays", "Hello World!");
jobDetail.getJobDataMap().put("myFloatValue", 3.141f);
jobDetail.getJobDataMap().put("myStateData", new ArrayList());

Here’s a quick example of getting data from the JobDataMap during the job’s execution:

Getting Values from a JobDataMap
public class DumbJob implements Job {

public DumbJob() {
}

public void execute(JobExecutionContext context)
throws JobExecutionException
{
String instName = context.getJobDetail().getName();
String instGroup = context.getJobDetail().getGroup();

JobDataMap dataMap = context.getJobDetail().getJobDataMap();

String jobSays = dataMap.getString("jobSays");
float myFloatValue = dataMap.getFloat("myFloatValue");
ArrayList state = (ArrayList)dataMap.get("myStateData");
state.add(new Date());

System.err.println("Instance " + instName + " of DumbJob says: " + jobSays);
}
}

If you use a persistent JobStore (discussed in the JobStore section
of this tutorial) you should use some care in deciding what you place
in the JobDataMap, because the object in it will be serialized, and
they therefore become prone to class-versioning problems. Obviously
standard Java types should be very safe, but beyond that, anytime
someone changes the definition of a class for which you have serialized
instances, care has to be taken not to break compatibility. Further
information on this topic can be found in this Java Developer
Connection Tech Tip: Serialization In The Real World. Optionally, you
can put JDBC-JobStore and JobDataMap into a mode where only primitives
and strings can be stored in the map, thus eliminating any possibility
of later serialization problems.

Triggers can also have JobDataMaps associated with them. This can be
useful in the case where you have a Job that is stored in the scheduler
for regular/repeated use by multiple Triggers, yet with each
independent triggering, you want to supply the Job with different data
inputs.

The JobDataMap that is found on the JobExecutionContext during Job
execution serves as a convenience. It is a merge of the JobDataMap
found on the JobDetail and the one found on the Trigger, with the value
in the latter overriding any same-named values in the former.

Here’s a quick example of getting data from the JobExecutionContext’s merged JobDataMap during the job’s execution:

Getting Values from the JobExecutionContext convenience/merged JobDataMap
public class DumbJob implements Job {

public DumbJob() {
}

public void execute(JobExecutionContext context)
throws JobExecutionException
{
String instName = context.getJobDetail().getName();
String instGroup = context.getJobDetail().getGroup();

JobDataMap dataMap = context.getMergedJobDataMap(); // Note the difference from the previous example

String jobSays = dataMap.getString("jobSays");
float myFloatValue = dataMap.getFloat("myFloatValue");
ArrayList state = (ArrayList)dataMap.get("myStateData");
state.add(new Date());

System.err.println("Instance " + instName + " of DumbJob says: " + jobSays);
}
}

StatefulJob

Now, some additional notes about a job’s state data (aka
JobDataMap): A Job instance can be defined as "stateful" or
"non-stateful". Non-stateful jobs only have their JobDataMap stored at
the time they are added to the scheduler. This means that any changes
made to the contents of the job data map during execution of the job
will be lost, and will not seen by the job the next time it executes.
You have probably guessed, a stateful job is just the opposite – its
JobDataMap is re-stored after every execution of the job. One
side-effect of making a job stateful is that it cannot be executed
concurrently. Or in other words: if a job is stateful, and a trigger
attempts to ‘fire’ the job while it is already executing, the trigger
will block (wait) until the previous execution completes.

You ‘mark’ a Job as stateful by having it implement the StatefulJob interface, rather than the Job interface.

Job ‘Instances’

One final point on this topic that may or may not be obvious by now:
You can create a single job class, and store many ‘instance
definitions’ of it within the scheduler by creating multiple instances
of JobDetails – each with its own set of properties and JobDataMap –
and adding them all to the scheduler.

When a trigger fires, the Job it is associated to is instantiated
via the JobFactory configured on the Scheduler. The default JobFactory
simply calls newInstance() on the job class. You may want to create
your own implementation of JobFactory to accomplish things such as
having your application’s IoC or DI container produce/initialize the
job instance.

Other Attributes Of Jobs

Here’s a quick summary of the other properties which can be defined for a job instance via the JobDetail object:

  • Durability – if a job is non-durable, it is automatically
    deleted from the scheduler once there are no longer any active triggers
    associated with it.
  • Volatility – if a job is volatile, it is not persisted between re-starts of the Quartz scheduler.
  • RequestsRecovery
    – if a job "requests recovery", and it is executing during the time of
    a ‘hard shutdown’ of the scheduler (i.e. the process it is running
    within crashes, or the machine is shut off), then it is re-executed
    when the scheduler is started again. In this case, the
    JobExecutionContext.isRecovering() method will return true.
  • JobListeners
    – a job can have a set of zero or more JobListeners associated with it.
    When the job executes, the listeners are notified. More discussion on
    JobListeners can be found in the section of this document that is
    dedicated to the topic of TriggerListeners & JobListeners.

The Job.execute(..) Method

JobExecutionException

Finally, we need to inform you of a few details of the
Job.execute(..) method. The only type of exception (including
RuntimeExceptions) that you are allowed to throw from the execute
method is the JobExecutionException. Because of this, you should
generally wrap the entire contents of the execute method with a
‘try-catch’ block. You should also spend some time looking at the
documentation for the JobExecutionException, as your job can use it to
provide the scheduler various directives as to how you want the
exception to be handled.

Posted 2009年11月3日 by gw8310 in Computers and Internet

发表评论

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / 更改 )

Twitter picture

You are commenting using your Twitter account. Log Out / 更改 )

Facebook photo

You are commenting using your Facebook account. Log Out / 更改 )

Google+ photo

You are commenting using your Google+ account. Log Out / 更改 )

Connecting to %s

%d 博主赞过: