Not to mention XXLJOB or any other scheduling framework, let’s look at the first task scheduling framework I came across.

Quartz’s dynamic pause restores modify and delete tasks

To implement the dynamic addition of scheduled tasks, let’s take a look at the initial goal of our implementation. Here we only operate in memory, and do not save any information from Quartz to the database, i.e. using RAMJobStore.

Of course, you can implement JDBCJobStore if you need to, so that the task information will be more comprehensive.

For example, we would first list scheduled tasks and scheduled tasks in progress, which in this case means that the task has been triggered and has not yet finished executing.

  • For example, a data import operation takes five minutes at 2 o ‘clock every day. The task is in progress within five minutes.
  • The pause button can be used when the task is normal, and the resume button can be used when the task is paused.

Trigger each state description:

  • None: Trigger has completed and is not executing, or the Trigger cannot be found, or Trigger has been deleted.
  • NORMAL: Indicates the NORMAL state
  • PAUSED: PAUSED
  • COMPLETE: The trigger completes, but the task may still be in progress
  • BLOCKED: The thread is BLOCKED
  • ERROR: An ERROR occurs

A scheduled task runs the factory class

The task run entry, the Job implementation class, which I think of here as the factory class:

/** * User: liyd * Date: 14-1-3 * Time: 10:11 am */
public class QuartzJobFactory implements Job {
 
    @Override
    public void execute(JobExecutionContext context) throws JobExecutionException {
        System.out.println("Task running successfully");
        ScheduleJob scheduleJob
 = (ScheduleJob)context.getMergedJobDataMap().get("scheduleJob");
        System.out.println("Task name = [" + scheduleJob.getJobName() + "]"); }}Copy the code

In quartz 2.2.1, the StatefulJob interface is no longer recommended. Instead, annotations are used to implement stateless jobs. Only need to add annotations to your implementation Job class @ DisallowConcurrentExecution stateful can be realized:

/** ** Scheduled tasks run factory class * 

* User: liyd * Date: 14-1-3 * Time: 10:11 am */
@DisallowConcurrentExecution public class QuartzJobFactory implements Job {... }Copy the code

Create a task

Since we want to create tasks dynamically, our task information needs to be stored somewhere. Here we create a new entity class that holds the corresponding task information:

/** * Scheduled task information * User: liyd * Date: 14-1-3 * Time: 10:24 am */
public class ScheduleJob {
    /** Task id */
    private String jobId;
    /** Task name */
    private String jobName;
    /** Task group */
    private String jobGroup;
    /** Task status 0 Disabled 1 Enabled 2 Deleted */
    private String jobStatus;
    /** Task run time expression */
    private String cronExpression;
    /** Task description */
    private String desc;
    getter and setter ....
}
Copy the code

Next, we create the test data, which can be stored in the database and other places in practice. We use the group name of the task + the task name as the unique key of the task, which is the same as the implementation in Quartz:

/** Schedule task map */
private static Map<String, ScheduleJob> jobMap = new HashMap<String, ScheduleJob>();
static {
    for (int i = 0; i < 5; i++) {
        ScheduleJob job = new ScheduleJob();
        job.setJobId("10001" + i);
        job.setJobName("data_import" + i);
        job.setJobGroup("dataWork");
        job.setJobStatus("1");
        job.setCronExpression("0/5 * * * *?");
        job.setDesc("Data import Task"); addJob(job); }}/** * Add task *@param scheduleJob
 */
public static void addJob(ScheduleJob scheduleJob) {
    jobMap.put(scheduleJob.getJobGroup() + "_" + scheduleJob.getJobName(), scheduleJob);
}
Copy the code

With scheduling factory, had a task to run portal implementation class, with the task information, the mission is to create our timing, I here to design it into a Job corresponds to a trigger, both group and the same name, convenient management, order is more clear, when creating a task if there is no new one, if the existing the update task, The main code is as follows:

The schedulerFactoryBean is created and injected by Spring

Scheduler scheduler = schedulerFactoryBean.getScheduler();
 
// Here is the task information data
List<ScheduleJob> jobList = DataWorkContext.getAllJob();
for (ScheduleJob job : jobList) {
    TriggerKey T = TriggerKey.triggerKey(job.getJobName(), job.getJobGroup());
    // Get trigger, the bean id defined in the Spring configuration file ="myTrigger"
    CronTrigger trigger = (CronTrigger) scheduler.getTrigger(triggerKey);
    // Create one if it does not exist
    if (null == trigger) {
        JobDetail jobDetail = JobBuilder.newJob(QuartzJobFactory.class)
            .withIdentity(job.getJobName(), job.getJobGroup()).build();
        jobDetail.getJobDataMap().put("scheduleJob", job);
        // Expression scheduling builder
        CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule(job
            .getCronExpression());
        // Build a new trigger with the new cronExpression
        trigger = TriggerBuilder.newTrigger().withIdentity(job.getJobName(), job.getJobGroup()).withSchedule(scheduleBuilder).build();
        scheduler.scheduleJob(jobDetail, trigger);
    } else {
        // Trigger already exists, then update the corresponding timing setting
        // Expression scheduling builder
        CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule(job
            .getCronExpression());
        // Rebuild trigger with the new cronExpression
        trigger = trigger.getTriggerBuilder().withIdentity(triggerKey)
            .withSchedule(scheduleBuilder).build();
        // Press the new trigger to reset job executionscheduler.rescheduleJob(triggerKey, trigger); }}Copy the code
  • This completes our dynamic task creation, and we’re done. With the above code, will the adding and modifying tasks also be solved?

  • The above 5 test tasks are executed once every 5 seconds and all call the Execute method of QuartzJobFactory, but the parameters of the task information passed in are different. The following code in the execute method is to get the specific task information, including task group and task name:

ScheduleJob scheduleJob = (ScheduleJob)context.getMergedJobDataMap().get("scheduleJob");
Copy the code
  • Given the task group and task name, the uniqueness of the task is determined. Is it easy to implement what is required next?
  • To add a new scheduled task, you only need to add a record to the task information list. Then, in the Execute method, you can perform specific operations based on the task group and task name.
  • The above has initially realized the functions we need, and the addition and modification can also be derived from the source code, but we need to test in the actual development, if a task is run once an hour, is it very inconvenient to test? Of course you can modify the runtime expression of the task, but that’s not the best way to do it. The next step is to trigger the task without making any changes to the current task information, and the trigger will only be run once for testing purposes.

Planned tasks

** is a task that has been added to the Quartz scheduler. Quartz does not provide such a query interface directly, so we need to combine JobKey and Trigger to implement it

	Scheduler scheduler = schedulerFactoryBean.getScheduler(); 
	GroupMatcher<JobKey> matcher = GroupMatcher.anyJobGroup(); 
	Set<JobKey> jobKeys = scheduler.getJobKeys(matcher); 
	List<ScheduleJob> jobList = new ArrayList<ScheduleJob>(); 
	for (JobKey jobKey : jobKeys) { 
 		List<? extends Trigger> triggers = scheduler.getTriggersOfJob(jobKey); 
 		for (Trigger trigger : triggers) { 
 			ScheduleJob job = new ScheduleJob(); 
			job.setJobName(jobKey.getName()); 
			job.setJobGroup(jobKey.getGroup()); 
			job.setDesc("Trigger :" + trigger.getKey()); 
			Trigger.TriggerState triggerState = scheduler.getTriggerState(trigger.getKey()); 
			job.setJobStatus(triggerState.name()); 
			if (trigger instanceofCronTrigger) { CronTrigger cronTrigger = (CronTrigger) trigger; String cronExpression = cronTrigger.getCronExpression(); job.setCronExpression(cronExpression); } jobList.add(job); }}Copy the code
  • JobList is the list of planned tasks that we need. It is necessary to pay attention to the situation that a job may have multiple triggers. When running a task immediately as described below, a temporary trigger will also be generated here.

  • In this case, a Job with multiple triggers is regarded as multiple tasks. CronTrigger is generally used in actual projects, so we focus on CronTrigger here.

Running task

The implementation is similar to the planned task, the core code:

Scheduler scheduler = schedulerFactoryBean.getScheduler(); 
List<JobExecutionContext> executingJobs = scheduler.getCurrentlyExecutingJobs(); 
List<ScheduleJob> jobList = new ArrayList<ScheduleJob>(executingJobs.size()); 
for (JobExecutionContext executingJob : executingJobs) { 
	 ScheduleJob job = new ScheduleJob(); 
	 JobDetail jobDetail = executingJob.getJobDetail(); 
	 JobKey jobKey = jobDetail.getKey(); 
	 Trigger trigger = executingJob.getTrigger(); 
	 job.setJobName(jobKey.getName()); 
	 job.setJobGroup(jobKey.getGroup()); 
	 job.setDesc("Trigger :" + trigger.getKey()); 
	 Trigger.TriggerState triggerState = scheduler.getTriggerState(trigger.getKey()); 
	 job.setJobStatus(triggerState.name()); 
	 if (trigger instanceof CronTrigger) { 
		 CronTrigger cronTrigger = (CronTrigger) trigger; 
		 String cronExpression = cronTrigger.getCronExpression(); 
		 job.setCronExpression(cronExpression); 
	 } 
	 jobList.add(job); 
}  
Copy the code

Pause task mechanism

Relatively simple, core code:

Scheduler scheduler = schedulerFactoryBean.getScheduler(); 
JobKey jobKey = JobKey.jobKey(scheduleJob.getJobName(), scheduleJob.getJobGroup()); 
scheduler.pauseJob(jobKey); 
Copy the code

The restore task

Pause task relative, core code:

Scheduler scheduler = schedulerFactoryBean.getScheduler(); 
JobKey jobKey = JobKey.jobKey(scheduleJob.getJobName(), scheduleJob.getJobGroup()); 
scheduler.resumeJob(jobKey); 
Copy the code

Delete the task

After the task is deleted, the corresponding trigger will also be deleted

Scheduler scheduler = schedulerFactoryBean.getScheduler(); 
JobKey jobKey = JobKey.jobKey(scheduleJob.getJobName(), scheduleJob.getJobGroup()); 
scheduler.deleteJob(jobKey);  
Copy the code

Run task now

  • Here run immediately, will only run once, convenient for testing. Quartz is achieved by temporarily generating a trigger that will be deleted automatically after the task is completed.

  • Trigger keys are randomly generated, for example: default.mt_4k9FD10jcn9mg.

  • In my tests, the first part of default.mt was fixed, and the second part was randomly generated.

Scheduler scheduler = schedulerFactoryBean.getScheduler(); 
JobKey jobKey = JobKey.jobKey(scheduleJob.getJobName(), scheduleJob.getJobGroup()); 
scheduler.triggerJob(jobKey);  
Copy the code

Update the time expression of the task

Immediately after the update, the task will execute with the new time expression:

Scheduler scheduler = schedulerFactoryBean.getScheduler(); 
  
TriggerKey triggerKey = TriggerKey.triggerKey(scheduleJob.getJobName(), 
 scheduleJob.getJobGroup()); 
  
// Get trigger, the bean id defined in the Spring configuration file ="myTrigger"
CronTrigger trigger = (CronTrigger) scheduler.getTrigger(triggerKey); 
  
// Expression scheduling builder
CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule(scheduleJob 
 .getCronExpression()); 
  
// Rebuild trigger with the new cronExpression
trigger = trigger.getTriggerBuilder().withIdentity(triggerKey) 
 .withSchedule(scheduleBuilder).build(); 
  
// Press the new trigger to reset job execution
scheduler.rescheduleJob(triggerKey, trigger);  
Copy the code

CronExpression expression:

  • Special characters allowed by field allowed values

    • 0-59 seconds, – * /
    • It’s 0-59, – * /
    • Hour 0-23, – * /
    • Dates 1-31, – *? / L W C
    • Month 1-12 or Jan-dec, – * /
    • Week 1-7 or Sun-sat, – *? / L C #
    • Year (optional) left blank, 1970-2099, – * /
  • Special character meaning

    • *Represents all values;
    • ? Represents an unspecified value, that is, does not care what value it is;
    • -Represents a specified range;
    • , to attach a possible value;
    • Before the/sign indicates the start time, after the sign indicates the value of each increment;
  • L W C

    • L(“last”) (“last”) “L” is used in the day-of-month field meaning “last day of the month “; Used in the day-of-week field, it simply means “7” or “SAT”. If used together with a number in the day-of-week field, it means “what’s the last day of the month?”
      • For example: “6L” means “last Friday of the month “. When we use” L”, it is important not to specify a list value or range, otherwise we may get some unexpected results.
    • W(“weekday”) can only be used in the day-of-month field. Used to describe the working day (Monday through Friday) closest to a specified day.
      • For example, use “15W” in the day-of-month field to refer to “the day closest to the 15th day of the month”. If the 15th day of the month falls on a Saturday, the trigger will be triggered on Friday, the 14th day of the month. If the 15th day of the month falls on a Sunday, the trigger will fire on the 16th day of the month on Monday. If the 15th day of the month falls on a Tuesday, trigger on the trigger day.
      • Note that this usage only computes values for the current month, not beyond it. The “W” character can only specify a day in day-of-month, and cannot be a range or list. You can also use “LW” to specify the last working day of the month.
    • #This parameter is available only in the day-of-week field. Used to specify the day of the month. Example: Use “6#3” in the day-of-week field to refer to the 3rd Friday of the month (6 refers to Friday and 3 refers to the 3rd). If the specified date does not exist, the trigger does not fire.
    • C is the value calculated after being associated with calendar.
      • Example: Use “5C” in the day-of-month field to refer to the 5th day of the month or later including the first day of the calendar; Use “1C” in the day-of-week field to refer to the first day including calendar on or after this Sunday.

– Abbreviation of week: – Monday Mon-Tuesday tue-Wednesday WED – Thursday THU – Friday FRI – Saturday SAT – Sunday SUN

Case insensitive in MONTH and Day Of Week fields

  • Expression meaning
    • It’s triggered every day at 12 noon
      • “0, 0, 12 * *?
    • Triggered every day at 10:15 a.m
      • “0 15 10? * *”
      • “0, 15, 10 * *?”
      • “0, 15, 10 * *? *” (the last item here is optional)
    • In 2005, it was triggered every day at 10:15 a.m
      • “0, 15, 10 * *? 2005”
    • Triggered every minute between 2 p.m. and 2:59 p.m. each day
      • “0 * 14 * *?”
    • Triggered every 5 minutes between 2 p.m. and 2:55 p.m. each day
      • “0 0/5 14 * *?”
    • Triggered every 5 minutes between 2 p.m. and 2:55 p.m. and 6 p.m. and 6:55 p.m. each day
      • “0/5 14,18 * *?”
    • Triggered every minute between 2 p.m. and 2:05 p.m. each day
      • “0 0-5 14 * *?”
    • Triggered at 2:10 PM and 2:44 PM on Wednesdays of March each year
      • “0 10 44 14? 3 WED”/”0 10,44, 14? 3 WED * “
    • Triggered Monday through Friday at 10:15 a.m
      • “0 15 10? * MON-FRI” / “0 15 10 ? * MON-FRI * “
    • Triggered at 10:15 am on the 15th of each month
      • “0, 15, 10, 15 times?”
    • Triggered at 10:15 a.m. on the last day of each month
      • “0 15 10 L * ?”   
    • Triggered at 10:15 am on the last Friday of every month
      • “0 15 10? * 6L”
    • Triggered on the last Friday of each month from 2002 to 2005 at 10:15 am
      • “0 15 10? * 6L 2002-2005”
    • Triggered at 10:15 am on the third Friday of every month
      • “0 15 10? * 6 # 3”
    • Every two hours
      • 0 */2 * * *