最为常用定时任务框架是Quartz,并且Spring也集成了Quartz的框架,Quartz不仅支持单实例方式还支持分布式方式。本文主要介绍Quartz,基础的Quartz的集成案例本,以及实现基于数据库的分布式任务管理和控制job生命周期。@pdai

  • SpringBoot定时任务 - 基础quartz实现方式

    • 准备知识点

      • 什么是Quartz
      • Quartz的体系结构
      • 什么是Quartz持久化
    • 实现案例 - 单实例方式
    • 实现案例 - 分布式方式
      • 后端实现
      • 前端实现
      • 测试效果
    • 示例源码
    • 参考文章

准备知识点

需要了解常用的Quartz框架。

什么是Quartz

来源百度百科, 官网地址:http://www.quartz-scheduler.org/

Quartz是OpenSymphony开源组织在Job scheduling领域又一个开源项目,它可以与J2EE与J2SE应用程序相结合也可以单独使用。Quartz可以用来创建简单或为运行十个,百个,甚至是好几万个Jobs这样复杂的程序。Jobs可以做成标准的Java组件或 EJBs。

它的特点如下

  • 纯java实现,可以作为独立的应用程序,也可以嵌入在另一个独立式应用程序运行
  • 强大的调度功能,Spring默认的调度框架,灵活可配置;
  • 作业持久化,调度环境持久化机制,可以保存并恢复调度现场。系统关闭数据不会丢失;灵活的应用方式,可以任意定义触发器的调度时间表,支持任务和调度各种组合,组件式监听器、各种插件、线程池等功能,多种存储方式等;
  • 分布式和集群能力,可以被实例化,一个Quartz集群中的每个节点作为一个独立的Quartz使用,通过相同的数据库表来感知到另一个Quartz应用

Quartz的体系结构

  • Job 表示一个工作,要执行的具体内容。
  • JobDetail 表示一个具体的可执行的调度程序,Job 是这个可执行程调度程序所要执行的内容,另外 JobDetail 还包含了这个任务调度的方案和策略。
  • Trigger 代表一个调度参数的配置,什么时候去调。
  • Scheduler 代表一个调度容器,一个调度容器中可以注册多个 JobDetail 和 Trigger。当 Trigger 与 JobDetail 组合,就可以被 Scheduler 容器调度了。

注: 上图来源于https://www.cnblogs.com/jijm123/p/14240320.html

什么是Quartz持久化

  • 为什么要持久化

当程序突然被中断时,如断电,内存超出时,很有可能造成任务的丢失。 可以将调度信息存储到数据库里面,进行持久化,当程序被中断后,再次启动,仍然会保留中断之前的数据,继续执行,而并不是重新开始。

  • Quartz提供了两种持久化方式

Quartz提供两种基本作业存储类型:

  1. RAMJobStore

在默认情况下Quartz将任务调度的运行信息保存在内存中,这种方法提供了最佳的性能,因为内存中数据访问最快。不足之处是缺乏数据的持久性,当程序路途停止或系统崩溃时,所有运行的信息都会丢失。

  1. JobStoreTX

所有的任务信息都会保存到数据库中,可以控制事物,还有就是如果应用服务器关闭或者重启,任务信息都不会丢失,并且可以恢复因服务器关闭或者重启而导致执行失败的任务。

实现案例 - 单实例方式

本例将展示quartz实现单实例方式。

  • 引入POM依赖
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-quartz</artifactId>
</dependency>
  • 定义Job

只需要继承QuartzJobBean,并重载executeInternal方法即可定义你自己的Job执行逻辑。

@Slf4j
public class HelloJob extends QuartzJobBean {@Overrideprotected void executeInternal(JobExecutionContext context) throws JobExecutionException {// get parameterscontext.getJobDetail().getJobDataMap().forEach((k, v) -> log.info("param, key:{}, value:{}", k, v));// your logicslog.info("Hello Job执行时间: " + new Date());}
}
  • 配置Job

JobDetail, Trigger, Schedule(这里采用CronScheduleBuilder)

/*** @author pdai*/
@Configuration
public class QuartzConfig {@Bean("helloJob")public JobDetail helloJobDetail() {return JobBuilder.newJob(HelloJob.class).withIdentity("DateTimeJob").usingJobData("msg", "Hello Quartz").storeDurably()//即使没有Trigger关联时,也不需要删除该JobDetail.build();}@Beanpublic Trigger printTimeJobTrigger() {// 每秒执行一次CronScheduleBuilder cronScheduleBuilder = CronScheduleBuilder.cronSchedule("0/1 * * * * ?");return TriggerBuilder.newTrigger().forJob(helloJobDetail()).withIdentity("quartzTaskService").withSchedule(cronScheduleBuilder).build();}
}
  • 执行测试
2021-10-01 13:09:00.380  INFO 38484 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat initialized with port(s): 8080 (http)
2021-10-01 13:09:00.391  INFO 38484 --- [           main] o.apache.catalina.core.StandardService   : Starting service [Tomcat]
2021-10-01 13:09:00.392  INFO 38484 --- [           main] org.apache.catalina.core.StandardEngine  : Starting Servlet engine: [Apache Tomcat/9.0.50]
2021-10-01 13:09:00.526  INFO 38484 --- [           main] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring embedded WebApplicationContext
2021-10-01 13:09:00.526  INFO 38484 --- [           main] w.s.c.ServletWebServerApplicationContext : Root WebApplicationContext: initialization completed in 1424 ms
2021-10-01 13:09:00.866  INFO 38484 --- [           main] org.quartz.impl.StdSchedulerFactory      : Using default implementation for ThreadExecutor
2021-10-01 13:09:00.877  INFO 38484 --- [           main] org.quartz.core.SchedulerSignalerImpl    : Initialized Scheduler Signaller of type: class org.quartz.core.SchedulerSignalerImpl
2021-10-01 13:09:00.877  INFO 38484 --- [           main] org.quartz.core.QuartzScheduler          : Quartz Scheduler v.2.3.2 created.
2021-10-01 13:09:00.878  INFO 38484 --- [           main] org.quartz.simpl.RAMJobStore             : RAMJobStore initialized.
2021-10-01 13:09:00.878  INFO 38484 --- [           main] org.quartz.core.QuartzScheduler          : Scheduler meta-data: Quartz Scheduler (v2.3.2) 'quartzScheduler' with instanceId 'NON_CLUSTERED'Scheduler class: 'org.quartz.core.QuartzScheduler' - running locally.NOT STARTED.Currently in standby mode.Number of jobs executed: 0Using thread pool 'org.quartz.simpl.SimpleThreadPool' - with 10 threads.Using job-store 'org.quartz.simpl.RAMJobStore' - which does not support persistence. and is not clustered.2021-10-01 13:09:00.878  INFO 38484 --- [           main] org.quartz.impl.StdSchedulerFactory      : Quartz scheduler 'quartzScheduler' initialized from an externally provided properties instance.
2021-10-01 13:09:00.879  INFO 38484 --- [           main] org.quartz.impl.StdSchedulerFactory      : Quartz scheduler version: 2.3.2
2021-10-01 13:09:00.879  INFO 38484 --- [           main] org.quartz.core.QuartzScheduler          : JobFactory set to: org.springframework.scheduling.quartz.SpringBeanJobFactory@6075b2d3
2021-10-01 13:09:00.922  INFO 38484 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat started on port(s): 8080 (http) with context path ''
2021-10-01 13:09:00.923  INFO 38484 --- [           main] o.s.s.quartz.SchedulerFactoryBean        : Starting Quartz Scheduler now
2021-10-01 13:09:00.923  INFO 38484 --- [           main] org.quartz.core.QuartzScheduler          : Scheduler quartzScheduler_$_NON_CLUSTERED started.
2021-10-01 13:09:00.933  INFO 38484 --- [           main] tech.pdai.springboot.quartz.App          : Started App in 2.64 seconds (JVM running for 3.621)
2021-10-01 13:09:00.931  INFO 38484 --- [eduler_Worker-1] t.pdai.springboot.quartz.job.HelloJob    : param, key:msg, value:Hello Quartz
2021-10-01 13:09:00.933  INFO 38484 --- [eduler_Worker-1] t.pdai.springboot.quartz.job.HelloJob    : Hello Job执行时间: Wed Oct 27 13:09:00 CST 2021
2021-10-01 13:09:01.001  INFO 38484 --- [eduler_Worker-2] t.pdai.springboot.quartz.job.HelloJob    : param, key:msg, value:Hello Quartz
2021-10-01 13:09:01.001  INFO 38484 --- [eduler_Worker-2] t.pdai.springboot.quartz.job.HelloJob    : Hello Job执行时间: Wed Oct 27 13:09:01 CST 2021
2021-10-01 13:09:02.000  INFO 38484 --- [eduler_Worker-3] t.pdai.springboot.quartz.job.HelloJob    : param, key:msg, value:Hello Quartz
2021-10-01 13:09:02.000  INFO 38484 --- [eduler_Worker-3] t.pdai.springboot.quartz.job.HelloJob    : Hello Job执行时间: Wed Oct 27 13:09:02 CST 2021
2021-10-01 13:09:03.000  INFO 38484 --- [eduler_Worker-4] t.pdai.springboot.quartz.job.HelloJob    : param, key:msg, value:Hello Quartz
2021-10-01 13:09:03.001  INFO 38484 --- [eduler_Worker-4] t.pdai.springboot.quartz.job.HelloJob    : Hello Job执行时间: Wed Oct 27 13:09:03 CST 2021
2021-10-01 13:09:04.001  INFO 38484 --- [eduler_Worker-5] t.pdai.springboot.quartz.job.HelloJob    : param, key:msg, value:Hello Quartz
2021-10-01 13:09:04.001  INFO 38484 --- [eduler_Worker-5] t.pdai.springboot.quartz.job.HelloJob    : Hello Job执行时间: Wed Oct 27 13:09:04 CST 2021
2021-10-01 13:09:05.002  INFO 38484 --- [eduler_Worker-6] t.pdai.springboot.quartz.job.HelloJob    : param, key:msg, value:Hello Quartz
2021-10-01 13:09:05.003  INFO 38484 --- [eduler_Worker-6] t.pdai.springboot.quartz.job.HelloJob    : Hello Job执行时间: Wed Oct 27 13:09:05 CST 2021
2021-10-01 13:09:06.000  INFO 38484 --- [eduler_Worker-7] t.pdai.springboot.quartz.job.HelloJob    : param, key:msg, value:Hello Quartz
2021-10-01 13:09:06.001  INFO 38484 --- [eduler_Worker-7] t.pdai.springboot.quartz.job.HelloJob    : Hello Job执行时间: Wed Oct 27 13:09:06 CST 2021
2021-10-01 13:09:07.002  INFO 38484 --- [eduler_Worker-8] t.pdai.springboot.quartz.job.HelloJob    : param, key:msg, value:Hello Quartz
2021-10-01 13:09:07.002  INFO 38484 --- [eduler_Worker-8] t.pdai.springboot.quartz.job.HelloJob    : Hello Job执行时间: Wed Oct 27 13:09:07 CST 2021
2021-10-01 13:09:08.002  INFO 38484 --- [eduler_Worker-9] t.pdai.springboot.quartz.job.HelloJob    : param, key:msg, value:Hello Quartz
2021-10-01 13:09:08.003  INFO 38484 --- [eduler_Worker-9] t.pdai.springboot.quartz.job.HelloJob    : Hello Job执行时间: Wed Oct 27 13:09:08 CST 2021
2021-10-01 13:09:09.000  INFO 38484 --- [duler_Worker-10] t.pdai.springboot.quartz.job.HelloJob    : param, key:msg, value:Hello Quartz
2021-10-01 13:09:09.000  INFO 38484 --- [duler_Worker-10] t.pdai.springboot.quartz.job.HelloJob    : Hello Job执行时间: Wed Oct 27 13:09:09 CST 2021
2021-10-01 13:09:10.001  INFO 38484 --- [eduler_Worker-1] t.pdai.springboot.quartz.job.HelloJob    : param, key:msg, value:Hello Quartz
2021-10-01 13:09:10.002  INFO 38484 --- [eduler_Worker-1] t.pdai.springboot.quartz.job.HelloJob    : Hello Job执行时间: Wed Oct 27 13:09:10 CST 2021
2021-10-01 13:09:11.014  INFO 38484 --- [eduler_Worker-2] t.pdai.springboot.quartz.job.HelloJob    : param, key:msg, value:Hello Quartz
2021-10-01 13:09:11.014  INFO 38484 --- [eduler_Worker-2] t.pdai.springboot.quartz.job.HelloJob    : Hello Job执行时间: Wed Oct 27 13:09:11 CST 2021

实现案例 - 分布式方式

本例将展示quartz实现基于数据库的分布式任务管理,和控制job生命周期。

整体项目结构如下:

后端实现

  • pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"><parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>2.5.3</version><relativePath/> <!-- lookup parent from repository --></parent><modelVersion>4.0.0</modelVersion><groupId>tech.pdai</groupId><artifactId>423-springboot-demo-schedule-quartz-cluster</artifactId><version>1.0-SNAPSHOT</version><properties><maven.compiler.source>8</maven.compiler.source><maven.compiler.target>8</maven.compiler.target></properties><dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-quartz</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-jpa</artifactId></dependency><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><version>5.1.42</version><!--$NO-MVN-MAN-VER$--><scope>runtime</scope></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope></dependency><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><version>1.18.20</version><optional>true</optional></dependency><dependency><groupId>com.github.pagehelper</groupId><artifactId>pagehelper</artifactId><version>5.0.0</version></dependency></dependencies></project>
  • 创建Schema

需要提前在MySQL中创建schema: quartz_jobs

# DROP TABLE IF EXISTS QRTZ_FIRED_TRIGGERS;
# DROP TABLE IF EXISTS QRTZ_PAUSED_TRIGGER_GRPS;
# DROP TABLE IF EXISTS QRTZ_SCHEDULER_STATE;
# DROP TABLE IF EXISTS QRTZ_LOCKS;
# DROP TABLE IF EXISTS QRTZ_SIMPLE_TRIGGERS;
# DROP TABLE IF EXISTS QRTZ_SIMPROP_TRIGGERS;
# DROP TABLE IF EXISTS QRTZ_CRON_TRIGGERS;
# DROP TABLE IF EXISTS QRTZ_BLOB_TRIGGERS;
# DROP TABLE IF EXISTS QRTZ_TRIGGERS;
# DROP TABLE IF EXISTS QRTZ_JOB_DETAILS;
# DROP TABLE IF EXISTS QRTZ_CALENDARS;
# DROP TABLE IF EXISTS QRTZ_TASK_HISTORY;CREATE TABLE QRTZ_JOB_DETAILS(SCHED_NAME VARCHAR(120) NOT NULL,JOB_NAME VARCHAR(200) NOT NULL,JOB_GROUP VARCHAR(200) NOT NULL,DESCRIPTION VARCHAR(250) NULL,JOB_CLASS_NAME VARCHAR(250) NOT NULL,IS_DURABLE VARCHAR(1) NOT NULL,IS_NONCONCURRENT VARCHAR(1) NOT NULL,IS_UPDATE_DATA VARCHAR(1) NOT NULL,REQUESTS_RECOVERY VARCHAR(1) NOT NULL,JOB_DATA BLOB NULL,PRIMARY KEY (SCHED_NAME,JOB_NAME,JOB_GROUP))ENGINE=InnoDB;CREATE TABLE QRTZ_TRIGGERS (SCHED_NAME VARCHAR(120) NOT NULL,TRIGGER_NAME VARCHAR(200) NOT NULL,TRIGGER_GROUP VARCHAR(200) NOT NULL,JOB_NAME VARCHAR(200) NOT NULL,JOB_GROUP VARCHAR(200) NOT NULL,DESCRIPTION VARCHAR(250) NULL,NEXT_FIRE_TIME BIGINT(13) NULL,PREV_FIRE_TIME BIGINT(13) NULL,PRIORITY INTEGER NULL,TRIGGER_STATE VARCHAR(16) NOT NULL,TRIGGER_TYPE VARCHAR(8) NOT NULL,START_TIME BIGINT(13) NOT NULL,END_TIME BIGINT(13) NULL,CALENDAR_NAME VARCHAR(200) NULL,MISFIRE_INSTR SMALLINT(2) NULL,JOB_DATA BLOB NULL,PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP),FOREIGN KEY (SCHED_NAME,JOB_NAME,JOB_GROUP)REFERENCES QRTZ_JOB_DETAILS(SCHED_NAME,JOB_NAME,JOB_GROUP))ENGINE=InnoDB;CREATE TABLE QRTZ_SIMPLE_TRIGGERS (SCHED_NAME VARCHAR(120) NOT NULL,TRIGGER_NAME VARCHAR(200) NOT NULL,TRIGGER_GROUP VARCHAR(200) NOT NULL,REPEAT_COUNT BIGINT(7) NOT NULL,REPEAT_INTERVAL BIGINT(12) NOT NULL,TIMES_TRIGGERED BIGINT(10) NOT NULL,PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP),FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)REFERENCES QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP))ENGINE=InnoDB;CREATE TABLE QRTZ_CRON_TRIGGERS (SCHED_NAME VARCHAR(120) NOT NULL,TRIGGER_NAME VARCHAR(200) NOT NULL,TRIGGER_GROUP VARCHAR(200) NOT NULL,CRON_EXPRESSION VARCHAR(120) NOT NULL,TIME_ZONE_ID VARCHAR(80),PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP),FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)REFERENCES QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP))ENGINE=InnoDB;CREATE TABLE QRTZ_SIMPROP_TRIGGERS
(SCHED_NAME VARCHAR(120) NOT NULL,TRIGGER_NAME VARCHAR(200) NOT NULL,TRIGGER_GROUP VARCHAR(200) NOT NULL,STR_PROP_1 VARCHAR(512) NULL,STR_PROP_2 VARCHAR(512) NULL,STR_PROP_3 VARCHAR(512) NULL,INT_PROP_1 INT NULL,INT_PROP_2 INT NULL,LONG_PROP_1 BIGINT NULL,LONG_PROP_2 BIGINT NULL,DEC_PROP_1 NUMERIC(13,4) NULL,DEC_PROP_2 NUMERIC(13,4) NULL,BOOL_PROP_1 VARCHAR(1) NULL,BOOL_PROP_2 VARCHAR(1) NULL,PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP),FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)REFERENCES QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP))ENGINE=InnoDB;CREATE TABLE QRTZ_BLOB_TRIGGERS (SCHED_NAME VARCHAR(120) NOT NULL,TRIGGER_NAME VARCHAR(200) NOT NULL,TRIGGER_GROUP VARCHAR(200) NOT NULL,BLOB_DATA BLOB NULL,PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP),INDEX (SCHED_NAME,TRIGGER_NAME, TRIGGER_GROUP),FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)REFERENCES QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP))ENGINE=InnoDB;CREATE TABLE QRTZ_CALENDARS (SCHED_NAME VARCHAR(120) NOT NULL,CALENDAR_NAME VARCHAR(200) NOT NULL,CALENDAR BLOB NOT NULL,PRIMARY KEY (SCHED_NAME,CALENDAR_NAME))ENGINE=InnoDB;CREATE TABLE QRTZ_PAUSED_TRIGGER_GRPS (SCHED_NAME VARCHAR(120) NOT NULL,TRIGGER_GROUP VARCHAR(200) NOT NULL,PRIMARY KEY (SCHED_NAME,TRIGGER_GROUP))ENGINE=InnoDB;CREATE TABLE QRTZ_FIRED_TRIGGERS (SCHED_NAME VARCHAR(120) NOT NULL,ENTRY_ID VARCHAR(95) NOT NULL,TRIGGER_NAME VARCHAR(200) NOT NULL,TRIGGER_GROUP VARCHAR(200) NOT NULL,INSTANCE_NAME VARCHAR(200) NOT NULL,FIRED_TIME BIGINT(13) NOT NULL,SCHED_TIME BIGINT(13) NOT NULL,PRIORITY INTEGER NOT NULL,STATE VARCHAR(16) NOT NULL,JOB_NAME VARCHAR(200) NULL,JOB_GROUP VARCHAR(200) NULL,IS_NONCONCURRENT VARCHAR(1) NULL,REQUESTS_RECOVERY VARCHAR(1) NULL,PRIMARY KEY (SCHED_NAME,ENTRY_ID))ENGINE=InnoDB;CREATE TABLE QRTZ_SCHEDULER_STATE (SCHED_NAME VARCHAR(120) NOT NULL,INSTANCE_NAME VARCHAR(200) NOT NULL,LAST_CHECKIN_TIME BIGINT(13) NOT NULL,CHECKIN_INTERVAL BIGINT(13) NOT NULL,PRIMARY KEY (SCHED_NAME,INSTANCE_NAME))ENGINE=InnoDB;CREATE TABLE QRTZ_LOCKS (SCHED_NAME VARCHAR(120) NOT NULL,LOCK_NAME VARCHAR(40) NOT NULL,PRIMARY KEY (SCHED_NAME,LOCK_NAME))ENGINE=InnoDB;CREATE TABLE QRTZ_TASK_HISTORY (SCHED_NAME VARCHAR(120) NOT NULL,INSTANCE_ID VARCHAR(200) NOT NULL,FIRE_ID VARCHAR(95) NOT NULL,TASK_NAME VARCHAR(200) NULL,TASK_GROUP VARCHAR(200) NULL,FIRED_TIME BIGINT(13) NULL,FIRED_WAY VARCHAR(8) NULL,COMPLETE_TIME BIGINT(13) NULL,EXPEND_TIME BIGINT(13) NULL,REFIRED INT NULL,EXEC_STATE VARCHAR(10) NULL,LOG TEXT NULL,PRIMARY KEY (FIRE_ID)
)ENGINE=InnoDB;CREATE INDEX IDX_QRTZ_J_REQ_RECOVERY ON QRTZ_JOB_DETAILS(SCHED_NAME,REQUESTS_RECOVERY);
CREATE INDEX IDX_QRTZ_J_GRP ON QRTZ_JOB_DETAILS(SCHED_NAME,JOB_GROUP);CREATE INDEX IDX_QRTZ_T_J ON QRTZ_TRIGGERS(SCHED_NAME,JOB_NAME,JOB_GROUP);
CREATE INDEX IDX_QRTZ_T_JG ON QRTZ_TRIGGERS(SCHED_NAME,JOB_GROUP);
CREATE INDEX IDX_QRTZ_T_C ON QRTZ_TRIGGERS(SCHED_NAME,CALENDAR_NAME);
CREATE INDEX IDX_QRTZ_T_G ON QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_GROUP);
CREATE INDEX IDX_QRTZ_T_STATE ON QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_STATE);
CREATE INDEX IDX_QRTZ_T_N_STATE ON QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP,TRIGGER_STATE);
CREATE INDEX IDX_QRTZ_T_N_G_STATE ON QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_GROUP,TRIGGER_STATE);
CREATE INDEX IDX_QRTZ_T_NEXT_FIRE_TIME ON QRTZ_TRIGGERS(SCHED_NAME,NEXT_FIRE_TIME);
CREATE INDEX IDX_QRTZ_T_NFT_ST ON QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_STATE,NEXT_FIRE_TIME);
CREATE INDEX IDX_QRTZ_T_NFT_MISFIRE ON QRTZ_TRIGGERS(SCHED_NAME,MISFIRE_INSTR,NEXT_FIRE_TIME);
CREATE INDEX IDX_QRTZ_T_NFT_ST_MISFIRE ON QRTZ_TRIGGERS(SCHED_NAME,MISFIRE_INSTR,NEXT_FIRE_TIME,TRIGGER_STATE);
CREATE INDEX IDX_QRTZ_T_NFT_ST_MISFIRE_GRP ON QRTZ_TRIGGERS(SCHED_NAME,MISFIRE_INSTR,NEXT_FIRE_TIME,TRIGGER_GROUP,TRIGGER_STATE);CREATE INDEX IDX_QRTZ_FT_TRIG_INST_NAME ON QRTZ_FIRED_TRIGGERS(SCHED_NAME,INSTANCE_NAME);
CREATE INDEX IDX_QRTZ_FT_INST_JOB_REQ_RCVRY ON QRTZ_FIRED_TRIGGERS(SCHED_NAME,INSTANCE_NAME,REQUESTS_RECOVERY);
CREATE INDEX IDX_QRTZ_FT_J_G ON QRTZ_FIRED_TRIGGERS(SCHED_NAME,JOB_NAME,JOB_GROUP);
CREATE INDEX IDX_QRTZ_FT_JG ON QRTZ_FIRED_TRIGGERS(SCHED_NAME,JOB_GROUP);
CREATE INDEX IDX_QRTZ_FT_T_G ON QRTZ_FIRED_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP);
CREATE INDEX IDX_QRTZ_FT_TG ON QRTZ_FIRED_TRIGGERS(SCHED_NAME,TRIGGER_GROUP);CREATE INDEX IDX_QRTZ_TK_S ON QRTZ_TASK_HISTORY(SCHED_NAME);commit;
  • application.yml
spring:datasource:url: jdbc:mysql://localhost:3306/quartz_jobs?useUnicode=true&useSSL=falseusername: rootpassword: xxxxxxxxdriver-class-name: com.mysql.jdbc.Driverquartz:#相关属性配置properties:org:quartz:scheduler:instanceName: clusteredSchedulerinstanceId: AUTOjobStore:class: org.quartz.impl.jdbcjobstore.JobStoreTXdriverDelegateClass: org.quartz.impl.jdbcjobstore.StdJDBCDelegatetablePrefix: QRTZ_isClustered: trueclusterCheckinInterval: 10000useProperties: falsethreadPool:class: org.quartz.simpl.SimpleThreadPoolthreadCount: 10threadPriority: 5threadsInheritContextClassLoaderOfInitializingThread: true#数据库方式job-store-type: jdbc
  • 定义JobDetails实体
/** * @author pdai**/
@Data
public class JobDetails{private String cronExpression;   private String jobClassName;    private String triggerGroupName;private String triggerName;private String jobGroupName;private String jobName;private Date nextFireTime;private Date previousFireTime;private Date startTime;private String timeZone;private String status;
}
  • Job管理类
package tech.pdai.springboot.quartz.cluster.manager;import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;
import org.quartz.CronScheduleBuilder;
import org.quartz.CronTrigger;
import org.quartz.DateBuilder;
import org.quartz.DateBuilder.IntervalUnit;
import org.quartz.Job;
import org.quartz.JobBuilder;
import org.quartz.JobDetail;
import org.quartz.JobExecutionContext;
import org.quartz.JobKey;
import org.quartz.Scheduler;
import org.quartz.SchedulerException;
import org.quartz.SimpleScheduleBuilder;
import org.quartz.Trigger;
import org.quartz.TriggerBuilder;
import org.quartz.TriggerKey;
import org.quartz.impl.matchers.GroupMatcher;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.quartz.QuartzJobBean;
import org.springframework.stereotype.Component;
import tech.pdai.springboot.quartz.cluster.entity.JobDetails;/*** @author pdai*/
@Component
public class QuartzManager {@Autowiredprivate Scheduler sched;/*** 创建or更新任务,存在则更新不存在创建** @param jobClass     任务类* @param jobName      任务名称* @param jobGroupName 任务组名称* @param jobCron      cron表达式*/public void addOrUpdateJob(Class<? extends QuartzJobBean> jobClass, String jobName, String jobGroupName, String jobCron) {try {TriggerKey triggerKey = TriggerKey.triggerKey(jobName, jobGroupName);CronTrigger trigger = (CronTrigger) sched.getTrigger(triggerKey);if (trigger==null) {addJob(jobClass, jobName, jobGroupName, jobCron);} else {if (trigger.getCronExpression().equals(jobCron)) {return;}updateJob(jobName, jobGroupName, jobCron);}} catch (SchedulerException e) {e.printStackTrace();}}/*** 增加一个job** @param jobClass     任务实现类* @param jobName      任务名称* @param jobGroupName 任务组名* @param jobCron      cron表达式(如:0/5 * * * * ? )*/public void addJob(Class<? extends QuartzJobBean> jobClass, String jobName, String jobGroupName, String jobCron) {try {JobDetail jobDetail = JobBuilder.newJob(jobClass).withIdentity(jobName, jobGroupName).build();Trigger trigger = TriggerBuilder.newTrigger().withIdentity(jobName, jobGroupName).startAt(DateBuilder.futureDate(1, IntervalUnit.SECOND)).withSchedule(CronScheduleBuilder.cronSchedule(jobCron)).startNow().build();sched.scheduleJob(jobDetail, trigger);if (!sched.isShutdown()) {sched.start();}} catch (SchedulerException e) {e.printStackTrace();}}/*** @param jobClass* @param jobName* @param jobGroupName* @param jobTime*/public void addJob(Class<? extends Job> jobClass, String jobName, String jobGroupName, int jobTime) {addJob(jobClass, jobName, jobGroupName, jobTime, -1);}public void addJob(Class<? extends Job> jobClass, String jobName, String jobGroupName, int jobTime, int jobTimes) {try {JobDetail jobDetail = JobBuilder.newJob(jobClass).withIdentity(jobName, jobGroupName)// 任务名称和组构成任务key.build();// 使用simpleTrigger规则Trigger trigger;if (jobTimes < 0) {trigger = TriggerBuilder.newTrigger().withIdentity(jobName, jobGroupName).withSchedule(SimpleScheduleBuilder.repeatSecondlyForever(1).withIntervalInSeconds(jobTime)).startNow().build();} else {trigger = TriggerBuilder.newTrigger().withIdentity(jobName, jobGroupName).withSchedule(SimpleScheduleBuilder.repeatSecondlyForever(1).withIntervalInSeconds(jobTime).withRepeatCount(jobTimes)).startNow().build();}sched.scheduleJob(jobDetail, trigger);if (!sched.isShutdown()) {sched.start();}} catch (SchedulerException e) {e.printStackTrace();}}public void updateJob(String jobName, String jobGroupName, String jobTime) {try {TriggerKey triggerKey = TriggerKey.triggerKey(jobName, jobGroupName);CronTrigger trigger = (CronTrigger) sched.getTrigger(triggerKey);trigger = trigger.getTriggerBuilder().withIdentity(triggerKey).withSchedule(CronScheduleBuilder.cronSchedule(jobTime)).build();// 重启触发器sched.rescheduleJob(triggerKey, trigger);} catch (SchedulerException e) {e.printStackTrace();}}/*** 删除任务一个job** @param jobName      任务名称* @param jobGroupName 任务组名*/public void deleteJob(String jobName, String jobGroupName) {try {sched.pauseTrigger(TriggerKey.triggerKey(jobName, jobGroupName));sched.unscheduleJob(TriggerKey.triggerKey(jobName, jobGroupName));sched.deleteJob(new JobKey(jobName, jobGroupName));} catch (Exception e) {e.printStackTrace();}}/*** 暂停一个job** @param jobName* @param jobGroupName*/public void pauseJob(String jobName, String jobGroupName) {try {JobKey jobKey = JobKey.jobKey(jobName, jobGroupName);sched.pauseJob(jobKey);} catch (SchedulerException e) {e.printStackTrace();}}/*** 恢复一个job** @param jobName* @param jobGroupName*/public void resumeJob(String jobName, String jobGroupName) {try {JobKey jobKey = JobKey.jobKey(jobName, jobGroupName);sched.resumeJob(jobKey);} catch (SchedulerException e) {e.printStackTrace();}}/*** 立即执行一个job** @param jobName* @param jobGroupName*/public void runAJobNow(String jobName, String jobGroupName) {try {JobKey jobKey = JobKey.jobKey(jobName, jobGroupName);sched.triggerJob(jobKey);} catch (SchedulerException e) {e.printStackTrace();}}public PageInfo<JobDetails> queryAllJobBean(int pageNum, int pageSize) {PageHelper.startPage(pageNum, pageSize);List<JobDetails> jobList = null;try {GroupMatcher<JobKey> matcher = GroupMatcher.anyJobGroup();Set<JobKey> jobKeys = sched.getJobKeys(matcher);jobList = new ArrayList<>();for (JobKey jobKey : jobKeys) {List<? extends Trigger> triggers = sched.getTriggersOfJob(jobKey);for (Trigger trigger : triggers) {JobDetails jobDetails = new JobDetails();if (trigger instanceof CronTrigger) {CronTrigger cronTrigger = (CronTrigger) trigger;jobDetails.setCronExpression(cronTrigger.getCronExpression());jobDetails.setTimeZone(cronTrigger.getTimeZone().getDisplayName());}jobDetails.setTriggerGroupName(trigger.getKey().getName());jobDetails.setTriggerName(trigger.getKey().getGroup());jobDetails.setJobGroupName(jobKey.getGroup());jobDetails.setJobName(jobKey.getName());jobDetails.setStartTime(trigger.getStartTime());jobDetails.setJobClassName(sched.getJobDetail(jobKey).getJobClass().getName());jobDetails.setNextFireTime(trigger.getNextFireTime());jobDetails.setPreviousFireTime(trigger.getPreviousFireTime());jobDetails.setStatus(sched.getTriggerState(trigger.getKey()).name());jobList.add(jobDetails);}}} catch (SchedulerException e) {e.printStackTrace();}return new PageInfo<>(jobList);}/*** 获取所有计划中的任务列表** @return*/public List<Map<String, Object>> queryAllJob() {List<Map<String, Object>> jobList = null;try {GroupMatcher<JobKey> matcher = GroupMatcher.anyJobGroup();Set<JobKey> jobKeys = sched.getJobKeys(matcher);jobList = new ArrayList<>();for (JobKey jobKey : jobKeys) {List<? extends Trigger> triggers = sched.getTriggersOfJob(jobKey);for (Trigger trigger : triggers) {Map<String, Object> map = new HashMap<>();map.put("jobName", jobKey.getName());map.put("jobGroupName", jobKey.getGroup());map.put("description", "trigger:" + trigger.getKey());Trigger.TriggerState triggerState = sched.getTriggerState(trigger.getKey());map.put("jobStatus", triggerState.name());if (trigger instanceof CronTrigger) {CronTrigger cronTrigger = (CronTrigger) trigger;String cronExpression = cronTrigger.getCronExpression();map.put("jobTime", cronExpression);}jobList.add(map);}}} catch (SchedulerException e) {e.printStackTrace();}return jobList;}/*** 获取所有正在运行的job** @return*/public List<Map<String, Object>> queryRunJon() {List<Map<String, Object>> jobList = null;try {List<JobExecutionContext> executingJobs = sched.getCurrentlyExecutingJobs();jobList = new ArrayList<>(executingJobs.size());for (JobExecutionContext executingJob : executingJobs) {Map<String, Object> map = new HashMap<>();JobDetail jobDetail = executingJob.getJobDetail();JobKey jobKey = jobDetail.getKey();Trigger trigger = executingJob.getTrigger();map.put("jobName", jobKey.getName());map.put("jobGroupName", jobKey.getGroup());map.put("description", "trigger:" + trigger.getKey());Trigger.TriggerState triggerState = sched.getTriggerState(trigger.getKey());map.put("jobStatus", triggerState.name());if (trigger instanceof CronTrigger) {CronTrigger cronTrigger = (CronTrigger) trigger;String cronExpression = cronTrigger.getCronExpression();map.put("jobTime", cronExpression);}jobList.add(map);}} catch (SchedulerException e) {e.printStackTrace();}return jobList;}
}
  • Job控制器接口
package tech.pdai.springboot.quartz.cluster.controller;import java.util.HashMap;
import java.util.Map;import com.github.pagehelper.PageInfo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.quartz.QuartzJobBean;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import tech.pdai.springboot.quartz.cluster.entity.JobDetails;
import tech.pdai.springboot.quartz.cluster.manager.QuartzManager;/*** @author pdai*/
@RestController
@RequestMapping(value = "/job")
public class JobController {@Autowiredprivate QuartzManager qtzManager;@SuppressWarnings("unchecked")private static Class<? extends QuartzJobBean> getClass(String classname) throws Exception {Class<?> class1 = Class.forName(classname);return (Class<? extends QuartzJobBean>) class1;}/*** @param jobClassName* @param jobGroupName* @param cronExpression* @throws Exception*/@PostMapping(value = "/addjob")public void addjob(@RequestParam(value = "jobClassName") String jobClassName,@RequestParam(value = "jobGroupName") String jobGroupName,@RequestParam(value = "cronExpression") String cronExpression) throws Exception {qtzManager.addOrUpdateJob(getClass(jobClassName), jobClassName, jobGroupName, cronExpression);}/*** @param jobClassName* @param jobGroupName* @throws Exception*/@PostMapping(value = "/pausejob")public void pausejob(@RequestParam(value = "jobClassName") String jobClassName,@RequestParam(value = "jobGroupName") String jobGroupName) throws Exception {qtzManager.pauseJob(jobClassName, jobGroupName);}/*** @param jobClassName* @param jobGroupName* @throws Exception*/@PostMapping(value = "/resumejob")public void resumejob(@RequestParam(value = "jobClassName") String jobClassName,@RequestParam(value = "jobGroupName") String jobGroupName) throws Exception {qtzManager.resumeJob(jobClassName, jobGroupName);}/*** @param jobClassName* @param jobGroupName* @param cronExpression* @throws Exception*/@PostMapping(value = "/reschedulejob")public void rescheduleJob(@RequestParam(value = "jobClassName") String jobClassName,@RequestParam(value = "jobGroupName") String jobGroupName,@RequestParam(value = "cronExpression") String cronExpression) throws Exception {qtzManager.addOrUpdateJob(getClass(jobClassName), jobClassName, jobGroupName, cronExpression);}/*** @param jobClassName* @param jobGroupName* @throws Exception*/@PostMapping(value = "/deletejob")public void deletejob(@RequestParam(value = "jobClassName") String jobClassName,@RequestParam(value = "jobGroupName") String jobGroupName) throws Exception {qtzManager.deleteJob(jobClassName, jobGroupName);}/*** @param pageNum* @param pageSize* @return*/@GetMapping(value = "/queryjob")public Map<String, Object> queryjob(@RequestParam(value = "pageNum") Integer pageNum,@RequestParam(value = "pageSize") Integer pageSize) {PageInfo<JobDetails> jobAndTrigger = qtzManager.queryAllJobBean(pageNum, pageSize);Map<String, Object> map = new HashMap<String, Object>();map.put("JobAndTrigger", jobAndTrigger);map.put("number", jobAndTrigger.getTotal());return map;}
}
  • 定义具体的Job
package tech.pdai.springboot.quartz.cluster.job;import java.util.Date;import lombok.extern.slf4j.Slf4j;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.springframework.scheduling.quartz.QuartzJobBean;@Slf4j
public class HelloJob extends QuartzJobBean {@Overrideprotected void executeInternal(JobExecutionContext context) throws JobExecutionException {// get parameterscontext.getJobDetail().getJobDataMap().forEach((k, v) -> log.info("param, key:{}, value:{}", k, v));// your logicslog.info("Hello Job执行时间: " + new Date());}
}

前端实现

简单用VueJS 写个页面测试

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8"><title>QuartzDemo</title><link rel="stylesheet" href="https://unpkg.com/element-ui@2.0.5/lib/theme-chalk/index.css"><script src="https://unpkg.com/vue/dist/vue.js"></script><script src="http://cdn.bootcss.com/vue-resource/1.3.4/vue-resource.js"></script><script src="https://unpkg.com/element-ui@2.0.5/lib/index.js"></script><style>#top {background:#20A0FF;padding:5px;overflow:hidden}</style></head>
<body><div id="test">               <div id="top">            <el-button type="text" @click="search" style="color:white">查询</el-button>  <el-button type="text" @click="handleadd" style="color:white">添加</el-button>   </span>                       </div>    <br/><div style="margin-top:15px"> <el-tableref="testTable"         :data="tableData"style="width:100%"border><el-table-columnprop="status"label="任务状态"sortableshow-overflow-tooltip></el-table-column><el-table-columnprop="jobName"label="任务名称"sortableshow-overflow-tooltip></el-table-column><el-table-columnprop="jobGroupName"label="任务所在组"sortable></el-table-column><el-table-columnprop="jobClassName"label="任务类名"sortable></el-table-column><el-table-columnprop="triggerName"label="触发器名称"sortable></el-table-column><el-table-columnprop="triggerGroupName"label="触发器所在组"sortable></el-table-column><el-table-columnprop="cronExpression"label="表达式"sortable></el-table-column><el-table-columnprop="timeZone"label="时区"sortable></el-table-column><el-table-column label="操作" width="300"><template scope="scope"><el-buttonsize="small"type="warning"@click="handlePause(scope.$index, scope.row)">暂停</el-button><el-buttonsize="small"type="info"@click="handleResume(scope.$index, scope.row)">恢复</el-button><el-buttonsize="small"type="danger"@click="handleDelete(scope.$index, scope.row)">删除</el-button><el-buttonsize="small"type="success"@click="handleUpdate(scope.$index, scope.row)">修改</el-button></template></el-table-column></el-table><div align="center"><el-pagination@size-change="handleSizeChange"@current-change="handleCurrentChange":current-page="currentPage":page-sizes="[10, 20, 30, 40]":page-size="pagesize"layout="total, sizes, prev, pager, next, jumper":total="totalCount"></el-pagination></div></div> <el-dialog title="添加任务" :visible.sync="dialogFormVisible"><el-form :model="form"><el-form-item label="任务名称" label-width="120px" style="width:35%"><el-input v-model="form.jobName" auto-complete="off"></el-input></el-form-item>        <el-form-item label="任务分组" label-width="120px" style="width:35%"><el-input v-model="form.jobGroup" auto-complete="off"></el-input></el-form-item><el-form-item label="表达式" label-width="120px" style="width:35%"><el-input v-model="form.cronExpression" auto-complete="off"></el-input></el-form-item></el-form><div slot="footer" class="dialog-footer"><el-button @click="dialogFormVisible = false">取 消</el-button><el-button type="primary" @click="add">确 定</el-button></div></el-dialog><el-dialog title="修改任务" :visible.sync="updateFormVisible"><el-form :model="updateform"><el-form-item label="表达式" label-width="120px" style="width:35%"><el-input v-model="updateform.cronExpression" auto-complete="off"></el-input></el-form-item></el-form><div slot="footer" class="dialog-footer"><el-button @click="updateFormVisible = false">取 消</el-button><el-button type="primary" @click="update">确 定</el-button></div></el-dialog></div><footer align="center"><p>&copy; Quartz 任务管理</p></footer><script>var vue = new Vue({            el:"#test",data: {          //表格当前页数据tableData: [],//请求的URLurl:'job/queryjob',//默认每页数据量pagesize: 10,               //当前页码currentPage: 1,//查询的页码start: 1,//默认数据总数totalCount: 1000,//添加对话框默认可见性dialogFormVisible: false,//修改对话框默认可见性updateFormVisible: false,//提交的表单form: {jobName: '',jobGroup: '',cronExpression: '',},updateform: {jobName: '',jobGroup: '',cronExpression: '',},},methods: {//从服务器读取数据loadData: function(pageNum, pageSize){                    this.$http.get('job/queryjob?' + 'pageNum=' +  pageNum + '&pageSize=' + pageSize).then(function(res){console.log(res)this.tableData = res.body.JobAndTrigger.list;this.totalCount = res.body.number;},function(){console.log('failed');});                   },                                //单行删除handleDelete: function(index, row) {this.$http.post('job/deletejob',{"jobClassName":row.jobName,"jobGroupName":row.jobGroupName},{emulateJSON: true}).then(function(res){this.loadData( this.currentPage, this.pagesize);},function(){console.log('failed');});},//暂停任务handlePause: function(index, row){this.$http.post('job/pausejob',{"jobClassName":row.jobName,"jobGroupName":row.jobGroupName},{emulateJSON: true}).then(function(res){this.loadData( this.currentPage, this.pagesize);},function(){console.log('failed');});},//恢复任务handleResume: function(index, row){this.$http.post('job/resumejob',{"jobClassName":row.jobName,"jobGroupName":row.jobGroupName},{emulateJSON: true}).then(function(res){this.loadData( this.currentPage, this.pagesize);},function(){console.log('failed');});},//搜索search: function(){this.loadData(this.currentPage, this.pagesize);},//弹出对话框handleadd: function(){                     this.dialogFormVisible = true;                },//添加add: function(){this.$http.post('job/addjob',{"jobClassName":this.form.jobName,"jobGroupName":this.form.jobGroup,"cronExpression":this.form.cronExpression},{emulateJSON: true}).then(function(res){this.loadData(this.currentPage, this.pagesize);this.dialogFormVisible = false;},function(){console.log('failed');});},//更新handleUpdate: function(index, row){console.log(row)this.updateFormVisible = true;this.updateform.jobName = row.jobClassName;this.updateform.jobGroup = row.jobGroupName;},//更新任务update: function(){this.$http.post('job/reschedulejob',{"jobClassName":this.updateform.jobName,"jobGroupName":this.updateform.jobGroup,"cronExpression":this.updateform.cronExpression},{emulateJSON: true}).then(function(res){this.loadData(this.currentPage, this.pagesize);this.updateFormVisible = false;},function(){console.log('failed');});},//每页显示数据量变更handleSizeChange: function(val) {this.pagesize = val;this.loadData(this.currentPage, this.pagesize);},//页码变更handleCurrentChange: function(val) {this.currentPage = val;this.loadData(this.currentPage, this.pagesize);},       },      });//载入数据vue.loadData(vue.currentPage, vue.pagesize);</script>  </body>
</html>

测试效果

(PS: 这里的任务名称需要改成你自己的完整类名称)

展示正在运行的Jobs:

增加新的Job:

Jobs持久化在数据库:

示例源码

https://github.com/realpdai/tech-pdai-spring-demos

更多内容

告别碎片化学习,无套路一站式体系化学习后端开发: Java 全栈知识体系 https://pdai.tech

SpringBoot定时任务 - 集成quartz实现定时任务(单实例和分布式两种方式)相关推荐

  1. 【REACT NATIVE 系列教程之十三】利用LISTVIEW与TEXTINPUT制作聊天/对话框获取组件实例常用的两种方式...

    本站文章均为 李华明Himi 原创,转载务必在明显处注明:  转载自[黑米GameDev街区] 原文链接: http://www.himigame.com/react-native/2346.html ...

  2. 单链表销毁的两种方式

    单链表销毁的两种方式 在创建和新增申请单链表节点时,需要通过malloc函数手动申请内存空间.如果不对相应内存进行手动释放则会约积累越多,存在内存泄漏风险. 递归销毁 通过递归找到链表最后一个节点,依 ...

  3. springboot项目中利用@WebFilter注解和@Bean配置类两种方式实现Filter过滤器

    过滤器(Filter) 过滤器实际上就是对web资源进行拦截,做一些处理后再交给下一个过滤器或servlet处理.通常都是用来拦截request进行处理的,也可以对返回的response进行拦截处理 ...

  4. springBoot项目集成quartz开发定时任务案例及注意事项

    文章目录 quartz下载.安装 实战案例 常见问题及解决方案 quartz下载.安装 Quartz是完全由java开发的一个开源的任务日程管理系统,任务进度管理器就是一个在预先确定的时间到达时,负责 ...

  5. php 恶汉单例,单例模式的两种方式(恶汉式,懒汉式)

    单例模式的两种形式(恶汉式,懒汉式) 单例模式的特点:解决了一个类在内存的唯一性,这个类的对象只有一个. 写单例模式的步骤: 1. 私有修饰构造方法 2. 在本类的成员位置, new 自己类的对象 3 ...

  6. form表单提交数据的两种方式——submit直接提交、AJAX提交

    submit提交 form表单本身提供action属性,在action属性中填写数据提交地址后,点击submit类型的按钮即可将数据提交至指定地址,代码如下: <form action=&quo ...

  7. springboot项目启动成功后执行一段代码的两种方式

    实现ApplicationRunner接口 package com.lnjecit.lifecycle;import org.springframework.boot.ApplicationArgum ...

  8. java quartz 动态执行,浅谈SpringBoot集成Quartz动态定时任务

    SpringBoot自带schedule 沿用的springboot少xml配置的优良传统,本身支持表达式等多种定时任务 注意在程序启动的时候加上@EnableScheduling @Schedule ...

  9. SpringBoot集成Quartz(定时任务)

    SpringBoot集成Quartz(定时任务)_鱼找水需要时间的博客-CSDN博客_springboot集成quartz

最新文章

  1. int main(int argc,char *argv[]),主函数的参数问题
  2. 最全三大框架整合(使用映射)——Emp.hbm.xml
  3. 【Python】【Python库】Python3.7.2 - 字符串str类 (2)
  4. Redis performance --- delete 100 records at maximum
  5. 焦点图,带数字显示,支持常见浏览器
  6. Docker+K8s视频教程下载、学习笔记
  7. rdlc和rdl的区别
  8. 一道逻辑推理题---猜卡片的颜色和数字
  9. 100base-fx 单模/多模接口是什么意思
  10. 检查计算机无法更新,Win7提示Windows Update当前无法检查更新,因为未运行服务解决方法...
  11. linux指令buster是什么,FGO三类战斗指令卡有什么用 合理利用手中指令卡
  12. Karabiner Elements改键
  13. 无法将“obj\Debug\net6.0\MvcMovie.dll”复制到“bin\Debug\net6.0\MvcMovie.dll
  14. 【初创公司系列】由软件先驱Tom Siebel支持的机器学习创业公司C3.ai申请IPO
  15. Response对象-响应字符数据
  16. 国内外php商城系统 开源、php商城比较。
  17. 单相交流电机为什么需要电容才能正常启动?
  18. 【老生谈算法】matlab实现音乐合成算法源码——音乐合成算法
  19. Mac 硬件驱动 kext 安装方法
  20. 儿童护眼哪个牌子好?精选双十一必买的儿童护眼灯品牌

热门文章

  1. 简书地址http://www.jianshu.com/users/9fb081407820/latest_articles
  2. 学习python的第三节课:基础数据
  3. (转)LiveUpdate 错误LU1814的解决方法之我见
  4. OSError: [WinError 10048] 通常每个套接字地址(协议/网络地址/端口)只允许使用一次。
  5. 《剑指offer 阅读笔记一》程序员面试流程,面试游刃有余
  6. 摄像头视频直播方案比较之方案三:好望云
  7. Excel复杂表头构建
  8. 为什么要去考阿里云计算ACP认证?
  9. Android系统启动顺序(按下power键后所做的的工作)
  10. ConcurrentHashMap、HashTable学习