Programación de tareas Guía de usuario de Quartzh Framework

descripción general

Quartz es el líder en el marco de programación de tareas de código abierto. Es otro proyecto de código abierto de la organización de código abierto OpenSymphony en el campo de la programación de trabajos. Está completamente desarrollado en Java y puede usarse para ejecutar tareas programadas, similar a java.util.Timer. Pero en comparación con Timer, Quartz agrega muchas características:

  • Quartz proporciona un poderoso mecanismo de programación de tareas mientras mantiene la simplicidad de uso. Quartz permite a los desarrolladores definir de manera flexible el cronograma de programación de los disparadores y puede asignar disparadores y tareas de forma asociativa.
  • Quartz proporciona un mecanismo de persistencia para programar el entorno operativo, que puede guardar y restaurar el sitio de programación.Incluso si el sistema se apaga debido a una falla, los datos del sitio de programación de tareas no se perderán.
  • Quartz también proporciona escuchas de componentes, varios complementos, grupos de subprocesos y otras funciones.

La mayoría de las empresas utilizan la función de tareas programadas. Tomemos como ejemplo la compra del billete de tren:

  1. Después de realizar un pedido y comprar un boleto, se insertará en segundo plano una tarea (trabajo) a pagar, generalmente 30 minutos, y el trabajo se ejecutará después de 30 minutos para determinar si pagar, y el pedido se cancelará si no se realiza el pago;
  2. Después de que se complete el pago, el fondo insertará una tarea (trabajo) para ser consumido después de recibir la devolución de llamada de pago. La fecha de activación del trabajo es la fecha de salida en el boleto de tren. Después de este tiempo, el trabajo se ejecutará para determinar si se usa, etc.

Las clases principales de Quartz tienen las siguientes tres partes:

  • Task Job: la clase de tarea que necesita implementar la interfaz org.quartz.Job, implementar execute()el método y completar la tarea después de la ejecución.

  • Disparador disparador:

    Implemente el activador que activa la ejecución de la tarea. La función más básica del activador es especificar el tiempo de ejecución, el intervalo de ejecución y el número de ejecuciones del Trabajo.

    Incluye SimpleTrigger (gatillo simple) y CronTrigger.

  • Programador Programador: el programador de tareas es responsable Triggerde ejecutar las tareas del trabajo en función de los disparadores.

Las principales relaciones son las siguientes:

inserte la descripción de la imagen aquí


Caso de inicio

confiar

Dependencias de integración de SpringBoot

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

Dependencias nativas

    <!-- 核心包 -->
    <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>

el código

clase de tarea personalizada

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 + "搞卫生");
    }
}

método de programación de tareas

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();
    }

API de cuarzo

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

Descripción general de las principales interfaces API

Las principales interfaces de la API de Quartz son:

  • Trabajo: La interfaz que la clase lógica de tareas necesita implementar, definiendo el contenido de la tarea a programar

  • JobDetail (detalles de la tarea): también conocida como instancia de tarea, se usa para vincular el trabajo y proporciona muchas propiedades de configuración para el trabajo.

    El planificador necesita la ayuda del objeto Jobdetail para agregar la instancia de trabajo.

  • JobBuilder: utilizado para construir instancias de JobDetail

  • Trigger (disparador): Definir el plan de programación de la tarea

  • TriggerBuilder: se utiliza para crear instancias de activación

  • Programador (controlador de programación de tareas): la API principal para interactuar con el programador

  • SchedulerFactory (fábrica de planificadores): crea una instancia de planificador

  • DateBuilder: es conveniente construir instancias de java.util.Date que representen diferentes puntos de tiempo

  • Calendario: una colección de puntos específicos del calendario en el tiempo. Un disparador puede contener múltiples calendarios para excluir o incluir ciertos puntos de tiempo


Trabajo (clase de lógica de tarea personalizada)

Implementar la interfaz de trabajo

La clase de tarea es una clase que implementa la interfaz org.quartz.Job y la clase de tarea debe tener un constructor vacío .

Cuando se activa un disparador (disparador) asociado con esta instancia de tarea, un subproceso de trabajo del programador (programador) llamará al método execute().

Ejemplo de código:

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

JobExecutionContext

El objeto JobExecutionContext pasado al método execute()

  • Guarda cierta información del trabajo cuando se está ejecutando, incluida la referencia del objeto planificador que ejecuta el trabajo, la referencia del objeto desencadenante que desencadena el trabajo, la referencia del objeto JobDetail, etc.

  • Métodos API:

    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();
    

Obtener el valor clave de JobDataMap

  • Método 1: obtenga los objetos JobDetail y Trigger a través del contexto en la clase de implementación Job y luego obtenga el objeto JobDataMap de estos dos objetos

  • Método 2: Obtener MergedJobDataMap directamente a través del contexto en la clase de implementación del trabajo (almacena el valor de la clave combinada de jobdetail y disparador)

    Es necesario asegurarse de que no haya claves duplicadas en JobDataMap de jobDetail y Trigger.

    Porque cuando hay la misma clave, primero se obtiene la del jobdetail, y luego la del disparador, y se sobrescribe el valor de la clave con el mismo nombre

  • Método 3: defina el atributo con el mismo nombre que la clave en JobDataMap en la clase de implementación del trabajo y agregue un método Setter

    La clase se inyecta automáticamente y la capa inferior usará la reflexión para inyectar directamente los valores clave en el detalle del trabajo en el planificador y el mapa de datos del trabajo en el disparador en los campos correspondientes a través del método Setter (los nombres deben ser los mismos)

    Es necesario asegurarse de que no haya claves duplicadas en JobDataMap de jobDetail y Trigger. Misma razón que la anterior


JobDetail (detalles de la tarea)

Contiene varias configuraciones de atributos del trabajo y el JobDataMap utilizado para almacenar los datos del objeto del trabajo.

Las propiedades de su clase de implementación:

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;	// 任务唯一标识,包含任务名称和任务分组信息

Métodos de API de interfaz:

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();

Creador de trabajos

JobBuilder se utiliza para crear instancias de JobDetail

Métodos API:

// 创建一个 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()

Desencadenar

Un disparador tiene muchas propiedades, que se establecen cuando el disparador se crea con TriggerBuilder.

Activar propiedades públicas:

/* 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;	// 触发器的本次触发时间

Método API de la interfaz de activación:

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 se usa para construir instancias de Trigger

TriggerBuilder (y otros constructores de Quartz) eligen valores predeterminados sensibles para las propiedades que no están establecidas explícitamente.

Por ejemplo:

  • Si no se llama al método withIdentity(…), TriggerBuilder generará un nombre aleatorio para el disparador

  • Si no se llama al método startAt(…), se usa la hora actual de forma predeterminada, es decir, el disparador surte efecto inmediatamente.

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()

Programador (controlador de programación de tareas)

Interfaz de métodos API comunes:

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 (fábrica de programadores)

Métodos API comúnmente utilizados:

// 构造方法
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)

Explicación detallada de los módulos principales

referencia:

clase de trabajo

Hay dos tipos de trabajo :

  • Stateless (sin estado): Por defecto, se creará un nuevo JobDataMap cada vez que se llame

  • Con estado: la anotación @PersistJobDataAfterExecution está marcada en la clase de implementación del trabajo

    Cierta información de estado se puede retener durante varias llamadas de trabajo, que se almacenan en JobDataMap


El ciclo de vida de una instancia de trabajo

  1. Cada vez que el planificador ejecuta un trabajo, se crea una nueva instancia de esta clase antes de llamar a su método de ejecución (...);

  2. Después de ejecutar la tarea, la referencia a la instancia se descarta y la instancia se recolectará como basura;

    Según esta estrategia de ejecución, el trabajo debe tener un constructor sin argumentos (la llamada que crea la instancia del trabajo cuando se usa JobFactory predeterminado). Además, en la clase de trabajo, no se deben definir atributos de datos con estado, porque los valores de estos atributos no se conservarán en múltiples ejecuciones del trabajo.

    Si desea agregar atributos o configuraciones a la instancia de trabajo y realizar un seguimiento del estado del trabajo en múltiples ejecuciones del trabajo, puede usar JobDataMap (parte del objeto JobDetail)


Anotación @DisallowConcurrentExecution: prohíbe la ejecución concurrente

  • Las tareas programadas de Quartz se ejecutan simultáneamente de forma predeterminada y no esperarán a que se ejecute la última tarea, y se ejecutarán tan pronto como expire el intervalo. Si la tarea programada se ejecuta durante demasiado tiempo, los recursos estarán ocupados durante mucho tiempo, lo que provocará el bloqueo de otras tareas.

  • Evite la simultaneidad marcando la anotación @DisallowConcurrentExecution en la clase de implementación del trabajo.

    La anotación @DisallowConcurrentExecution prohíbe la ejecución simultánea de varios JobDetails de la misma definición. Esta anotación se agrega a la clase Job, pero no significa que no se puedan ejecutar varios trabajos al mismo tiempo, sino que la misma definición de trabajo (definida por JobDetail) no se puede ejecutar al mismo tiempo, pero se pueden ejecutar varios JobDetails diferentes al mismo tiempo.

    Por ejemplo: hay una clase de trabajo llamada SayHelloJob, y esta anotación se agrega a este trabajo, y luego se definen muchos JobDetails en este trabajo, como sayHelloToJoeJobDetail, sayHelloToMikeJobDetail, luego, cuando se inicia el programador, varios sayHelloToJoeJobDetail o sayHelloToMikeJobDetail no se ejecutarán simultáneamente, pero se pueden ejecutar al mismo tiempo Execute sayHelloToJoeJobDetail y DiHolaAMikeTrabajoDetalle.

    Código de prueba: el intervalo de tiempo establecido es de 3 segundos, pero el tiempo de ejecución del trabajo es de 5 segundos. Después de configurar @DisallowConcurrentExecution, el programa esperará a que se ejecute la tarea antes de ejecutarla, de lo contrario, iniciará un nuevo hilo para su ejecución en 3 segundos.


Anotación @PersistJobDataAfterExecution: guardar JobDataMap

  • Esta anotación se agrega a la clase de implementación del trabajo. Indica que después de la ejecución normal del trabajo, los datos en JobDataMap deben cambiarse para la próxima llamada.

  • **Nota:** Al usar la anotación @PersistJobDataAfterExecution, para evitar confusiones al almacenar datos durante la simultaneidad, se recomienda agregar la anotación @DisallowConcurrentExecution.


JobDetail (detalles de la tarea)

Puede crear solo una clase de trabajo y luego crear varias instancias de JobDetail asociadas con el trabajo, cada instancia tiene su propio conjunto de atributos y JobDataMap y, finalmente, agregar todas las instancias al programador.

Cuando se activa un activador, se cargará la instancia asociada de JobDetail y la clase de trabajo a la que hace referencia JobDetail se inicializa a través de JobFactory configurado en el Programador.

  • La implementación predeterminada de JobFactory simplemente llama al método newInstance() de la clase de trabajo y luego intenta llamar al método setter de la clave en JobDataMap.
  • También puede personalizar la implementación de JobFactory, como permitir que los contenedores IOC o DI creen/inicialicen instancias de trabajo

Nombres descriptivos para Job y JobDetail

  • En el lenguaje de descripción de Quartz, el JobDetail guardado generalmente se denomina "definición de trabajo" o "instancia de JobDetail", y un trabajo en ejecución se denomina "instancia de trabajo" o "instancia de definición de trabajo".

  • Cuando se usa "trabajo", generalmente se refiere a la definición del trabajo o JobDetail

  • Al referirse a una clase que implementa la interfaz de trabajo, es común usar "clase de trabajo"


A través del objeto JobDetail, otros atributos que se pueden configurar para la instancia de trabajo son:

  • Atributo Durabilidad: Indica si se debe retener si no hay una asociación Trigger, tipo booleano

    Si un JobDetail no es persistente, se elimina automáticamente del programador cuando no hay activadores activos asociados con él.

    En otras palabras, el ciclo de vida de un JobDetail no persistente está determinado por la presencia o ausencia de un activador.

  • Atributo RequestsRecovery: identifica si el programa finaliza de forma anormal durante la ejecución de la tarea y si debe ejecutarse la tarea nuevamente cuando el programa se reinicia, tipo booleano

    Si un trabajo es recuperable y el programador se cierra por completo durante su ejecución (por ejemplo, si un proceso en ejecución falla o se cierra), el trabajo se volverá a ejecutar cuando se reinicie el programador.


Desencadenar

descripción general

Se utiliza para desencadenar la ejecución del trabajo. Al prepararse para programar un trabajo, debe crear una instancia de Trigger y establecer propiedades relacionadas con la programación.

Nota: Un disparador solo se puede vincular a un JobDetail y un JobDetail se puede vincular a varios disparadores.


Activar prioridad (prioridad)

Si hay muchos disparadores (o muy pocos subprocesos de trabajo en el grupo de subprocesos de Quartz), es posible que Quartz no tenga suficientes recursos para activar todos los disparadores al mismo tiempo. En este caso, puede usar los subprocesos de trabajo de Quartz primero configurando el atributo de prioridad (prioridad) del disparador.

  • La prioridad solo tiene sentido cuando el tiempo de activación del activador es el mismo
  • El valor de prioridad del disparador por defecto es 5
  • Cuando una tarea solicita reanudar la ejecución, su prioridad es la misma que la prioridad original

Instrucciones de Miss Fire

  • Si el programador está cerrado o no hay subprocesos disponibles en el grupo de subprocesos de Quartz para ejecutar el trabajo, el disparador persistente perderá (perderá) su hora de disparador, es decir, perderá el disparador (falla).
  • Los diferentes tipos de disparadores tienen diferentes mecanismos de fallo de encendido. Por defecto, se utiliza la "política inteligente", es decir, el comportamiento se ajusta dinámicamente según el tipo y la configuración del disparador.
  • Cuando se inicia el programador, consulta todos los disparadores persistentes que han perdido disparadores (fallo de encendido); luego actualiza la información del disparador de acuerdo con sus respectivos mecanismos de falla de encendido.

Clasificación de desencadenantes

Quartz viene con varios tipos de disparadores, los más utilizados son SimpleTrigger y CronTrigger.

Las propiedades principales de la instancia del disparador se establecen a través de TriggerBuilder, y las propiedades únicas de varios tipos de disparadores se establecen a través del método TriggerBuilder.withSchedule().

  • SimpleTrigger (disparador simple)

    Uno de los disparadores más básicos. Ejecutar una vez en un punto de tiempo específico, o ejecutar en un punto de tiempo específico y repetirlo varias veces a intervalos específicos.

    Las propiedades de SimpleTrigger incluyen la hora de inicio, la hora de finalización, el conteo de repeticiones y el intervalo de repeticiones

    • atributo repeatInterval: intervalo de repetición, el valor predeterminado es 0
    • atributo repeatCount: el número de repeticiones, el valor predeterminado es 0, el número real de ejecuciones es repeatCount + 1

    Aviso:

    • Si el intervalo de repetición es 0, el activador se ejecutará simultáneamente con el número de repeticiones (o el número aproximado de simultaneidad que puede manejar el programador)

    • El valor del atributo endTime anula el valor del atributo que establece el número de repeticiones

      Es decir, puede crear un disparador que se ejecute cada 10 segundos antes de la hora de finalización. No necesita calcular la cantidad de repeticiones entre la hora de inicio y la hora de finalización. Solo necesita configurar la hora de finalización y establecer la cantidad de repeticiones en REPEAT_INDEFINITELY (repetir indefinidamente)

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

    Constantes de la política Misfire de SimpleTrigger:

    // 触发器默认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 (disparador basado en expresiones cron)

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

    Constantes de la política Misfire de CronTrigger:

    // 触发器默认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 (disparador de intervalo de tiempo del calendario)

    Similar a SimpleTrigger, pero la diferencia es que el intervalo de tiempo subyacente especificado por SimpleTrigger es de milisegundos, y las unidades de intervalo admitidas por CalendarIntervalTrigger son segundos, minutos, horas, días, meses, años y semanas.

    Las propiedades de CalendarIntervalTrigger son:

    • intervalo : intervalo de ejecución
    • intervalUnit : la unidad del intervalo de ejecución (segundos, minutos, horas, días, meses, años, semanas)
    Trigger trigger = TriggerBuilder.newTrigger()  
        .withSchedule(
        	calendarIntervalSchedule()
            .withIntervalInDays(1) //每天执行一次
            //.withIntervalInWeeks(1) //每周执行一次
    	)
        .build();
    
  • DailyTimeIntervalTrigger (disparador de intervalo de tiempo diario)

    Tipo de intervalo de tiempo de soporte y semana especificada

    Atributos comunes:

    • startTimeOfDay : hora de inicio del día
    • endTimeOfDay: la hora de finalización de cada día
    • daysOfWeek: el día de la semana que debe ejecutarse
    • intervalo : intervalo de ejecución
    • intervalUnit : la unidad del intervalo de ejecución (segundos, minutos, horas, días, meses, años, semanas)
    • repeatCount: número de repeticiones
    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();
    

Excluir franjas horarias de los horarios

El objeto org.quartz.Calendar se utiliza para excluir períodos de tiempo de la programación del disparador.

Por ejemplo, puede crear un disparador que se ejecute a las 9:30 am todos los días de la semana y luego agregar un calendario para excluir todos los días festivos comerciales.

Cualquier objeto serializable que implemente la interfaz org.quartz.Calendar se puede usar como un objeto de calendario de cuarzo.

Clase de implementación de interfaz de calendario proporcionada por Quartz:

  • BaseCalender: implementa funciones básicas para Calender avanzado e implementa la interfaz org.quartz.Calender
  • WeeklyCalendar: excluye uno o más días de la semana, por ejemplo, puede usarse para excluir fines de semana
  • MonthlyCalendar: excluye los días del mes, por ejemplo, se puede usar para excluir el último día de cada mes
  • Calendario anual: excluye uno o más días del año
  • HolidayCalendar: especialmente utilizado para excluir días festivos de Trigger

Cómo utilizar

  • Cree una instancia del Calendario y agregue las fechas que se excluirán
  • Registre la instancia de Calendario en el programador a través del método addCalendar()
  • Al definir el disparador, asocie la instancia del Calendario con la instancia del disparador

Ejemplo:

// 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")....

programador

Referencia: Comprensión y uso de Scheduler en Quartz

descripción general

El programador es el corazón del marco de cuarzo, se utiliza para administrar activadores y trabajos, y para garantizar que los trabajos se puedan activar para ejecutar. Las llamadas entre el programador y el marco se realizan a través de la interfaz org.quartz.Scheduler.

La implementación de la interfaz del Programador es en realidad solo un proxy del programador central (org.quartz.core.QuartzScheduler), y cuando se llama al método del proxy, se pasará a la instancia del programador central subyacente. QuartzScheduler está en la raíz del marco de cuarzo y dirige todo el marco de cuarzo.


Tipo de horario

Hay tres tipos:

  • StdScheduler (programador predeterminado estándar): más comúnmente utilizado

    El valor predeterminado cargado es el archivo de propiedades "quartz.properties" en el directorio de trabajo actual. Si la carga falla, se cargará el archivo de propiedades "quartz.properties" del paquete org/quartz.

  • RemoteScheduler (programador remoto)

  • RemoteMBeanSchedulerRemoteMBeanSchedulerRemoteMBeanScheduler


Scheduler tiene dos componentes importantes:

  • ThreadPool: grupo de subprocesos del programador, todas las tareas serán ejecutadas por el grupo de subprocesos

  • JobStore: almacena información de tiempo de ejecución, incluidos Trigger, Scheduler, JobDetail, bloqueos comerciales, etc.

    La implementación de JobStore incluye RAMJob (implementación de memoria), JobStoreTX (JDBC, las transacciones son administradas por Quartz), JobStoreCMT (JDBC, usando transacciones de contenedor), ClusteredJobStore (implementación de clúster), etc.

    Nota: El trabajo y el activador deben almacenarse antes de que puedan usarse


Horario de fábrica

Se utiliza para crear instancias de programación

Hay dos tipos:

  • StdSchedulerFactory (más comúnmente utilizado)
  • DirectSchedulerFactory

Ciclo de vida del programador

  • Comienza cuando SchedulerFactory lo crea y finaliza cuando Scheduler llama al método shutdown()

  • Después de crear el programador, puede agregar, eliminar y enumerar trabajos y disparadores, y realizar otras operaciones relacionadas con la programación (como suspender disparadores).

    Sin embargo, el programador activará el activador (es decir, ejecutará el trabajo) solo después de llamar al método start().


Clasificación de hilos de cuarzo

  • Hilo de programación del programador

    Hay:

    • Subproceso del programador regular (Subproceso del programador regular): sondee todos los activadores almacenados en la consulta y, cuando se alcance el tiempo de activación, obtenga un subproceso inactivo del grupo de subprocesos para ejecutar las tareas asociadas con los activadores

    • Subproceso del programador de fallas: el subproceso de fallas escanea todos los disparadores para verificar si hay subprocesos fallados, es decir, subprocesos que no se han perdido durante la ejecución. Si hay alguno, se procesan por separado de acuerdo con la estrategia de fallas de encendido.

  • subproceso de ejecución de tareas


Crear un programador

StdScheduler solo proporciona un método de construcción parametrizado, que necesita pasar dos parámetros de instancia de QuartzScheduler y SchedulingContext.

Generalmente, el constructor no se usa para crear el planificador, sino que se crea a través de la fábrica de planificadores.

La interfaz de fábrica del programador org.quartz.SchedulerFactory proporciona dos tipos diferentes de implementaciones de fábrica, a saber, org.quartz.impl.DirectSchedulerFactoryh y org.quartz.impl.StdSchedulerFactory.

  • Creado usando la fábrica StdSchedulerFactory

    Esta fábrica se basa en una serie de propiedades para determinar cómo crear instancias del programador.

    Obtenga el programador estándar predeterminado:

    • Esta fábrica proporciona un método initialize() sin parámetros para la inicialización. La esencia de este método es cargar el archivo de propiedades de cuarzo.properties predeterminado en el paquete jar de cuarzo. Los pasos de carga específicos son:
      1. Verifique si el nombre del archivo está establecido en las propiedades del sistema, a través de System.getProperty("org.quartz.properties")
      2. Si no está configurado, use las propiedades de cuarzo predeterminadas como el nombre de archivo para cargar
      3. Luego cargue este archivo desde el directorio de trabajo actual primero, si no lo encuentra, luego cargue este archivo desde el classpath del sistema
    • La fábrica StdSchedulerFactory tampoco puede llamar activamente al método initialize() para la inicialización, sino que puede usar directamente el método estático getDefaultScheduler() de StdSchedulerFactory para obtener un programador estándar predeterminado.

    Obtenga un programador personalizado:

    Hay tres formas de proporcionar atributos personalizados:

    • A través de la instancia de la propiedad java.util.Properties
    • Proporcionado a través de un archivo de propiedades externo
    • Proporcionado a través del flujo de archivos java.io.InputStream con el contenido del archivo de propiedades
        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();
            }
        }
    
    • El primer método pasa dos propiedades a la fábrica, a saber, el nombre de clase del grupo de subprocesos y el tamaño del grupo de subprocesos.Estas dos propiedades son necesarias, porque si la propiedad Propiedades personalizadas se usa para la inicialización, la fábrica no especifica valores predeterminados para ellos.

    • La segunda forma es definiendo un archivo de propiedades externo

      La implementación subyacente es: primero obtenga el flujo de archivo a través de Thread.currentThread().getContextClassLoader().getResourceAsStream(nombre de archivo), luego use el método de carga de la instancia de propiedades para cargar el flujo de archivo para formar una instancia de propiedad y finalmente complete la inicialización a través de initialize(props).

    • La tercera forma es usar directamente el método de carga de la instancia de Propiedades para cargar la secuencia de archivos para formar una instancia de propiedad y luego completar la inicialización a través de initialize(props).

  • Creado usando la fábrica DirectSchedulerFactory

    Este método de fábrica es adecuado para escenarios en los que desea tener control absoluto sobre la instancia de Scheduler.

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

    Crear pasos:

    1. Obtenga la instancia a través del método getInstance de DirectSchedulerFactory
    2. Llame al método createXXX para inicializar la fábrica
    3. Llame al método getScheduler de la instancia de fábrica para obtener la instancia del programador

    DirectSchedulerFactory inicializa la fábrica al pasar los parámetros de configuración a través del método createXXX.Este método de inicialización está codificado y rara vez se usa en el trabajo.


Administrar programador

Después de obtener la instancia del programador, puede realizar el siguiente trabajo en el ciclo de vida de la programación, por ejemplo: iniciar el programador, establecer el programador en modo de espera, continuar o detener el programador.

  • Iniciar programador

    Cuando se inicializa el programador y se registran el trabajo y el activador, puede llamar a scheduler.start() para iniciar el programador.

    Una vez que se llama al método start(), el programador comienza a buscar trabajos que deben ejecutarse.

  • Establecer el modo de espera

    Configurar el programador en modo de espera hará que el programador haga una pausa en busca de trabajos para ejecutar.

    Ejemplo de escenario de aplicación: cuando es necesario reiniciar la base de datos, el programador se puede configurar primero en modo de espera y luego se puede iniciar el programador a través de start() una vez que se completa el inicio de la base de datos.

  • cerrar programador

    Llame al método shutdown() o shutdown(boolean waitForJobsToComplete) para detener el programador.

    Después de llamar al método de apagado, ya no se puede volver a llamar al método de inicio, porque el método de apagado destruirá todos los recursos (subprocesos, conexiones de base de datos, etc.) creados por el Programador.

En general, el programador no necesita hacer nada más después de que se inicia.


Tiendas de trabajo

Quartz ofrece dos tipos básicos de almacenamiento de trabajos:

  • RAMJobStore (almacenamiento de tareas en memoria): de forma predeterminada, Quartz almacenará la programación de tareas en la memoria

    De esta manera, el rendimiento es el mejor, porque la velocidad de la memoria es la más rápida.

    La desventaja es que los datos carecen de persistencia. Cuando el programa falla o se vuelve a publicar, se perderá toda la información en ejecución.

  • JDBCJobStore (almacén de trabajos de la base de datos)

    Después de guardar en la base de datos, se puede hacer como un solo punto o como un grupo

    Cuando hay más tareas, se pueden gestionar de manera uniforme (parar, suspender, modificar tareas)

    Apague o reinicie el servidor, la información en ejecución no se perderá

    La desventaja es que la velocidad de operación depende de la velocidad de conexión a la base de datos.


Quartz viene con un esquema de base de datos

Dirección de la secuencia de comandos de la base de datos:

  • https://gitee.com/qianwei4712/code-of-shiva/blob/master/quartz/quartz.sql (con comentarios, gitee)
  • https://lqcoder.com/quartz.sql (sin comentarios, enlace de descarga)
Nombre de la tabla Descripción
QRTZ_CALENDARIOS Almacene la información del calendario de Quartz
QRTZ_CRON_TRIGGERS Almacena CronTriggers, incluidas expresiones Cron e información de zona horaria
QRTZ_FIRED_TRIGGERS Almacene la información de estado relacionada con el disparador activado, así como la información de ejecución del trabajo asociado
QRTZ_PAUSED_TRIGGER_GRPS Almacena información sobre grupos de activación en pausa
QRTZ_SCHEDULER_STATE Almacene una pequeña cantidad de información de estado sobre el Programador y otras instancias del Programador
QRTZ_BLOQUEOS Información de bloqueo pesimista del programa almacenado
QRTZ_JOB_DETALLES Almacene los detalles de cada trabajo configurado
QRTZ_JOB_LISTENERS Almacena información sobre JobListeners configurados
QRTZ_SIMPLE_TRIGGERS Almacene disparadores simples, incluidas repeticiones, intervalos y la cantidad de toques
QRTZ_BLOG_TRIGGERS El activador se almacena como un tipo Blob
QRTZ_TRIGGER_LISTENERS Almacenar información sobre TriggerListeners configurados
QRTZ_TRIGGERS Almacenar información de activación configurada

Oyentes (oyentes de eventos)

Oyentes: se utilizan para realizar acciones basadas en eventos que ocurren en el programador.

Disparador, trabajo, interfaz de monitoreo del programador

  • TriggerListeners : recibe eventos relacionados con disparadores

    Los eventos relacionados con el disparador incluyen: disparador disparador, falla del disparador, finalización del disparador (disparador apagado)

    interfaz org.quartz.TriggerListener:

    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 : recibir eventos relacionados con trabajos

    Los eventos relacionados con el trabajo incluyen: notificaciones de que un trabajo está a punto de ejecutarse y notificaciones cuando un trabajo ha completado la ejecución.

    interfaz org.quartz.JobListener:

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

    Los eventos relacionados con el programador incluyen: agregar un trabajo/activador, eliminar un trabajo/activador, errores críticos en el programador, notificaciones para cerrar el programador, etc.

    interfaz org.quartz.SchedulerListener:

    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();
    }
    

Oyentes personalizados

paso:

  1. Personalice una clase que implemente org.quartz.TriggerListener o JobListener, interfaz SchedulerListener

    Para mayor comodidad, el oyente personalizado también puede heredar la clase de implementación de su subinterfaz JobListenerSupport o TriggerListenerSupport, SchedulerListenerSupport y anular el evento de interés.

  2. Registre el oyente con el programador y configure el trabajo o active el Matcher que el oyente desea recibir eventos

    Nota: los oyentes no se almacenan en JobStore junto con trabajos y activadores. Esto se debe a que el oyente suele ser el punto de integración con la aplicación, por lo que cada vez que se ejecuta la aplicación, el despachador debe volver a registrarse.

Ejemplo:

  • Agregue un JobListener interesado en todos los trabajos:

    scheduler.getListenerManager().addJobListener(myJobListener, allJobs());
    
  • Agregue un JobListener interesado en un trabajo específico:

    scheduler.getListenerManager().addJobListener(
        myJobListener,KeyMatcher.jobKeyEquals(new JobKey("myJobName""myJobGroup")));
    
  • Agregue un JobListener interesado en todos los trabajos de un grupo en particular:

    scheduler.getListenerManager().addJobListener(myJobListener, jobGroupEquals("myJobGroup"));
    
  • Agregue un JobListener interesado en todos los trabajos de dos grupos específicos:

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

    scheduler.getListenerManager().addSchedulerListener(mySchedListener);
    
  • Eliminar el SchedulerListener:

    scheduler.getListenerManager().removeSchedulerListener(mySchedListener);
    

SchedulerFactoryBean (integración de primavera)

referencia:

SchedulerFactory de Quartz es una clase de fábrica estándar, que no es adecuada para su uso en el entorno Spring.

Para garantizar que el Programador pueda percibir el ciclo de vida del contenedor Spring y completar las operaciones de inicio y apagado automático, el Programador debe estar asociado con el ciclo de vida del contenedor Spring. De modo que después de que se inicie el contenedor Spring, el Programador comenzará a funcionar automáticamente, y antes de que se cierre el contenedor Spring, el Programador se cerrará automáticamente. Con este fin, Spring proporciona SchedulerFactoryBean , que tiene aproximadamente las siguientes funciones:

  • Proporcione información de configuración para Scheduler de una manera más estilo Bean
  • Deje que el Scheduler y el ciclo de vida del contenedor Spring estén asociados, y están estrechamente relacionados
  • Reemplace parcial o completamente el propio archivo de configuración de Quartz a través de la configuración de propiedades

Introducción a la propiedad SchedulerFactoryBean:

  • Atributo autoStartup : si iniciar el programador inmediatamente después de inicializar SchedulerFactoryBean, el valor predeterminado es verdadero

    Si se establece en falso, el Programador debe iniciarse manualmente

  • Atributo startupDelay : después de que se completa la inicialización de SchedulerFactoryBean, cuántos segundos se retrasan para iniciar el programador. El valor predeterminado es 0, lo que significa que se inicia inmediatamente.

    Si no tiene tareas que deban ejecutarse de inmediato, puede usar el atributo startupDelay para retrasar el inicio del Programador durante un breve período de tiempo, de modo que Spring pueda inicializar los beans restantes en el contenedor más rápido.

  • Atributo Triggers: el tipo es Trigger[], se pueden registrar múltiples Triggers a través de este atributo

  • atributo calendars: el tipo es Mapa, a través del cual se registra el Calendario con el Programador

  • Atributo jobDetails: el tipo es JobDetail[], a través del cual se registra el JobDetail con el Scheduler

  • Atributo SchedulerContextMap: puede almacenar algunos datos en el contexto del programador a través de este atributo.

    El bean en el contenedor Spring solo se puede colocar en SchedulerContext y pasar al trabajo a través del método setSchedulerContextAsMap() de SchedulerFactoryBean, y el bean almacenado se puede obtener en el trabajo a través de JobExecutionContext.getScheduler().getContext()


Una función importante de SchedulerFactoryBean es permitir que la información de los archivos de configuración de Quartz se transfiera a los archivos de configuración de Spring.

El beneficio es la administración centralizada de la información de configuración, y los desarrolladores no necesitan estar familiarizados con la estructura del archivo de configuración de varios marcos. Recuerde que Spring integra marcos JPA e Hibernate, y sabrá que este es uno de los trucos que Spring usa a menudo para integrar marcos de terceros.

SchedulerFactoryBean reemplaza el propio archivo de configuración del marco con las siguientes propiedades:

  • Atributo dataSource : cuando necesite usar la base de datos para conservar los datos de programación de tareas, puede configurar la fuente de datos en Quartz

    También puede especificar una fuente de datos administrada por Spring directamente en Spring a través del método setDataSource()

    Si se especifica esta propiedad, incluso si la fuente de datos se ha definido en quartz.properties, esta fuente de datos la sobrescribirá.

    Después de configurar la fuente de datos dataSource e iniciar la aplicación, los siguientes datos se insertarán automáticamente en la tabla QRTZ_LOCKS de Quartz:

    INSERT INTO QRTZ_LOCKS values('TRIGGER_ACCESS');
    INSERT INTO QRTZ_LOCKS values('JOB_ACCESS');
    
  • Atributo TransactionManager : puede configurar un administrador de transacciones de Spring a través de este atributo.

    Al configurar dataSource, Spring recomienda encarecidamente utilizar un administrador de transacciones; de lo contrario, es posible que el bloqueo de la tabla de datos no funcione correctamente;

  • Atributo nonTransactionalDataSource: En el caso de una transacción global, si no desea que el Programador ejecute la operación de datos para participar en la transacción global, puede especificar la fuente de datos a través de este atributo.

    En el caso de las transacciones nativas de Spring, es suficiente usar la propiedad dataSource

  • Atributo quartzProperties : el tipo es Propiedades, lo que permite a los desarrolladores definir las propiedades de Quartz en Spring.

    Su valor anulará la configuración en el archivo de configuración quartz.properties. Estas propiedades deben ser propiedades legales que Quartz pueda reconocer. Al configurar, es posible que deba consultar los documentos relevantes de Quartz.


SchedulerFactoryBean implementa la interfaz InitializingBean

Por lo tanto, cuando se inicializa el bean, se ejecutará el método afterPropertiesSet(), que llamará al método SchedulerFactory (DirectSchedulerFactory o StdSchedulerFactory) para crear un Scheduler, generalmente usando StdSchedulerFactory.

Durante el proceso de creación de quartzScheduler, SchedulerFactory leerá los parámetros de configuración e inicializará cada componente. Los componentes clave son los siguientes:

  • ThreadPool: generalmente usa SimpleThreadPool

    SimpleThreadPool crea una cierta cantidad de instancias de WorkerThread para permitir que los trabajos se procesen en subprocesos.

    • WorkerThread es una clase interna definida en la clase SimpleThreadPool, que es esencialmente un hilo

    Hay tres listas en SimpleThreadPool:

    • trabajadores: almacenar todas las referencias de subprocesos en el grupo
    • aproveWorkers: almacena todos los subprocesos inactivos
    • busyWorkers: almacena todos los hilos de trabajo

    Los parámetros de configuración del grupo de subprocesos son los siguientes:

    org.quartz.threadPool.class=org.quartz.simpl.SimpleThreadPool
    org.quartz.threadPool.threadCount=3 
    org.quartz.threadPool.threadPriority=5
    
  • JobStore: se divide en RAMJobStore almacenado en la memoria y JobStoreSupport almacenado en la base de datos

    JobStoreSupport incluye dos implementaciones de JobStoreTX y JobStoreCMT

    • JobStoreCMT se basa en contenedores para la gestión de transacciones
    • JobStoreTX gestiona las transacciones por sí mismo

    Si desea utilizar el clúster, debe utilizar el método JobStoreSupport

  • QuartzSchedulerThread: subproceso utilizado para la programación de tareas

    Paused=true, halted=false durante la inicialización. Es decir, aunque el subproceso comienza a ejecutarse, pero pausado = verdadero, el subproceso esperará hasta que se llame al método start () para establecer pausado en falso


SchedulerFactoryBean implementa la interfaz SmartLifeCycle

Por lo tanto, después de que se complete la inicialización, se ejecutará el método start(), que realizará principalmente las siguientes acciones:

  1. Cree un subproceso ClusterManager e inicie el subproceso: este subproceso se usa para la detección y el procesamiento de fallas del clúster
  2. Cree un subproceso MisfireHandler e inicie el subproceso: este subproceso se utiliza para procesar tareas de fallas de encendido
  3. Establezca pausado = falso de QuartzSchedulerThread, el hilo de programación realmente comenzará a programar

Todo el proceso de inicio de SchedulerFactoryBean es el siguiente:

inserte la descripción de la imagen aquí


Casos prácticos a nivel empresarial

Referencia: Marco de programación de tareas Guía de uso de Quartz

Preparación ambiental

Dependencia, archivo de configuración de la aplicación

confiar

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

archivo de configuración de la aplicación:

# 开发环境配置
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

Guión de base de datos de cuarzo

Quartz viene con un esquema de base de datos

  • Guión de guión listo para usar: https://gitee.com/qianwei4712/code-of-shiva/blob/master/quartz/quartz.sql

  • Este artículo usa uniformemente el método Cron para crear

    Nota: Las 4 tablas de datos necesarias para el método cron: qrtz_triggers, qrtz_cron_triggers, qrtz_fired_triggers, qrtz_job_details

  • Tabla de base de datos adicional para guardar tareas:

    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;
    }
    

clase de herramienta SprinUtils

Rol: Obtener los beans en 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);
    }
}

Clase de variable estática ScheduleConstants

clase de variable estática

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;
    }
}

método de tarea

Prepare un método de tarea (llamado más tarde por reflexión, por lo que no es necesario implementar la interfaz de trabajo aquí):

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);
    }
}

Clase de código de configuración de ScheduleConfig

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;
    }
}

Clase de herramienta de programación ScheduleUtils

El código central

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 tarea abstracta

Esta clase delega executelas tareas realizadas por el método original al doExecutemétodo sobrecargado de la subclase

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;
}

Clase de implementación AbstractQuartzJob

Las dos clases de implementación se dividen en las que permiten la concurrencia y las que no, la diferencia es una anotación para permitir tareas concurrentes:

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);
    }
}

Clase de herramienta de trabajo de llamada de reflexión JobInvokeUtil

JobInvokeUtil realiza llamadas de métodos reales a través de la reflexión

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;
    }
}

Inicie el programa para ver si se inicia el programador

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.

CuarzoHorarioTareaImpl

Primero configure la tarea en el estado de pausa, después de que la base de datos se haya insertado correctamente, agregue una nueva tarea en el programador y luego inicie manualmente la tarea de acuerdo con la 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;
        }
    }
}

Nueva solicitud de tarea

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

Supongo que te gusta

Origin blog.csdn.net/footless_bird/article/details/126844246
Recomendado
Clasificación