`
iwindyforest
  • 浏览: 230041 次
  • 性别: Icon_minigender_1
  • 来自: 上海
社区版块
存档分类
最新评论

Quartz Scheduler Learning Note

阅读更多

Quartz Scheduler Learning Note

 

Quartz is a convenient tool for task scheduling.

Key Interfaces

To use quartz, the following interfaces should be included:

 

  • Scheduler - the main API for interacting with the scheduler.
  • Job - an interface to be implemented by components that you wish to have executed by the scheduler.
  • JobDetail - used to define instances of Jobs.
  • Trigger - a component that defines the schedule upon which a given Job will be executed.
  • JobBuilder - used to define/build JobDetail instances, which define instances of Jobs.
  • TriggerBuilder - used to define/build Trigger instances.

 

 

Import Static Methods or Classes

For the convenience of using api, the following methods or classes should better be included:

import static org.quartz.JobBuilder.newJob;
import static org.quartz.SimpleScheduleBuilder.simpleSchedule;
import static org.quartz.TriggerBuilder.newTrigger;
import static org.quartz.JobKey.*;
import static org.quartz.impl.matchers.KeyMatcher.*;
import static org.quartz.impl.matchers.GroupMatcher.*;
import static org.quartz.impl.matchers.AndMatcher.*;
import static org.quartz.impl.matchers.OrMatcher.*;
import static org.quartz.impl.matchers.EverythingMatcher.*;

 

 

The start up configuration for quartz.properties

org.quartz.scheduler.skipUpdateCheck=true

 

org.quartz.scheduler.instanceName = MyScheduler

 

org.quartz.threadPool.threadCount = 3

 

org.quartz.jobStore.class = org.quartz.simpl.RAMJobStore

 

Note that quartz.properties has to be put under the root class path.

 

Jobs 

Job Class and Job Instance

In "Quartz speak", we refer to each stored JobDetail as a "job definition" or "JobDetail instance", and we refer to a each executing job as a "job instance" or "instance of a job definition". Usually if we just use the word "job" we are referring to a named definition, or JobDetail. When we are referring to the class implementing the job interface, we usually use the term "job class".

 

Each (and every) time the scheduler executes the job, it creates a new instance of the class before calling its execute(..) method. When the execution is complete, references to the job class instance are dropped, and the instance is then garbage collected.

 

A job class is an implementation of Job Interface as such:

public class HelloJob implements Job {
    public HelloJob() {
    }

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

 

A jobDetail as a "job definition" or "JobDetail instance" can be create as the following:

// define the job and tie it to our HelloJob class
JobDetail job = newJob(HelloJob.class).withIdentity(HelloJob.class.getSimpleName(), this.groupName).usingJobData("message", "Hello, Quartz").build(); 

 

Please note that newJob is a static method of JobBuilder class, as we imported:

 

importstatic org.quartz.JobBuilder.newJob;

 

 

 

JobDataMap and MergedJobDataMap

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.

 

The JobDataMap can be used to hold any amount of (serializable) data 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 retrieving data of primitive types.

 

When creating jobDetail instance by using JobBuilder class, we can use API usingJobData() to set the properties which used to get transferred to Job while its execution.

 

.usingJobData("message", "Hello, Quartz").

 

To use the job data passed by usingJobData() in Job implementation, JobDataMap and MergedJobDataMap can be used:

 

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

 

JobDataMap mergedJobDataMap = context.getMergedJobDataMap();

 

The mergedJobDataMap 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. It is thus considered a 'best practice' that the execute code of a Job retrieve data from the JobDataMap found on this object.

 

If you add setter methods to your job class that correspond to the names of keys in the JobDataMap (such as a setJobSays(String val) method for the data in the example above), then Quartz's default JobFactory implementation will automatically call those setters when the job is instantiated, thus preventing the need to explicitly get the values out of the map within your execute method.

public class HelloJob implements Job {
      Logger logger = LoggerFactory.getLogger(this.getClass().getName());
      private String message; 

      public String getMessage() {
            return this.message;
      }

      /**
       * automatically injected while initialization
       * @param message
       */
      public void setMessage(String message) {
            this.message = message;
      } 

      @Override
      public void execute(JobExecutionContext context) throws JobExecutionException {
            JobKey key = context.getJobDetail().getKey();  
            this.logger.info("Instance of " + key + ": " + this.getMessage());
      }
}

 

 

 

@DisallowConcurrentExecution is an annotation that can be added to the Job class that tells Quartz not to execute multiple instances of a given job definition (that refers to the given job class) concurrently.

 

Notice the wording there, as it was chosen very carefully. In the example from the previous section, if "SalesReportJob" has this annotation, than only one instance of "SalesReportForJoe" can execute at a given time, but it can execute concurrently with an instance of "SalesReportForMike". The constraint is based upon an instance definition (JobDetail), not on instances of the job class. However, it was decided (during the design of Quartz) to have the annotation carried on the class itself, because it does often make a difference to how the class is coded.

 

@PersistJobDataAfterExecution is an annotation that can be added to the Job class that tells Quartz to update the stored copy of the JobDetail's JobDataMap after the execute() method completes successfully (without throwing an exception), such that the next execution of the same job (JobDetail) receives the updated values rather than the originally stored values. Like the @DisallowConcurrentExecution annotation, this applies to a job definition instance, not a job class instance, though it was decided to have the job class carry the attribute because it does often make a difference to how the class is coded (e.g. the 'statefulness' will need to be explicitly 'understood' by the code within the execute method).

 

If you use the @PersistJobDataAfterExecution annotation, you should strongly consider also using the @DisallowConcurrentExecution annotation, in order to avoid possible confusion (race conditions) of what data was left stored when two instances of the same job (JobDetail) executed concurrently.

 

 

 

Triggers

 

SimpleTrigger

SimpleTrigger can be created by using TriggerBuilder.newTrigger , this is a static method, so you should import like the following:

 

 

import static org.quartz.TriggerBuilder.newTrigger;                                       

// Trigger the job to run now, and then repeat every 5 seconds
Trigger trigger = newTrigger().withIdentity("helloWorldTestTrigger", this.groupName).startNow().withSchedule(simpleSchedule().withIntervalInSeconds(5).repeatForever()).build();

 

 

 

CronTrigger

The public interface for inspecting settings specific to a CronTrigger, . which is used to fire a org.quartz.Job at given moments in time, defined with Unix 'cron-like' schedule definitions.

 

 

Cron Expression

Cron expressions provide the ability to specify complex time combinations such as "At 8:00am every Monday through Friday" or "At 1:30am every last Friday of the month".

 

Cron expressions are comprised of 6 required fields and one optional field separated by white space. The fields respectively are described as follows:

 

Field Name

 

Allowed Values

 

Allowed Special Characters

Seconds

 

0-59

 

, - * /

Minutes

 

0-59

 

, - * /

Hours

 

0-23

 

, - * /

Day-of-month

 

1-31

 

, - * ? / L W

Month

 

1-12 or JAN-DEC

 

, - * /

Day-of-Week

 

1-7 or SUN-SAT

 

, - * ? / L #

Year (Optional)

 

empty, 1970-2199

 

, - * /

 

The '*' character is used to specify all values. For example, "*" in the minute field means "every minute".

 

The '?' character is allowed for the day-of-month and day-of-week fields. It is used to specify 'no specific value'. This is useful when you need to specify something in one of the two fields, but not the other.

 

The '-' character is used to specify ranges For example "10-12" in the hour field means "the hours 10, 11 and 12".

 

The ',' character is used to specify additional values. For example "MON,WED,FRI" in the day-of-week field means "the days Monday, Wednesday, and Friday".

 

The '/' character is used to specify increments. For example "0/15" in the seconds field means "the seconds 0, 15, 30, and 45". And "5/15" in the seconds field means "the seconds 5, 20, 35, and 50". Specifying '*' before the '/' is equivalent to specifying 0 is the value to start with. Essentially, for each field in the expression, there is a set of numbers that can be turned on or off. For seconds and minutes, the numbers range from 0 to 59. For hours 0 to 23, for days of the month 0 to 31, and for months 1 to 12. The "/" character simply helps you turn on every "nth" value in the given set. Thus "7/6" in the month field only turns on month "7", it does NOT mean every 6th month, please note that subtlety.

 

The 'L' character is allowed for the day-of-month and day-of-week fields. This character is short-hand for "last", but it has different meaning in each of the two fields. For example, the value "L" in the day-of-month field means "the last day of the month" - day 31 for January, day 28 for February on non-leap years. If used in the day-of-week field by itself, it simply means "7" or "SAT". But if used in the day-of-week field after another value, it means "the last xxx day of the month" - for example "6L" means "the last friday of the month". You can also specify an offset from the last day of the month, such as "L-3" which would mean the third-to-last day of the calendar month. When using the 'L' option, it is important not to specify lists, or ranges of values, as you'll get confusing/unexpected results.

 

The 'W' character is allowed for the day-of-month field. This character is used to specify the weekday (Monday-Friday) nearest the given day. As an example, if you were to specify "15W" as the value for the day-of-month field, the meaning is: "the nearest weekday to the 15th of the month". So if the 15th is a Saturday, the trigger will fire on Friday the 14th. If the 15th is a Sunday, the trigger will fire on Monday the 16th. If the 15th is a Tuesday, then it will fire on Tuesday the 15th. However if you specify "1W" as the value for day-of-month, and the 1st is a Saturday, the trigger will fire on Monday the 3rd, as it will not 'jump' over the boundary of a month's days. The 'W' character can only be specified when the day-of-month is a single day, not a range or list of days.

 

The 'L' and 'W' characters can also be combined for the day-of-month expression to yield 'LW', which translates to "last weekday of the month".

 

The '#' character is allowed for the day-of-week field. This character is used to specify "the nth" XXX day of the month. For example, the value of "6#3" in the day-of-week field means the third Friday of the month (day 6 = Friday and "#3" = the 3rd one in the month). Other examples: "2#1" = the first Monday of the month and "4#5" = the fifth Wednesday of the month. Note that if you specify "#5" and there is not 5 of the given day-of-week in the month, then no firing will occur that month. If the '#' character is used, there can only be one expression in the day-of-week field ("3#1,6#3" is not valid, since there are two expressions).

 

The legal characters and the names of months and days of the week are not case sensitive.

 

NOTE:

 

  • Support for specifying both a day-of-week and a day-of-month value is not complete (you'll need to use the '?' character in one of these fields).
  • Overflowing ranges is supported - that is, having a larger number on the left hand side than the right. You might do 22-2 to catch 10 o'clock at night until 2 o'clock in the morning, or you might have NOV-FEB. It is very important to note that overuse of overflowing ranges creates ranges that don't make sense and no effort has been made to determine which interpretation CronExpression chooses. An example would be "0 0 14-6 ? * FRI-MON".

 

 

 

TriggerListeners and JobListeners

 

For your convenience, tather than implementing those interfaces, your class could also extend the class JobListenerSupport or TriggerListenerSupport (and SchedulerListenerSupport)and simply override the events you're interested in.

 

Listeners are registered with the scheduler during run time, and are NOT stored in the JobStore along with the jobs and triggers. This is because listeners are typically an integration point with your application. Hence, each time your application runs, the listeners need to be re-registered with the scheduler.

 

 

Job Stores

 

RAMJobStore

RAMJobStore is the simplest JobStore to use, it is also the most performant (in terms of CPU time). RAMJobStore gets its name in the obvious way: it keeps all of its data in RAM

 

org.quartz.jobStore.class = org.quartz.simpl.RAMJobStore

 

 

JDBCJobStore

JDBCJobStore is also aptly named - it keeps all of its data in a database via JDBC.

 

To use JDBCJobStore, you must first create a set of database tables for Quartz to use. You can find table-creation SQL scripts in the "docs/dbTables" directory of the Quartz distribution.

 

org.quartz.jobStore.class = org.quartz.impl.jdbcjobstore.JobStoreTX
org.quartz.jobStore.driverDelegateClass = org.quartz.impl.jdbcjobstore.StdJDBCDelegate
org.quartz.jobStore.tablePrefix = QRTZ_ 

 

 

Third Party DataSource Connection Pool

To use third party datasource pool, such like druid, you should create an implementation class for interface org.quartz.utils.ConnectionProvider.

 

The implementation could be as following:

 

public class DruidConnectionProvider extends DruidDataSource implements ConnectionProvider {
      private static final long serialVersionUID = 1L; 

      @Override
      public void shutdown() throws SQLException {
            this.close();
      }

      @Override
      public void initialize() throws SQLException {
            this.init();
      }
} 

 

 

Then include the properties as below:

 

org.quartz.jobStore.dataSource = druid

org.quartz.dataSource.druid.connectionProvider.class = com.java.quartz.utils.DruidConnectionProvider
org.quartz.dataSource.druid.driverClassName = org.h2.Driver
org.quartz.dataSource.druid.url = jdbc:h2:tcp://localhost/C:/java/h2/data/test;MODE=MYSQL;MVCC=TRUE;DB_CLOSE_DELAY=-1;
org.quartz.dataSource.druid.username = admin
org.quartz.dataSource.druid.password = admin
org.quartz.dataSource.druid.validationQuery = select now()
org.quartz.dataSource.druid.testWhileIdle = true
org.quartz.dataSource.druid.testOnBorrow = false
org.quartz.dataSource.druid.testOnReturn = false
org.quartz.dataSource.druid.filters = slf4j
org.quartz.dataSource.druid.poolPreparedStatements = false
org.quartz.dataSource.druid.maxOpenPreparedStatements = -1

 

 

 

Clustering With JobStoreTX

These features will be discussed later,

 

To be continued…

 

 

 

 

分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics