Task Scheduling Quartzh Framework User Guide

overview

Quartz is the leader in the open source task scheduling framework. It is another open source project of the OpenSymphony open source organization in the field of Job scheduling. It is completely developed in Java and can be used to execute scheduled tasks, similar to java.util.Timer. But compared to Timer, Quartz adds a lot of features:

  • Quartz provides a powerful task scheduling mechanism while maintaining simplicity of use. Quartz allows developers to flexibly define the scheduling schedule of triggers, and can map triggers and tasks associatively.
  • Quartz provides a persistence mechanism for scheduling the operating environment, which can save and restore the scheduling site. Even if the system is shut down due to a failure, the task scheduling site data will not be lost.
  • Quartz also provides component listeners, various plug-ins, thread pools and other functions.

Most companies use the function of scheduled tasks. Take the train ticket purchase as an example:

  1. After placing an order and purchasing a ticket, a task (job) to be paid will be inserted in the background, usually 30 minutes, and the job will be executed after 30 minutes to determine whether to pay, and the order will be canceled if the payment is not made;
  2. After the payment is completed, the background will insert a task (job) to be consumed after receiving the payment callback. The job trigger date is the departure date on the train ticket. After this time, the job will be executed to determine whether to use it, etc.

Quartz's core classes have the following three parts:

  • Task Job: the task class that needs to implement the org.quartz.Job interface, implement execute()the method, and complete the task after execution.

  • Trigger Trigger:

    Implement the trigger that triggers the execution of the task. The most basic function of the trigger is to specify the execution time, execution interval, and number of runs of the Job.

    Includes SimpleTrigger (simple trigger) and CronTrigger.

  • Scheduler Scheduler: The task scheduler is responsible Triggerfor executing Job tasks based on triggers.

The main relationships are as follows:

insert image description here


Getting Started Case

rely

SpringBoot integration dependencies

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-quartz</artifactId>
    </dependency>

Native dependencies

    <!-- 核心包 -->
    <dependency>
        <groupId>org.quartz-scheduler</groupId>
        <artifactId>quartz</artifactId>
        <version>2.3.0</version>
    </dependency>
    <!-- 工具包 -->
    <dependency>
        <groupId>org.quartz-scheduler</groupId>
        <artifactId>quartz-jobs</artifactId>
        <version>2.3.0</version>
    </dependency>

the code

custom task class

import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.time.DateFormatUtils;
import org.quartz.Job;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import java.util.Date;

@Slf4j
public class QuartzJob implements Job {
    
    
    @Override
    public void execute(JobExecutionContext context) throws JobExecutionException {
    
    
        //先得到任务,之后就得到map中的名字
        Object name = context.getJobDetail().getJobDataMap().get("name");
        log.info(DateFormatUtils.format(new Date(), "yyyy-MM-dd HH:mm:ss") + " "+ name + "搞卫生");
    }
}

task scheduling method

import lombok.extern.slf4j.Slf4j;
import org.quartz.*;
import org.quartz.impl.StdSchedulerFactory;
import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
import static org.quartz.SimpleScheduleBuilder.simpleSchedule;

@Slf4j
@Component
public class QuartzSheduleTask {
    
    

//    @PostConstruct	// 实例化类后执行该方法
    public void startDheduleTask() throws Exception {
    
    
        // 创建任务
        JobDetail jobDetail = JobBuilder.newJob(QuartzJob.class)
            .withIdentity("任务-A", "任务分组-A")
            .withDescription("开年大扫除")
            .usingJobData("name", "王阿姨")
            .build();

        // 创建触发器
        Trigger trigger = TriggerBuilder.newTrigger()
            .withIdentity("触发器-A", "触发器分组-A")
//            .startAt(new Date())
            .startNow()
            .withSchedule(
                simpleSchedule()
                    .withIntervalInSeconds(5)   // 任务执行间隔
//                    .withRepeatCount(10)      // 任务重复执行次数,-1 为一直执行,缺省值为0
                    .repeatForever()            // 任务一直执行
            )
//            .withSchedule(
//                CronScheduleBuilder.cronSchedule("0/5 * * * * ?")
//            )
            .withDescription("大扫除触发器")
            .build();

        // 实例化调度器工厂
        SchedulerFactory factory = new StdSchedulerFactory();
        // 得到调度器
        Scheduler scheduler = factory.getScheduler();
        // 将触发器和任务绑定到调度器中去
        scheduler.scheduleJob(jobDetail, trigger);
        // 启动调度器
        scheduler.start();
    }

Quartz API

官方API:http://www.quartz-scheduler.org/api/2.3.0/index.html

Overview of the main API interface

The main interfaces of the Quartz API are:

  • Job: The interface that the task logic class needs to implement, defining the content of the task to be scheduled

  • JobDetail (task details): Also known as a task instance, it is used to bind the Job and provides many setting properties for the Job

    The scheduler needs the help of the Jobdetail object to add the Job instance.

  • JobBuilder: used to build JobDetail instances

  • Trigger (trigger): Define the scheduling plan of the task

  • TriggerBuilder: used to build trigger instances

  • Scheduler (task scheduling controller): the main API to interact with the scheduler

  • SchedulerFactory (scheduler factory): create a scheduler instance

  • DateBuilder: It is convenient to construct java.util.Date instances representing different time points

  • Calendar: A collection of calendar-specific points in time. A trigger can contain multiple Calendars to exclude or include certain time points


Job (custom task logic class)

Implement the Job interface

The task class is a class that implements the org.quartz.Job interface, and the task class must have an empty constructor .

When a trigger (trigger) associated with this task instance is triggered, the execute() method will be called by a worker thread of the scheduler (scheduler).

Code example:

public class QuartzJob implements Job {
    
    
    @Override
    public void execute(JobExecutionContext context) throws JobExecutionException {
    
    
        // 任务的具体逻辑
    }
}

JobExecutionContext

The JobExecutionContext object passed to the execute() method

  • It saves some information of the job when it is running, including the reference of the scheduler object that executes the job, the reference of the trigger object that triggers the job, the reference of the JobDetail object, etc.

  • API methods:

    Scheduler getScheduler();
    Trigger getTrigger();
    Calendar getCalendar();
    boolean isRecovering();
    TriggerKey getRecoveringTriggerKey();
    int getRefireCount();
    JobDataMap getMergedJobDataMap();
    JobDetail getJobDetail();
    Job getJobInstance();
    Date getFireTime();
    Date getScheduledFireTime();
    Date getPreviousFireTime();
    Date getNextFireTime();
    String getFireInstanceId();
    Object getResult();
    void setResult(Object var1);
    long getJobRunTime();
    

Get the key value of JobDataMap

  • Method 1: Obtain the JobDetail and Trigger objects through the context in the Job implementation class, and then obtain the JobDataMap object from these two objects

  • Method 2: Obtain MergedJobDataMap directly through context in the Job implementation class (stores the merged key value of jobdetail and trigger)

    It is necessary to ensure that there are no duplicate keys in the JobDataMap of jobDetail and Trigger.

    Because when there is the same key, the one in the jobdetail is obtained first, and then the one in the trigger is obtained, and the value of the key with the same name will be overwritten

  • Method 3: Define the attribute with the same name as the key in the JobDataMap in the Job implementation class, and add a Setter method

    The class is automatically injected, and the bottom layer will use reflection to directly inject the key values ​​in the jobdetail in the scheduler and the jobDataMap in the trigger into the corresponding fields through the Setter method (the names must be the same)

    It is necessary to ensure that there are no duplicate keys in the JobDataMap of jobDetail and Trigger. Same reason as above


JobDetail (task details)

Contains various attribute settings of the job, and the JobDataMap used to store the data of the job object.

The properties of its implementation class:

private String name;			// 任务名称,同分组必须唯一
private String group="DEFAULT";	// 任务分组
private String description;		// 任务描述
private Class<? extends Job> jobClass;	// 任务类
private JobDataMap jobDataMap;			// 任务数据
private boolean durability=false;		// 在没有 Trigger 关联的条件下是否保留
private boolean shouldRecover=false;	// 当任务执行期间,程序被异常终止,程序再次启动时是否再次执行该任务
private transient JobKey key;	// 任务唯一标识,包含任务名称和任务分组信息

Interface API methods:

JobKey getKey();
String getDescription();
Class<? extends Job> getJobClass();
JobDataMap getJobDataMap();
boolean isDurable();		// 获取 durability 的值
boolean isPersistJobDataAfterExecution();	// 任务调用后是否保存任务数据(JobData)
boolean isConcurrentExectionDisallowed();	// 获取任务类是否标注了 @DisallowConcurrentExecution 注解
boolean requestsRecovery();		// 获取 shouldRecover 的值
Object clone();		// 克隆一个 JobDetail 对象
JobBuilder getJobBuilder();

JobBuilder

JobBuilder is used to build JobDetail instances

API methods:

// 创建一个 JobBuilder 对象
public static JobBuilder newJob()
public static JobBuilder newJob(Class<? extends Job> jobClass)

// 设置任务的名称、分组、唯一标识。不指定则会自动生成任务的名称、唯一标识,分组默认DEFUALT
public JobBuilder withIdentity(String name)
public JobBuilder withIdentity(String name, String group)
public JobBuilder withIdentity(JobKey jobKey)

public JobBuilder withDescription(String jobDescription)	// 设置任务描述
public JobBuilder ofType(Class<? extends Job> jobClazz)		// 设置任务类
public JobBuilder requestRecovery()		// 设置 shouldRecover=true
public JobBuilder requestRecovery(boolean jobShouldRecover)
public JobBuilder storeDurably()		// 设置 durability=true
public JobBuilder storeDurably(boolean jobDurability)
public JobBuilder usingJobData(String dataKey, String value)	// jobDataMap.put(dataKey, value)
public JobBuilder usingJobData(JobDataMap newJobDataMap)		// jobDataMap.putAll(newJobDataMap)
public JobBuilder setJobData(JobDataMap newJobDataMap)			// jobDataMap=newJobDataMap

public JobDetail build()

Trigger

A trigger has many properties, which are set when the trigger is built using TriggerBuilder.

Trigger public properties:

/* Trigger 接口中定义 */
int MISFIRE_INSTRUCTION_SMART_POLICY = 0;				// 默认Misfire策略-智能
int MISFIRE_INSTRUCTION_IGNORE_MISFIRE_POLICY = -1;		// Misfire策略-忽略错过的触发
int DEFAULT_PRIORITY = 5;				// 默认触发器优先级

/* AbstractTrigger 抽象类中定义 */
private String name;					// 触发器名称,同分组必须唯一
private String group = "DEFAULT";		// 触发器分组
private String jobName;					// 任务名称
private String jobGroup = "DEFAULT";	// 任务分组
private String description;				// 触发器描述
private JobDataMap jobDataMap;			// 任务数据,用于给 Job 传递一些触发相关的参数
private boolean volatility = false;		// 表示该触发器是否被持久化到数据库存储
private String calendarName;
private String fireInstanceId;
private int misfireInstruction = 0;
private int priority = 5;				// 触发器优先级
private transient TriggerKey key;		// 触发器唯一标识,在一个 Scheduler 中必须是唯一的。
										// 多个触发器可以指向同一个工作,但一个触发器只能指向一个工作。

/* 各触发器实现类中定义 */
private Date startTime;			// 触发器的首次触时间
private Date endTime;			// 触发器的末次触时间,设置了结束时间则在这之后,不再触发
private Date nextFireTime;		// 触发器的下一次触发时间
private Date previousFireTime;	// 触发器的本次触发时间

Trigger interface API method:

TriggerKey getKey();
JobKey getJobKey();
String getDescription();
String getCalendarName();
JobDataMap getJobDataMap();
int getPriority();
boolean mayFireAgain();

Date getStartTime();
Date getEndTime();
Date getNextFireTime();
Date getPreviousFireTime();
Date getFireTimeAfter(Date var1);
Date getFinalFireTime();

int getMisfireInstruction();
int compareTo(Trigger var1);

TriggerBuilder<? extends Trigger> getTriggerBuilder();
ScheduleBuilder<? extends Trigger> getScheduleBuilder();

TriggerBuilder

TriggerBuilder is used to build Trigger instances

TriggerBuilder (and other Quartz builders) choose sensible default values ​​for properties that are not explicitly set.

For example:

  • If the withIdentity(…) method is not called, TriggerBuilder will generate a random name for the trigger

  • If the startAt(…) method is not called, the current time is used by default, that is, the trigger takes effect immediately

public static TriggerBuilder<Trigger> newTrigger()

// 设置触发器的名称、分组、唯一标识,不指定则会自动生成触发器的名称、唯一标识,分组默认DEFUALT
public TriggerBuilder<T> withIdentity(String name)
public TriggerBuilder<T> withIdentity(String name, String group)
public TriggerBuilder<T> withIdentity(TriggerKey triggerKey)

public TriggerBuilder<T> withDescription(String triggerDescription)
public TriggerBuilder<T> withPriority(int triggerPriority)
public TriggerBuilder<T> modifiedByCalendar(String calName)

// 开始时间默认 new Date(),结束时间默认 null
public TriggerBuilder<T> startAt(Date triggerStartTime)
public TriggerBuilder<T> startNow()
public TriggerBuilder<T> endAt(Date triggerEndTime)

// 设置触发器的类别,默认是只执行一次的 SimpleTrigger
public <SBT extends T> TriggerBuilder<SBT> withSchedule(ScheduleBuilder<SBT> schedBuilder)

public TriggerBuilder<T> forJob(JobKey keyOfJobToFire)
public TriggerBuilder<T> forJob(String jobName)
public TriggerBuilder<T> forJob(String jobName, String jobGroup)
public TriggerBuilder<T> forJob(JobDetail jobDetail)
public TriggerBuilder<T> usingJobData(String dataKey, String value)
public TriggerBuilder<T> usingJobData(JobDataMap newJobDataMap)

public T build()

Scheduler (task scheduling controller)

Interface common API methods:

void start()		// 调度器开始工作
void shutdown()		// 关闭调度器
void shutdown(boolean waitForJobsToComplete) 	// 等待所有正在执行的 job 执行完毕后才关闭调度器
void pauseAll()		// 暂停调度器
void resumeAll()	// 恢复调度器

// 绑定/删除触发器和任务
Date scheduleJob(Trigger trigger)
Date scheduleJob(JobDetail jobDetail, Trigger trigger)
void scheduleJob(JobDetail jobDetail, Set<? extends Trigger> triggersForJob, boolean replace)
void scheduleJobs(Map<JobDetail, Set<? extends Trigger>> triggersAndJobs, boolean replace)
void addJob(JobDetail jobDetail, boolean replace)
boolean deleteJobs(List<JobKey> jobKeys)
boolean unscheduleJob(TriggerKey triggerKey)

SchedulerFactory (scheduler factory)

Commonly used API methods:

// 构造方法
public StdSchedulerFactory()
public StdSchedulerFactory(Properties props)
public StdSchedulerFactory(String fileName)

// 获取一个默认的标准调度器
public static Scheduler getDefaultScheduler()
// 获取一个调度器。若调度器工厂未初始化,则获取一个默认的标准调度器
public Scheduler getScheduler()

// 初始化调度器工厂
public void initialize()	// 使用quartz的jar包中默认的quartz.properties文件初始化(默认的标准调度器)
public void initialize(Properties props)
public void initialize(String filename)
public void initialize(InputStream propertiesStream)

Detailed explanation of core modules

reference:

Job class

There are two job types :

  • Stateless (stateless): By default, a new JobDataMap will be created every time it is called

  • Stateful: The @PersistJobDataAfterExecution annotation is marked on the Job implementation class

    Some state information can be held during multiple job calls, which are stored in the JobDataMap


The lifecycle of a Job instance

  1. Every time the scheduler executes a job, a new instance of this class is created before calling its execute(…) method;

  2. After the task is executed, the reference to the instance is discarded, and the instance will be garbage collected;

    Based on this execution strategy, the job must have a no-argument constructor (the call that creates the job instance when using the default JobFactory). In addition, in the job class, no stateful data attributes should be defined, because the values ​​​​of these attributes will not be preserved across multiple executions of the job.

    If you want to add attributes or configurations to the job instance and track the status of the job in multiple executions of the job, you can use JobDataMap (part of the JobDetail object)


@DisallowConcurrentExecution annotation: prohibits concurrent execution

  • Quartz scheduled tasks are executed concurrently by default, and will not wait for the last task to be executed, and will be executed as soon as the interval expires. If the scheduled task is executed for too long, resources will be occupied for a long time, causing other tasks to be blocked.

  • Prevent concurrency by marking the @DisallowConcurrentExecution annotation on the Job implementation class.

    The @DisallowConcurrentExecution annotation prohibits concurrent execution of multiple JobDetails of the same definition. This annotation is added to the Job class, but it does not mean that multiple Jobs cannot be executed at the same time, but that the same Job Definition (defined by JobDetail) cannot be executed concurrently, but multiple different JobDetails can be executed at the same time.

    For example: There is a Job class called SayHelloJob, and this annotation is added to this Job, and then many JobDetails are defined on this Job, such as sayHelloToJoeJobDetail, sayHelloToMikeJobDetail, then when the scheduler starts, multiple sayHelloToJoeJobDetail or sayHelloToMikeJobDetail will not be executed concurrently, but can be executed at the same time Execute sayHelloToJoeJobDetail and sayHelloToMikeJobDetail.

    Test code: The set time interval is 3 seconds, but the job execution time is 5 seconds. After setting @DisallowConcurrentExecution, the program will wait for the task to be executed before executing it, otherwise it will start a new thread for execution in 3 seconds.


@PersistJobDataAfterExecution annotation: save JobDataMap

  • This annotation is added to the Job implementation class. Indicates that after the normal execution of the Job, the data in the JobDataMap should be changed for the next call.

  • **Note:** When using the @PersistJobDataAfterExecution annotation, in order to avoid confusion when storing data during concurrency, it is strongly recommended to add the @DisallowConcurrentExecution annotation.


JobDetail (task details)

You can create only one job class, and then create multiple JobDetail instances associated with the job, each instance has its own attribute set and JobDataMap, and finally, add all instances to the scheduler.

When a trigger is triggered, the associated JobDetail instance will be loaded, and the job class referenced by JobDetail is initialized through the JobFactory configured on the Scheduler.

  • The default implementation of JobFactory just calls the newInstance() method of the job class, and then tries to call the setter method of the key in the JobDataMap
  • You can also customize the JobFactory implementation, such as allowing IOC or DI containers to create/initialize job instances

Descriptive names for Job and JobDetail

  • In Quartz's description language, the saved JobDetail is usually called "job definition" or "JobDetail instance", and an executing job is called "job instance" or "job definition instance".

  • When using "job", generally refers to the job definition or JobDetail

  • When referring to a class that implements the Job interface, it is common to use "job class"


Through the JobDetail object, other attributes that can be configured for the job instance are:

  • Durability attribute: Indicates whether to retain if there is no Trigger association, boolean type

    If a JobDetail is non-persistent, it is automatically removed from the scheduler when there are no active triggers associated with it.

    In other words, the life cycle of a non-persistent JobDetail is determined by the presence or absence of a trigger

  • requestsRecovery attribute: identifies whether the program is terminated abnormally during task execution, and whether to execute the task again when the program restarts, boolean type

    If a job is recoverable and the scheduler is hard shut down during its execution (such as a running process crashing or shutting down), the job will be re-executed when the scheduler restarts.


Trigger

overview

Used to trigger the execution of the Job. When preparing to schedule a job, you need to create an instance of Trigger and set scheduling-related properties.

Note: A Trigger can only be bound to one JobDetail, and a JobDetail can be bound to multiple Triggers.


Trigger priority (priority)

If there are many triggers (or too few worker threads in the Quartz thread pool), Quartz may not have enough resources to trigger all triggers at the same time. In this case, you can use the worker threads of Quartz first by setting the priority (priority) attribute of the trigger.

  • The priority is only meaningful when the trigger time of the trigger is the same
  • The priority value of the trigger defaults to 5
  • When a task requests to resume execution, its priority is the same as the original priority

Miss Fire Instructions

  • If the scheduler is closed, or there are no available threads in the Quartz thread pool to execute the job, the persistent trigger will miss (miss) its trigger time, that is, miss the trigger (misfire).
  • Different types of triggers have different misfire mechanisms. By default, the "smart policy" is used, that is, the behavior is dynamically adjusted according to the type and configuration of the trigger.
  • When the scheduler starts, it queries all persistent triggers that have missed triggers (misfire); then updates the trigger information according to their respective misfire mechanisms.

Classification of triggers

Quartz comes with various types of Triggers, the most commonly used ones are SimpleTrigger and CronTrigger.

The main properties of the trigger instance are set through TriggerBuilder, and the unique properties of various types of triggers are set through the TriggerBuilder.withSchedule() method.

  • SimpleTrigger (simple trigger)

    One of the most basic triggers. Execute once at a specific time point, or execute at a specific time point and repeat it several times at specified intervals.

    The properties of SimpleTrigger include start time, end time, repeat count and repeat interval

    • repeatInterval attribute: repeat interval, default is 0
    • repeatCount attribute: the number of repetitions, the default is 0, the actual number of executions is repeatCount + 1

    Notice:

    • If the repeat interval is 0, the trigger will be executed concurrently with the number of repetitions (or the approximate number of concurrency that the scheduler can handle)

    • The value of the endTime attribute overrides the value of the attribute that sets the number of repetitions

      That is, you can create a trigger that executes every 10 seconds before the end time. You don't need to calculate the number of repetitions between the start time and the end time. You only need to set the end time and set the number of repetitions to REPEAT_INDEFINITELY (repeat indefinitely)

    Trigger trigger = TriggerBuilder.newTrigger()       
        .withSchedule(            
        	SimpleScheduleBuilder.simpleSchedule()
        	//.withIntervalInHours(1) 	// 每小时执行一次
        	//.withIntervalInMinutes(1) // 每分钟执行一次
        	.repeatForever() 			// 次数不限
        	.withRepeatCount(10) 		// 次数为10次
        )       
        .build();
    

    SimpleTrigger's Misfire policy constants:

    // 触发器默认Misfire策略常量,SimpleTrigger会根据实例的配置及状态,在所有MISFIRE策略中动态选择一种Misfire策略
    int MISFIRE_INSTRUCTION_SMART_POLICY = 0;
    
    int MISFIRE_INSTRUCTION_IGNORE_MISFIRE_POLICY = -1;
    int MISFIRE_INSTRUCTION_FIRE_NOW = 1;
    int MISFIRE_INSTRUCTION_RESCHEDULE_NOW_WITH_EXISTING_REPEAT_COUNT = 2;
    int MISFIRE_INSTRUCTION_RESCHEDULE_NOW_WITH_REMAINING_REPEAT_COUNT = 3;
    int MISFIRE_INSTRUCTION_RESCHEDULE_NEXT_WITH_REMAINING_COUNT = 4;
    int MISFIRE_INSTRUCTION_RESCHEDULE_NEXT_WITH_EXISTING_COUNT = 5;
    
  • CronTrigger (trigger based on cron expressions)

    Trigger trigger = TriggerBuilder.newTrigger()  
        .withSchedule(CronScheduleBuilder.cronSchedule("2 * * * * *"))
        .build();
    

    CronTrigger's Misfire policy constants:

    // 触发器默认Misfire策略常量,由CronTrigger解释为MISFIRE_INSTRUCTION_FIRE_NOW
    int MISFIRE_INSTRUCTION_SMART_POLICY = 0;
    
    int MISFIRE_INSTRUCTION_IGNORE_MISFIRE_POLICY = -1;
    int MISFIRE_INSTRUCTION_FIRE_ONCE_NOW = 1;
    int MISFIRE_INSTRUCTION_DO_NOTHING = 2;
    
  • CalendarIntervalTrigger (calendar time interval trigger)

    Similar to SimpleTrigger, but the difference is that the underlying time interval specified by SimpleTrigger is milliseconds, and the interval units supported by CalendarIntervalTrigger are seconds, minutes, hours, days, months, years, and weeks.

    The properties of CalendarIntervalTrigger are:

    • interval : execution interval
    • intervalUnit : the unit of the execution interval (seconds, minutes, hours, days, months, years, weeks)
    Trigger trigger = TriggerBuilder.newTrigger()  
        .withSchedule(
        	calendarIntervalSchedule()
            .withIntervalInDays(1) //每天执行一次
            //.withIntervalInWeeks(1) //每周执行一次
    	)
        .build();
    
  • DailyTimeIntervalTrigger (daily time interval trigger)

    Support time interval type and specified week

    Common attributes:

    • startTimeOfDay : start time of day
    • endTimeOfDay : the end time of each day
    • daysOfWeek : the day of the week that needs to be executed
    • interval : execution interval
    • intervalUnit : the unit of the execution interval (seconds, minutes, hours, days, months, years, weeks)
    • repeatCount: number of repetitions
    Trigger trigger = TriggerBuilder.newTrigger()  
        .withSchedule(
    		dailyTimeIntervalSchedule()
                .startingDailyAt(TimeOfDay.hourAndMinuteOfDay(9, 0)) 	// 第天9:00开始
                .endingDailyAt(TimeOfDay.hourAndMinuteOfDay(15, 0)) 	// 15:00 结束 
                .onDaysOfTheWeek(MONDAY,TUESDAY,WEDNESDAY,THURSDAY,FRIDAY) // 周一至周五执行
                .withIntervalInHours(1) 	// 每间隔1小时执行一次
                .withRepeatCount(100) 		// 最多重复100次(实际执行100+1次)
    	)
        .build();
    

Exclude time slots from schedules

The org.quartz.Calendar object is used to exclude time periods from the trigger's schedule.

For example, you can create a trigger that is executed at 9:30 am every weekday, and then add a Calendar to exclude all commercial holidays.

Any serializable object that implements the org.quartz.Calendar interface can be used as a Quartz Calendar object.

Calendar interface implementation class provided by Quartz:

  • BaseCalender: implements basic functions for advanced Calender, and implements org.quartz.Calender interface
  • WeeklyCalendar : excludes one or more days of the week, e.g. can be used to exclude weekends
  • MonthlyCalendar : Excludes days in the month, for example, can be used to exclude the last day of each month
  • AnnualCalendar : exclude one or more days of the year
  • HolidayCalendar : specially used to exclude holidays from Trigger

How to use

  • Instantiate Calendar and add the dates to be excluded
  • Register the Calendar instance to the scheduler through the addCalendar() method
  • When defining the trigger, associate the Calendar instance with the trigger instance

Example:

// 2014-8-15这一天不执行任何任务
AnnualCalendar cal = new AnnualCalendar();
cal.setDayExcluded(new GregorianCalendar(2014, 7, 15), true);
// 注册日历到调度器
scheduler.addCalendar("excludeCalendar", cal, false, false);

TriggerBuilder.newTrigger().modifiedByCalendar("excludeCalendar")....

Scheduler

Reference: Understanding and use of Scheduler in Quartz

overview

The Scheduler is the heart of the Quartz framework, used to manage triggers and Jobs, and to ensure that Jobs can be triggered to execute. The calls between the programmer and the framework are all done through the org.quartz.Scheduler interface.

The implementation of the Scheduler interface is actually just a proxy of the core scheduler (org.quartz.core.QuartzScheduler), and when the method of the proxy is called, it will be passed to the underlying core scheduler instance. QuartzScheduler is at the root of the Quartz framework and drives the entire Quartz framework.


Schedule type

There are three types:

  • StdScheduler (standard default scheduler): most commonly used

    The default value loaded is the "quartz.properties" properties file in the current working directory. If the loading fails, the "quartz.properties" property file of the org/quartz package will be loaded.

  • RemoteScheduler (remote scheduler)

  • RemoteMBeanScheduler


Scheduler has two important components:

  • ThreadPool: Scheduler thread pool, all tasks will be executed by the thread pool

  • JobStore: stores runtime information, including Trigger, Scheduler, JobDetail, business locks, etc.

    The implementation of JobStore includes RAMJob (memory implementation), JobStoreTX (JDBC, transactions are managed by Quartz), JobStoreCMT (JDBC, using container transactions), ClusteredJobStore (cluster implementation), etc.

    Note: Job and Trigger need to be stored before they can be used


Schedule factory

Used to create Schedule instances

There are two types:

  • StdSchedulerFactory (most commonly used)
  • DirectSchedulerFactory

Scheduler life cycle

  • Starts when the SchedulerFactory creates it and ends when the Scheduler calls the shutdown() method

  • After the Scheduler is created, you can add, delete, and enumerate Jobs and Triggers, and perform other scheduling-related operations (such as suspending Triggers).

    However, the Scheduler will actually trigger the trigger (i.e. execute the job) only after calling the start() method.


Quartz Thread Classification

  • Scheduler scheduling thread

    There are:

    • Regular Scheduler Thread (Regular Scheduler Thread): poll all the triggers stored in the query, and when the trigger time is reached, obtain an idle thread from the thread pool to execute the tasks associated with the triggers

    • Misfire Scheduler Thread: Misfire thread scans all triggers to check whether there are misfired threads, that is, threads that have not been missed by execution. If there are any, they are processed separately according to the misfire strategy

  • task execution thread


Create a scheduler

StdScheduler only provides a parameterized construction method, which needs to pass two instance parameters of QuartzScheduler and SchedulingContext.

Generally, the constructor is not used to create the scheduler, but is created through the scheduler factory.

The scheduler factory interface org.quartz.SchedulerFactory provides two different types of factory implementations, namely org.quartz.impl.DirectSchedulerFactoryh and org.quartz.impl.StdSchedulerFactory.

  • Created using the StdSchedulerFactory factory

    This factory relies on a series of properties to determine how to create scheduler instances.

    Get the default standard scheduler:

    • This factory provides a parameterless initialize() method for initialization. The essence of this method is to load the default quartz.properties property file in the jar package of quartz. The specific loading steps are:
      1. Check whether the file name is set in the system properties, through System.getProperty("org.quartz.properties")
      2. If not set, use the default quartz.properties as the filename to load
      3. Then load this file from the current working directory first, if not found, then load this file from the system classpath
    • The StdSchedulerFactory factory can also not actively call the initialize() method for initialization, but directly use the static method getDefaultScheduler() of StdSchedulerFactory to obtain a default standard scheduler.

    Get a custom scheduler:

    There are three ways to provide custom attributes:

    • Through the java.util.Properties property instance
    • Provided via an external properties file
    • Provided via java.io.InputStream file stream with property file content
        public static void main(String[] args) {
          
          
            try {
          
          
                StdSchedulerFactory schedulerFactory = new StdSchedulerFactory();
                
                // 第一种方式 通过Properties属性实例创建
                Properties props = new Properties();
                props.put(StdSchedulerFactory.PROP_THREAD_POOL_CLASS, 
                          "org.quartz.simpl.SimpleThreadPool");
                props.put("org.quartz.threadPool.threadCount", 5);
                schedulerFactory.initialize(props);
                
                // 第二种方式 通过传入文件名
                // schedulerFactory.initialize("my.properties");
                
                // 第三种方式 通过传入包含属性内容的文件输入流
                // InputStream is = new FileInputStream(new File("my.properties"));
                // schedulerFactory.initialize(is);
    
                // 获取调度器实例
                Scheduler scheduler = schedulerFactory.getScheduler();
                
            } catch (Exception e) {
          
          
                e.printStackTrace();
            }
        }
    
    • The first method passes in two properties to the factory, namely the class name of the thread pool and the size of the thread pool. These two properties are necessary, because if the custom Properties property is used for initialization, the factory does not specify default values ​​for them.

    • The second way is by defining an external properties file

      The underlying implementation is: first obtain the file stream through Thread.currentThread().getContextClassLoader().getResourceAsStream(filename), then use the load method of the Properties instance to load the file stream to form a property instance, and finally complete the initialization through initialize(props).

    • The third way is to directly use the load method of the Properties instance to load the file stream to form a property instance, and then complete the initialization through initialize(props).

  • Created using the DirectSchedulerFactory factory

    This factory method is suitable for scenarios where you want to have absolute control over the Scheduler instance.

      public static void main(String[] args) {
          
          
          try {
          
          
            DirectSchedulerFactory schedulerFactory = DirectSchedulerFactory.getInstance();
            // 表示以3个工作线程初始化工厂
            schedulerFactory.createVolatileScheduler(3);
            Scheduler scheduler = schedulerFactory.getScheduler();  
          } catch (SchedulerException e) {
          
          
            e.printStackTrace();
          }
      }
    

    Create steps:

    1. Get the instance through the getInstance method of DirectSchedulerFactory
    2. Call the createXXX method to initialize the factory
    3. Call the getScheduler method of the factory instance to get the scheduler instance

    DirectSchedulerFactory initializes the factory by passing configuration parameters through the createXXX method. This initialization method is hard-coded and is rarely used in work.


Manage Scheduler

After getting the scheduler instance, you can do the following work in the scheduling life cycle, for example: start the scheduler, set the scheduler to standby mode, continue or stop the scheduler.

  • Start Scheduler

    When the scheduler is initialized and the Job and Trigger are registered, you can call scheduler.start() to start the scheduler.

    Once the start() method is called, the scheduler starts searching for jobs that need to be executed.

  • Set standby mode

    Setting the Scheduler to standby mode will cause the scheduler to pause looking for jobs to execute.

    Application scenario example: When the database needs to be restarted, the scheduler can be set to standby mode first, and then the scheduler can be started through start() after the database startup is complete.

  • close scheduler

    Call the shutdown() or shutdown(boolean waitForJobsToComplete) method to stop the scheduler.

    After the shutdown method is called, the start method cannot be called anymore, because the shutdown method will destroy all resources (threads, database connections, etc.) created by the Scheduler.

In general, the scheduler does not need to do anything else after it starts.


Job Stores

Quartz provides two basic types of job storage:

  • RAMJobStore (memory task storage): By default, Quartz will store task scheduling in memory

    This way performance is the best, because the memory speed is the fastest

    The disadvantage is that the data lacks persistence. When the program crashes or is re-released, all running information will be lost

  • JDBCJobStore (database job store)

    After saving to the database, it can be done as a single point or as a cluster

    When there are more tasks, they can be managed uniformly (stop, suspend, modify tasks)

    Shut down or restart the server, the running information will not be lost

    The disadvantage is that the speed of operation depends on the speed of connecting to the database


Quartz comes with a database schema

Database script address:

  • https://gitee.com/qianwei4712/code-of-shiva/blob/master/quartz/quartz.sql (with comments, gitee)
  • https://lqcoder.com/quartz.sql (without comments, download link)
Table Name Description
QRTZ_CALENDARS Store Quartz's Calendar information
QRTZ_CRON_TRIGGERS Stores CronTriggers, including Cron expressions and time zone information
QRTZ_FIRED_TRIGGERS Store the status information related to the triggered Trigger, as well as the execution information of the associated Job
QRTZ_PAUSED_TRIGGER_GRPS Stores information about paused Trigger groups
QRTZ_SCHEDULER_STATE Store a small amount of state information about the Scheduler, and other Scheduler instances
QRTZ_LOCKS Stored program pessimistic locking information
QRTZ_JOB_DETAILS Store the details of each configured Job
QRTZ_JOB_LISTENERS Stores information about configured JobListeners
QRTZ_SIMPLE_TRIGGERS Store simple Triggers, including repetitions, intervals, and the number of touches
QRTZ_BLOG_TRIGGERS Trigger is stored as a Blob type
QRTZ_TRIGGER_LISTENERS Store information about configured TriggerListeners
QRTZ_TRIGGERS Store configured Trigger information

Listeners (event listeners)

Listeners: Used to perform actions based on events occurring in the scheduler.

Trigger, Job, Scheduler monitoring interface

  • TriggerListeners : Receive events related to triggers

    Trigger-related events include: trigger trigger, trigger failure, trigger completion (trigger off)

    org.quartz.TriggerListener interface:

    public interface TriggerListener {
          
          
        public String getName();
    	//触发器触发
        public void triggerFired(Trigger trigger, JobExecutionContext context);
        public boolean vetoJobExecution(Trigger trigger, JobExecutionContext context);
    	//触发失灵
        public void triggerMisfired(Trigger trigger);
    	//触发器完成
        public void triggerComplete(Trigger trigger, JobExecutionContext context, 
                                    int triggerInstructionCode);
    }
    
  • JobListeners : receive events related to jobs

    Job-related events include: notifications that a job is about to execute, and notifications when a job has completed execution.

    org.quartz.JobListener interface:

    public interface JobListener {
          
          
        public String getName();
        public void jobToBeExecuted(JobExecutionContext context);
        public void jobExecutionVetoed(JobExecutionContext context);
        public void jobWasExecuted(JobExecutionContext context, JobExecutionException jobException);
    }
    
  • SchedulerListeners

    Events related to the scheduler include: adding a job/trigger, deleting a job/trigger, critical errors in the scheduler, notifications to shut down the scheduler, etc.

    org.quartz.SchedulerListener interface:

    public interface SchedulerListener {
          
          
        public void jobScheduled(Trigger trigger);
        public void jobUnscheduled(String triggerName, String triggerGroup);
        public void triggerFinalized(Trigger trigger);
        public void triggersPaused(String triggerName, String triggerGroup);
        public void triggersResumed(String triggerName, String triggerGroup);
        public void jobsPaused(String jobName, String jobGroup);
        public void jobsResumed(String jobName, String jobGroup);
        public void schedulerError(String msg, SchedulerException cause);
        public void schedulerStarted();
        public void schedulerInStandbyMode();
        public void schedulerShutdown();
        public void schedulingDataCleared();
    }
    

Custom Listeners

step:

  1. Customize a class that implements org.quartz.TriggerListener or JobListener, SchedulerListener interface

    For convenience, the custom listener can also inherit the implementation class of its sub-interface JobListenerSupport or TriggerListenerSupport, SchedulerListenerSupport, and override the event of interest.

  2. Register the listener with the scheduler, and configure the job or trigger Matcher that the listener wants to receive events

    Note: listeners are not stored in the JobStore along with jobs and triggers. This is because the listener is usually the point of integration with the application, so every time the application is run, the dispatcher needs to be re-registered.

Example:

  • Add a JobListener interested in all jobs:

    scheduler.getListenerManager().addJobListener(myJobListener, allJobs());
    
  • Add a JobListener interested in a specific job:

    scheduler.getListenerManager().addJobListener(
        myJobListener,KeyMatcher.jobKeyEquals(new JobKey("myJobName""myJobGroup")));
    
  • Add a JobListener interested in all jobs of a particular group:

    scheduler.getListenerManager().addJobListener(myJobListener, jobGroupEquals("myJobGroup"));
    
  • Add a JobListener interested in all jobs of two specific groups:

    scheduler.getListenerManager().addJobListener(
        myJobListener, or(jobGroupEquals("myJobGroup"), jobGroupEquals("yourGroup")));
    
  • Add SchedulerListener:

    scheduler.getListenerManager().addSchedulerListener(mySchedListener);
    
  • Remove the SchedulerListener:

    scheduler.getListenerManager().removeSchedulerListener(mySchedListener);
    

SchedulerFactoryBean (Spring Integration)

reference:

Quartz's SchedulerFactory is a standard factory class, which is not suitable for use in the Spring environment.

In order to ensure that the Scheduler can perceive the life cycle of the Spring container and complete the automatic startup and shutdown operations, the Scheduler must be associated with the life cycle of the Spring container. So that after the Spring container is started, the Scheduler will automatically start working, and before the Spring container is closed, the Scheduler will be automatically closed. To this end, Spring provides SchedulerFactoryBean , which roughly has the following functions:

  • Provide configuration information for Scheduler in a more Bean-style way
  • Let the Scheduler and the life cycle of the Spring container be associated, and they are closely related
  • Partially or completely replace Quartz's own configuration file through property configuration

SchedulerFactoryBean property introduction:

  • autoStartup attribute: Whether to start the Scheduler immediately after the SchedulerFactoryBean is initialized, the default is true

    If set to false, Scheduler needs to be started manually

  • startupDelay attribute: After the initialization of SchedulerFactoryBean is completed, how many seconds are delayed to start the Scheduler. The default is 0, which means to start immediately.

    If you don't have tasks that need to be executed immediately, you can use the startupDelay attribute to delay the Scheduler for a short period of time to start, so that Spring can initialize the remaining beans in the container faster

  • Triggers attribute: the type is Trigger[], multiple Triggers can be registered through this attribute

  • calendars attribute: the type is Map, through which the Calendar is registered with the Scheduler

  • jobDetails attribute: the type is JobDetail[], through which the JobDetail is registered with the Scheduler

  • schedulerContextMap attribute: You can store some data in the Scheduler context through this attribute

    The bean in the spring container can only be placed in the SchedulerContext and passed into the job through the setSchedulerContextAsMap() method of SchedulerFactoryBean, and the stored bean can be obtained in the job through JobExecutionContext.getScheduler().getContext()


An important function of the SchedulerFactoryBean is to allow information from Quartz configuration files to be transferred to Spring configuration files

The benefit is the centralized management of configuration information, and developers do not need to be familiar with the configuration file structure of various frameworks. Recall that Spring integrates JPA and Hibernate frameworks, and you will know that this is one of the tricks Spring often uses in integrating third-party frameworks.

The SchedulerFactoryBean replaces the framework's own configuration file with the following properties:

  • dataSource attribute: When you need to use the database to persist task scheduling data, you can configure the data source in Quartz

    You can also specify a Spring-managed data source directly in Spring through the setDataSource() method

    If this property is specified, even if the data source has been defined in quartz.properties, it will be overwritten by this dataSource

    After configuring the data source dataSource and starting the application, the following data will be automatically inserted into the QRTZ_LOCKS table of Quartz:

    INSERT INTO QRTZ_LOCKS values('TRIGGER_ACCESS');
    INSERT INTO QRTZ_LOCKS values('JOB_ACCESS');
    
  • transactionManager attribute: You can set a Spring transaction manager through this attribute.

    When setting dataSource, Spring strongly recommends using a transaction manager, otherwise data table locking may not work properly;

  • nonTransactionalDataSource attribute: In the case of a global transaction, if you do not want the Scheduler to execute the data operation to participate in the global transaction, you can specify the data source through this attribute.

    In the case of Spring native transactions, it is sufficient to use the dataSource property

  • quartzProperties attribute: the type is Properties, allowing developers to define Quartz properties in Spring.

    Its value will override the settings in the quartz.properties configuration file. These properties must be legal properties that Quartz can recognize. When configuring, you may need to check the relevant documents of Quartz.


SchedulerFactoryBean implements the InitializingBean interface

Therefore, when the bean is initialized, the afterPropertiesSet() method will be executed, which will call the SchedulerFactory (DirectSchedulerFactory or StdSchedulerFactory) method to create a Scheduler, usually using StdSchedulerFactory.

During the process of creating quartzScheduler, SchedulerFactory will read configuration parameters and initialize each component. The key components are as follows:

  • ThreadPool: generally use SimpleThreadPool

    SimpleThreadPool creates a certain number of WorkerThread instances to enable Jobs to be processed in threads.

    • WorkerThread is an internal class defined in the SimpleThreadPool class, which is essentially a thread

    There are three lists in SimpleThreadPool:

    • workers : store all thread references in the pool
    • availWorkers : store all idle threads
    • busyWorkers : stores all working threads

    The configuration parameters of the thread pool are as follows:

    org.quartz.threadPool.class=org.quartz.simpl.SimpleThreadPool
    org.quartz.threadPool.threadCount=3 
    org.quartz.threadPool.threadPriority=5
    
  • JobStore: It is divided into RAMJobStore stored in memory and JobStoreSupport stored in database

    JobStoreSupport includes two implementations of JobStoreTX and JobStoreCMT

    • JobStoreCMT relies on containers for transaction management
    • JobStoreTX manages transactions by itself

    If you want to use the cluster, you need to use the JobStoreSupport method

  • QuartzSchedulerThread: thread used for task scheduling

    Paused=true, halted=false during initialization. That is, although the thread starts to run, but paused=true, the thread will wait until the start() method is called to set paused to false


SchedulerFactoryBean implements the SmartLifeCycle interface

Therefore, after the initialization is completed, the start() method will be executed, which will mainly perform the following actions:

  1. Create a ClusterManager thread and start the thread: this thread is used for cluster fault detection and processing
  2. Create a MisfireHandler thread and start the thread: this thread is used to process misfire tasks
  3. Set the paused=false of QuartzSchedulerThread, the scheduling thread will actually start scheduling

The entire startup process of SchedulerFactoryBean is as follows:

insert image description here


Enterprise-level practical cases

Reference: Task Scheduling Framework Quartz Usage Guide

Environmental preparation

Dependency, application configuration file

rely

<!--  Quartz 任务调度 -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-quartz</artifactId>
</dependency>

application configuration file:

# 开发环境配置
server:
  # 服务器的HTTP端口
  port: 80
  servlet:
    # 应用的访问路径
    context-path: /
  tomcat:
    # tomcat的URI编码
    uri-encoding: UTF-8
 
spring:
  datasource:
    username: root
    password: root
    url: jdbc:mysql://127.0.0.1:3306/quartz?useUnicode=true&characterEncoding=utf-8&useSSL=true
    driver-class-name: com.mysql.cj.jdbc.Driver

Quartz database script

Quartz comes with a database schema

  • Script ready-made script: https://gitee.com/qianwei4712/code-of-shiva/blob/master/quartz/quartz.sql

  • This article uniformly uses the Cron method to create

    Note: The 4 data tables needed for the cron method: qrtz_triggers, qrtz_cron_triggers, qrtz_fired_triggers, qrtz_job_details

  • Additional database table for saving tasks:

    DROP TABLE IF EXISTS quartz_job;
    CREATE TABLE quartz_job (
      job_id bigint(20) NOT NULL AUTO_INCREMENT COMMENT '任务ID',
      job_name varchar(64) NOT NULL DEFAULT '' COMMENT '任务名称',
      job_group varchar(64) NOT NULL DEFAULT 'DEFAULT' COMMENT '任务组名',
      invoke_target varchar(500) NOT NULL COMMENT '调用目标字符串',
      cron_expression varchar(255) DEFAULT '' COMMENT 'cron执行表达式',
      misfire_policy varchar(20) DEFAULT '3' COMMENT '计划执行错误策略(1立即执行 2执行一次 3放弃执行)',
      concurrent char(1) DEFAULT '1' COMMENT '是否并发执行(0允许 1禁止)',
      status char(1) DEFAULT '0' COMMENT '状态(0正常 1暂停)',
      remark varchar(500) DEFAULT '' COMMENT '备注信息',
      PRIMARY KEY (job_id),
      UNIQUE INDEX quartz_job_unique(job_id, job_name, job_group)
    ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8 COMMENT='定时任务调度表';
    
    import lombok.AllArgsConstructor;
    import lombok.Builder;
    import lombok.Data;
    import lombok.NoArgsConstructor;
    import org.hibernate.annotations.DynamicInsert;
    import org.hibernate.annotations.DynamicUpdate;
    import javax.persistence.*;
    import java.io.Serializable;
    
    @Data
    @Builder
    @NoArgsConstructor
    @AllArgsConstructor
    @Entity
    @DynamicInsert
    @DynamicUpdate
    @Table(name="quartz_job")
    public class QuartzJob implements Serializable {
          
          
        public static final long serialVersionUID = 42L;
    
        /**
         * 任务ID
         */
        @Id
        @GeneratedValue(strategy = GenerationType.AUTO)
        @Column(name = "job_id", nullable = false)
        private Long jobId;
    
        
        /**
         * 任务名称
         */
        @Column(name = "job_name", length = 64, nullable = false)
        private String jobName;
    
        
        /**
         * 任务组名
         */
        @Column(name = "job_group", length = 64, nullable = false)
        private String jobGroup;
    
        /**
         * 调用目标字符串
         */
        @Column(name = "invoke_target", length = 500, nullable = false)
        private String invokeTarget;
    
        /**
         * cron执行表达式
         */
        @Column(name = "cron_expression", length = 255)
        private String cronExpression;
    
        /**
         * 计划执行错误策略(1立即执行 2执行一次 3放弃执行)
         */
        @Column(name = "misfire_policy", length = 20)
        private String misfirePolicy;
    
        /**
         * 是否并发执行(0允许 1禁止)
         */
        @Column(name = "concurrent")
        private String concurrent;
    
        /**
         * 状态(0正常 1暂停)
         */
        @Column(name = "status")
        private String status;
    
        /**
         * 备注信息
         */
        @Column(name = "remark", length = 500)
        private String remark;
    }
    

SprinUtils tool class

Role: Get the beans in the ApplicationContext

import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;

/**
 * SpringUtils工具类:获取bean
 */
@Component
public class SpringUtils implements ApplicationContextAware {
    
    
    private static ApplicationContext applicationContext = null;

    @Override
    public void setApplicationContext(ApplicationContext arg0) throws BeansException {
    
    
        if (SpringUtils.applicationContext == null) {
    
    
            SpringUtils.applicationContext = arg0;
        }
    }

    // 获取applicationContext
    public static ApplicationContext getApplicationContext() {
    
    
        return applicationContext;
    }

    // 通过name获取 Bean.
    public static Object getBean(String name) {
    
    
        return getApplicationContext().getBean(name);
    }

    // 通过class获取Bean.
    public static <T> T getBean(Class<T> clazz) {
    
    
        return getApplicationContext().getBean(clazz);
    }

    // 通过name,以及Clazz返回指定的Bean
    public static <T> T getBean(String name, Class<T> clazz) {
    
    
        return getApplicationContext().getBean(name, clazz);
    }
}

ScheduleConstants static variable class

static variable class

import lombok.AllArgsConstructor;
import lombok.Getter;

public class ScheduleConstants {
    
    
    // 计划执行错误策略-默认策略
    public static final String MISFIRE_DEFAULT = "0";
    // 计划执行错误策略-立即执行(立即执行执行所有misfire的任务)
    public static final String MISFIRE_IGNORE_MISFIRES = "1";
    // 计划执行错误策略-执行一次(立即执行一次任务)
    public static final String MISFIRE_FIRE_AND_PROCEED = "2";
    // 计划执行错误策略-放弃执行(什么都不做,等待下次触发)
    public static final String MISFIRE_DO_NOTHING = "3";

    // 任务实例名称
    public static final String TASK_CLASS_NAME = "TASK_CLASS_NAME";
    // 任务内容
    public static final String TASK_PROPERTIES = "TASK_PROPERTIES";


    @AllArgsConstructor
    @Getter
    public enum Status {
    
    

        NORMAL("0"),
        PAUSE("1");

        private String value;
    }
}

task method

Prepare a task method (called later by reflection, so there is no need to implement the Job interface here):

import lombok.extern.slf4j.Slf4j;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;

@Slf4j
@Component("mysqlJob")
public class MysqlJob {
    
    
    protected final Logger logger = LoggerFactory.getLogger(this.getClass());
    public void execute(String param) {
    
    
        logger.info("执行 Mysql Job,当前时间:{},任务参数:{}", LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")), param);
    }
}

ScheduleConfig configuration code class

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.quartz.SchedulerFactoryBean;
import javax.sql.DataSource;
import java.util.Properties;

@Configuration
public class ScheduleConfig {
    
    

    /**
    * SchedulerFactoryBean 继承了 InitializingBean 接口会在类被注入Spring容器后执行 afterPropertiesSet 方法
    */
    @Bean
    public SchedulerFactoryBean schedulerFactoryBean(DataSource dataSource) {
    
    
        SchedulerFactoryBean factory = new SchedulerFactoryBean();
        factory.setDataSource(dataSource);

        // quartz参数
        Properties prop = new Properties();
        prop.put("org.quartz.scheduler.instanceName", "shivaScheduler");
        prop.put("org.quartz.scheduler.instanceId", "AUTO");
        // 线程池配置
        prop.put("org.quartz.threadPool.class", "org.quartz.simpl.SimpleThreadPool");
        prop.put("org.quartz.threadPool.threadCount", "20");
        prop.put("org.quartz.threadPool.threadPriority", "5");
        // JobStore配置
        prop.put("org.quartz.jobStore.class", "org.quartz.impl.jdbcjobstore.JobStoreTX");
        // 集群配置
        prop.put("org.quartz.jobStore.isClustered", "true");
        prop.put("org.quartz.jobStore.clusterCheckinInterval", "15000");
        prop.put("org.quartz.jobStore.maxMisfiresToHandleAtATime", "1");
        prop.put("org.quartz.jobStore.txIsolationLevelSerializable", "true");

        // sqlserver 启用
        // prop.put("org.quartz.jobStore.selectWithLockSQL", "SELECT * FROM {0}LOCKS UPDLOCK WHERE LOCK_NAME = ?");
        prop.put("org.quartz.jobStore.misfireThreshold", "12000");
        prop.put("org.quartz.jobStore.tablePrefix", "QRTZ_");
        factory.setQuartzProperties(prop);

        factory.setSchedulerName("shivaScheduler");
        // 延时启动
        factory.setStartupDelay(1);
        factory.setApplicationContextSchedulerContextKey("applicationContextKey");
        // 可选,QuartzScheduler
        // 启动时更新己存在的Job,这样就不用每次修改targetObject后删除qrtz_job_details表对应记录了
        factory.setOverwriteExistingJobs(true);
        // 设置自动启动,默认为true
        factory.setAutoStartup(true);

        return factory;
    }
}

ScheduleUtils scheduling tool class

The core code

import org.quartz.*;

public class ScheduleUtils {
    
    
    /**
     * 得到quartz任务类
     *
     * @param job 执行计划
     * @return 具体执行任务类
     */
    private static Class<? extends Job> getQuartzJobClass(QuartzJob job) {
    
    
        boolean isConcurrent = "0".equals(job.getConcurrent());
        return isConcurrent ? QuartzJobExecution.class : QuartzDisallowConcurrentExecution.class;
    }

    /**
     * 构建任务触发对象
     */
    public static TriggerKey getTriggerKey(Long jobId, String jobGroup) {
    
    
        return TriggerKey.triggerKey(ScheduleConstants.TASK_CLASS_NAME + jobId, jobGroup);
    }

    /**
     * 构建任务键对象
     */
    public static JobKey getJobKey(Long jobId, String jobGroup) {
    
    
        return JobKey.jobKey(ScheduleConstants.TASK_CLASS_NAME + jobId, jobGroup);
    }

    /**
     * 创建定时任务
     */
    public static void createScheduleJob(Scheduler scheduler, QuartzJob job) throws Exception {
    
    
        // 得到quartz任务类
        Class<? extends Job> jobClass = getQuartzJobClass(job);
        // 构建job信息
        Long jobId = job.getJobId();
        String jobGroup = job.getJobGroup();
        JobDetail jobDetail = JobBuilder.newJob(jobClass).withIdentity(getJobKey(jobId, jobGroup)).build();

        // 表达式调度构建器
        CronScheduleBuilder cronScheduleBuilder = CronScheduleBuilder.cronSchedule(job.getCronExpression());
        cronScheduleBuilder = handleCronScheduleMisfirePolicy(job, cronScheduleBuilder);

        // 按新的cronExpression表达式构建一个新的trigger
        CronTrigger trigger = TriggerBuilder.newTrigger()
            .withIdentity(getTriggerKey(jobId, jobGroup))
            .withSchedule(cronScheduleBuilder).build();

        // 放入参数,运行时的方法可以获取
        jobDetail.getJobDataMap().put(ScheduleConstants.TASK_PROPERTIES, job);

        // 判断是否存在
        if (scheduler.checkExists(getJobKey(jobId, jobGroup))) {
    
    
            // 防止创建时存在数据问题 先移除,然后在执行创建操作
            scheduler.deleteJob(getJobKey(jobId, jobGroup));
        }

        scheduler.scheduleJob(jobDetail, trigger);

        // 暂停任务。完成任务与触发器的关联后,如果是暂停状态,会先让调度器停止任务。
        if (job.getStatus().equals(ScheduleConstants.Status.PAUSE.getValue())) {
    
    
            scheduler.pauseJob(ScheduleUtils.getJobKey(jobId, jobGroup));
        }
    }

    /**
     * 设置定时任务策略
     */
    public static CronScheduleBuilder handleCronScheduleMisfirePolicy(QuartzJob job, CronScheduleBuilder cb) throws Exception {
    
    
        switch (job.getMisfirePolicy()) {
    
    
            case ScheduleConstants.MISFIRE_DEFAULT:
                return cb;
            case ScheduleConstants.MISFIRE_IGNORE_MISFIRES:
                return cb.withMisfireHandlingInstructionIgnoreMisfires();
            case ScheduleConstants.MISFIRE_FIRE_AND_PROCEED:
                return cb.withMisfireHandlingInstructionFireAndProceed();
            case ScheduleConstants.MISFIRE_DO_NOTHING:
                return cb.withMisfireHandlingInstructionDoNothing();
            default:
                throw new Exception("The task misfire policy '" + job.getMisfirePolicy()
                    + "' cannot be used in cron schedule tasks");
        }
    }
}

AbstractQuartzJob abstract task

This class delegates executethe tasks performed by the original method to the overloaded doExecutemethod of the subclass

import lombok.extern.slf4j.Slf4j;
import org.quartz.Job;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeanUtils;
import java.util.Date;

@Slf4j
public abstract class AbstractQuartzJob implements Job {
    
    

    /**
     * 线程本地变量
     */
    private static ThreadLocal<Date> threadLocal = new ThreadLocal<>();

    @Override
    public void execute(JobExecutionContext context) throws JobExecutionException {
    
    
        QuartzJob job = new QuartzJob();
        BeanUtils.copyProperties(context.getMergedJobDataMap().get(ScheduleConstants.TASK_PROPERTIES), job);
        try {
    
    
            before(context, job);
            doExecute(context, job);
            after(context, job, null);
        } catch (Exception e) {
    
    
            log.error("任务执行异常  - :", e);
            after(context, job, e);
        }
    }

    /**
     * 执行前
     *
     * @param context 工作执行上下文对象
     * @param job     系统计划任务
     */
    protected void before(JobExecutionContext context, QuartzJob job) {
    
    
        threadLocal.set(new Date());
    }

    /**
     * 执行后
     *
     * @param context 工作执行上下文对象
     * @param sysJob  系统计划任务
     */
    protected void after(JobExecutionContext context, QuartzJob sysJob, Exception e) {
    
    

    }

    /**
     * 执行方法,由子类重载
     *
     * @param context 工作执行上下文对象
     * @param job     系统计划任务
     * @throws Exception 执行过程中的异常
     */
    protected abstract void doExecute(JobExecutionContext context, QuartzJob job) throws Exception;
}

AbstractQuartzJob implementation class

The two implementation classes are divided into those that allow concurrency and those that do not. The difference is an annotation to allow concurrent tasks:

import org.quartz.JobExecutionContext;

public class QuartzJobExecution extends AbstractQuartzJob {
    
    
    @Override
    protected void doExecute(JobExecutionContext context, QuartzJob job) throws Exception {
    
    
        JobInvokeUtil.invokeMethod(job);
    }
}
import org.quartz.DisallowConcurrentExecution;
import org.quartz.JobExecutionContext;

@DisallowConcurrentExecution
public class QuartzDisallowConcurrentExecution extends AbstractQuartzJob {
    
    
    @Override
    protected void doExecute(JobExecutionContext context, QuartzJob job) throws Exception {
    
    
        JobInvokeUtil.invokeMethod(job);
    }
}

JobInvokeUtil reflection call job tool class

JobInvokeUtil makes actual method calls through reflection

import org.apache.commons.lang3.StringUtils;
import org.springframework.util.CollectionUtils;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.LinkedList;
import java.util.List;

public class JobInvokeUtil {
    
    
    /**
     * 执行方法
     *
     * @param job 系统任务
     */
    public static void invokeMethod(QuartzJob job) throws Exception {
    
    
        String invokeTarget = job.getInvokeTarget();
        String beanName = getBeanName(invokeTarget);
        String methodName = getMethodName(invokeTarget);
        List<Object[]> methodParams = getMethodParams(invokeTarget);

        if (!isValidClassName(beanName)) {
    
    
            Object bean = SpringUtils.getBean(beanName);
            invokeMethod(bean, methodName, methodParams);
        } else {
    
    
            Object bean = Class.forName(beanName).newInstance();
            invokeMethod(bean, methodName, methodParams);
        }
    }

    /**
     * 调用任务方法
     *
     * @param bean         目标对象
     * @param methodName   方法名称
     * @param methodParams 方法参数
     */
    private static void invokeMethod(Object bean, String methodName, List<Object[]> methodParams)
        throws NoSuchMethodException, SecurityException, IllegalAccessException, IllegalArgumentException,
        InvocationTargetException {
    
    
        if (!CollectionUtils.isEmpty(methodParams)) {
    
    
            Method method = bean.getClass().getDeclaredMethod(methodName, getMethodParamsType(methodParams));
            method.invoke(bean, getMethodParamsValue(methodParams));
        } else {
    
    
            Method method = bean.getClass().getDeclaredMethod(methodName);
            method.invoke(bean);
        }
    }

    /**
     * 校验是否为为class包名
     *
     * @param invokeTarget 名称
     * @return true是 false否
     */
    public static boolean isValidClassName(String invokeTarget) {
    
    
        return StringUtils.countMatches(invokeTarget, ".") > 1;
    }

    /**
     * 获取bean名称
     *
     * @param invokeTarget 目标字符串
     * @return bean名称
     */
    public static String getBeanName(String invokeTarget) {
    
    
        String beanName = StringUtils.substringBefore(invokeTarget, "(");
        return StringUtils.substringBeforeLast(beanName, ".");
    }

    /**
     * 获取bean方法
     *
     * @param invokeTarget 目标字符串
     * @return method方法
     */
    public static String getMethodName(String invokeTarget) {
    
    
        String methodName = StringUtils.substringBefore(invokeTarget, "(");
        return StringUtils.substringAfterLast(methodName, ".");
    }

    /**
     * 获取method方法参数相关列表
     *
     * @param invokeTarget 目标字符串
     * @return method方法相关参数列表
     */
    public static List<Object[]> getMethodParams(String invokeTarget) {
    
    
        String methodStr = StringUtils.substringBetween(invokeTarget, "(", ")");
        if (StringUtils.isEmpty(methodStr)) {
    
    
            return null;
        }
        String[] methodParams = methodStr.split(",");
        List<Object[]> classs = new LinkedList<>();
        for (String methodParam : methodParams) {
    
    
            String str = StringUtils.trimToEmpty(methodParam);
            // String字符串类型,包含'
            if (StringUtils.contains(str, "'")) {
    
    
                classs.add(new Object[]{
    
    StringUtils.replace(str, "'", ""), String.class});
            }
            // boolean布尔类型,等于true或者false
            else if (StringUtils.equals(str, "true") || StringUtils.equalsIgnoreCase(str, "false")) {
    
    
                classs.add(new Object[]{
    
    Boolean.valueOf(str), Boolean.class});
            }
            // long长整形,包含L
            else if (StringUtils.containsIgnoreCase(str, "L")) {
    
    
                classs.add(new Object[]{
    
    Long.valueOf(StringUtils.replaceChars(str, "L", "")), Long.class});
            }
            // double浮点类型,包含D
            else if (StringUtils.containsIgnoreCase(str, "D")) {
    
    
                classs.add(new Object[]{
    
    Double.valueOf(StringUtils.replaceChars(str, "D", "")), Double.class});
            }
            // 其他类型归类为整形
            else {
    
    
                classs.add(new Object[]{
    
    Integer.valueOf(str), Integer.class});
            }
        }
        return classs;
    }

    /**
     * 获取参数类型
     *
     * @param methodParams 参数相关列表
     * @return 参数类型列表
     */
    public static Class<?>[] getMethodParamsType(List<Object[]> methodParams) {
    
    
        Class<?>[] classs = new Class<?>[methodParams.size()];
        int index = 0;
        for (Object[] os : methodParams) {
    
    
            classs[index] = (Class<?>) os[1];
            index++;
        }
        return classs;
    }

    /**
     * 获取参数值
     *
     * @param methodParams 参数相关列表
     * @return 参数值列表
     */
    public static Object[] getMethodParamsValue(List<Object[]> methodParams) {
    
    
        Object[] classs = new Object[methodParams.size()];
        int index = 0;
        for (Object[] os : methodParams) {
    
    
            classs[index] = (Object) os[0];
            index++;
        }
        return classs;
    }
}

Start the program to see if the scheduler is started

2021-10-06 16:26:05.162  INFO 10764 --- [shivaScheduler]] o.s.s.quartz.SchedulerFactoryBean        : Starting Quartz Scheduler now, after delay of 1 seconds
2021-10-06 16:26:05.306  INFO 10764 --- [shivaScheduler]] org.quartz.core.QuartzScheduler          : Scheduler shivaScheduler_$_DESKTOP-OKMJ1351633508761366 started.

QuartzSheduleTaskImpl

First set the task to the paused state, after the database is successfully inserted, add a new task in the scheduler, and then manually start the task according to the ID.

import com.duran.ssmtest.schedule.quartz.respositories.QuartzJob;
import com.duran.ssmtest.schedule.quartz.respositories.QuartzJobRespository;
import lombok.extern.slf4j.Slf4j;
import org.quartz.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.quartz.SchedulerFactoryBean;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.interceptor.TransactionAspectSupport;
import javax.annotation.PostConstruct;
import java.util.List;

@Slf4j
@Service
public class QuartzSheduleTaskImpl {
    
    

    @Autowired
    private QuartzJobRespository quartzJobRespository;
    @Autowired
    private SchedulerFactoryBean schedulerFactoryBean;

    private Scheduler scheduler;


    /**
     * 项目启动时,初始化定时器
     * 主要是防止手动修改数据库导致未同步到定时任务处理(注:不能手动修改数据库ID和任务组名,否则会导致脏数据)
     */
    @PostConstruct
    public void init() throws Exception {
    
    
        scheduler = schedulerFactoryBean.getScheduler();

        scheduler.clear();
        List<QuartzJob> jobList = quartzJobRespository.findAll();
        for (QuartzJob job : jobList) {
    
    
            ScheduleUtils.createScheduleJob(scheduler, job);
        }
    }

    /**
     * 新增定时任务
     */
    @Transactional(rollbackFor = Exception.class)
    public int insertJob(QuartzJob job){
    
    
        // 先将任务设置为暂停状态
        job.setStatus(ScheduleConstants.Status.PAUSE.getValue());
        try {
    
    
            Long jobId = quartzJobRespository.save(job).getJobId();
            ScheduleUtils.createScheduleJob(scheduler, job);
            return jobId.shortValue();
        } catch (Exception e) {
    
    
            log.error("failed to insertJob", e);
            // 手动回滚
            TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
            return 0;
        }
    }

    /**
     * 修改定时任务的状态:启动任务/暂停任务
     */
    @Transactional(rollbackFor = Exception.class)
    public int changeStatus(Long jobId, String status) throws SchedulerException {
    
    
        QuartzJob job = quartzJobRespository.findById(jobId).orElse(null);
        if (job == null) {
    
    
            return 0;
        }

        job.setStatus(status);
        try {
    
    
            quartzJobRespository.save(job);
        } catch (Exception e) {
    
    
            log.error("failed to changeStatus", e);
            // 手动回滚
            TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
            return 0;
        }

        //根据状态来启动或者关闭
        if (ScheduleConstants.Status.NORMAL.getValue().equals(status)) {
    
    
            scheduler.resumeJob(ScheduleUtils.getJobKey(jobId, job.getJobGroup()));
        } else if (ScheduleConstants.Status.PAUSE.getValue().equals(status)) {
    
    
            scheduler.pauseJob(ScheduleUtils.getJobKey(jobId, job.getJobGroup()));
        }

        return 1;
    }

    /**
     * 删除定时任务
     */
    @Transactional(rollbackFor = Exception.class)
    public int deleteJob(Long jobId){
    
    
        QuartzJob job = quartzJobRespository.findById(jobId).orElse(null);
        if (job == null) {
    
    
            return 0;
        }
        try {
    
    
            quartzJobRespository.deleteById(jobId);
            scheduler.deleteJob(ScheduleUtils.getJobKey(jobId, job.getJobGroup()));
            return 1;
        } catch (Exception e) {
    
    
            log.error("failed to insertJob", e);
            // 手动回滚
            TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
            return 0;
        }
    }
}

New task request

{
    
    
  "concurrent": "1",
  "cronExpression": "0/10 * * * * ?",
  "invokeTarget": "mysqlJob.execute('got it!!!')",
  "jobGroup": "mysqlGroup",
  "jobName": "新增 mysqlJob 任务",
  "misfirePolicy": "1",
  "remark": "",
  "status": "0"
}

Guess you like

Origin blog.csdn.net/footless_bird/article/details/126844246