SpringBoot 定时任务及Quartz定时框架

发布时间:2021-12-03 11:05:45

定时任务创建方法与特点

springboot定时任务主要有以下三种创建方式:


1、基于注解(@Scheduled)


基于注解@Scheduled默认为单线程,开启多个任务时,任务的执行时机会受上一个任务执行时间的影响。

@Configuration //标记配置类
@EnableScheduling // 开启定时任务
public class SaticScheduleTask {
//添加定时任务
//cron表达式:cron是一个字符串,之间通过空格隔开,分割成6,7个域,每个域代表一种含义
@Scheduled(cron = "")
//或直接指定时间间隔,例如:5秒
//@Scheduled(fixedRate=5000)
private void configureTasks() {
System.err.println("定时任务执行时间" + LocalDateTime.now());
}
}

@Scheduled:除了支持灵活的参数表达式cron之外,还支持简单的延时操作,例如 fixedDelay ,fixedRate 填写相应的毫秒数即可。使用@Scheduled 注解很方便,但缺点是当我们调整了执行周期的时候,需要*粲τ貌拍苌В舛嗌儆行┎环奖恪N舜锏绞凳鄙У男Ч梢允褂媒涌诶赐瓿啥ㄊ比挝 。

2、基于接口(SchedulingConfigurer) 前者相信大家都很熟悉,但是实际使用中我们往往想从数据库中读取指定时间来动态执行定时任务,这时候基于接口的定时任务就派上用场了。


public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
taskRegistrar.addTriggerTask(
//1.添加任务内容(Runnable)
() -> System.out.println("执行定时任务: " + LocalDateTime.now().toLocalTime()),
//2.设置执行周期(Trigger)
triggerContext -> {
//2.1 从数据库获取执行周期
String cron = cronMapper.getCron();
//2.2 合法性校验.
if (StringUtils.isEmpty(cron)) {
// Omitted Code ..
}
//2.3 返回执行周期(Date)
return new CronTrigger(cron).nextExecutionTime(triggerContext);
}
);
}

3、基于注解设定多线程定时任务


//@Component注解用于对那些比较中立的类进行注释;
//相对与在持久层、业务层和控制层分别采用 @Repository、@Service 和 @Controller 对分层中的类进行注释
@Component
@EnableScheduling // 1.开启定时任务
@EnableAsync // 2.开启多线程
public class MultithreadScheduleTask {

@Async
@Scheduled(fixedDelay = 1000) //间隔1秒
public void first() throws InterruptedException {
System.out.println("第一个定时任务开始 : " + LocalDateTime.now().toLocalTime() + "
线程 : " + Thread.currentThread().getName());
System.out.println();
Thread.sleep(1000 * 10);
}

@Async
@Scheduled(fixedDelay = 2000)
public void second() {
System.out.println("第二个定时任务开始 : " + LocalDateTime.now().toLocalTime() + "
线程 : " + Thread.currentThread().getName());
System.out.println();
}
}

在Spring中,基于@Async标注的方法,称之为异步方法;这些方法在执行的时候,将会在独立的线程中被执行,调用者无需等待它的完成,即可继续其他的操作。
定时任务框架: Quartz

什么是Quartz



Quartz是OpenSymphony开源组织在Job scheduling领域又一个开源项目,完全由Java开发,可以用来执行定时任务,类似于java.util.Timer。但是相较于Timer, Quartz增加了很多功能:


持久性作业 - 就是保持调度定时的状态;
作业管理 - 对调度作业进行有效的管理;



在我们实际的项目中,当Job过多的时候,肯定不能人工去操作,这时候就需要一个任务调度框架,帮我们自动去执行这些程序。


    首先我们需要定义实现一个定时功能的接口,我们可以称之为Task(或Job)有了任务之后,还需要一个能够实现触发任务去执行的触发器,触发器Trigger最基本的功能是指定Job的执行时间,执行间隔,运行次数等。有了Job和Trigger后,怎么样将两者结合起来呢?即怎样指定Trigger去执行指定的Job呢?这时需要一个Schedule,来负责这个功能的实现。

上面三个部分就是Quartz的基本组成部分:


调度器:Scheduler任务:JobDetail触发器:Trigger,包括SimpleTrigger和CronTrigger

Quartz框架使用


Scheduler在使用之前需要实例化。一般通过SchedulerFactory来创建一个实例。scheduler实例化后,可以启动(start)、暂停(stand-by)、停止(shutdown)。注意:scheduler被停止后,除非重新实例化,否则不能重新启动;只有当scheduler启动后,即使处于暂停状态也不行,trigger才会被触发(job才会被执行)。

SchedulerFactory schedFact = new org.quartz.impl.StdSchedulerFactory();

Scheduler sched = schedFact.getScheduler();

sched.start();

// 定义任务并将其绑定到我们的HelloJob类
JobDetail job = newJob(HelloJob.class)
.withIdentity("myJob", "group1")
.build();

// 触发器定时每四十秒触发
Trigger trigger = newTrigger()
.withIdentity("myTrigger", "group1")
.startNow()
.withSchedule(simpleSchedule()
.withIntervalInSeconds(40)
.repeatForever())
.build();

// Tell quartz to schedule the job using our trigger
sched.scheduleJob(job, trigger);


Quartz框架具体API,Job和Trigger


Quartz API核心接口有:


Scheduler ? 与scheduler交互的主要API;Job ? 你通过scheduler执行任务,你的任务类需要实现的接口;JobDetail ? 定义Job的实例;Trigger ? 触发Job的执行;JobBuilder ? 定义和创建JobDetail实例的接口;TriggerBuilder ? 定义和创建Trigger实例的接口;

说明:


Scheduler的生命期,从SchedulerFactory创建它时开始,到Scheduler调用shutdown()方法时结束;Scheduler被创建后,可以增加、删除和列举Job和Trigger,以及执行其它与调度相关的操作(如暂停Trigger)。但是,Scheduler只有在调用start()方法后,才会真正地触发trigger(即执行job)

SchedulerBuilder接口的各种实现类,可以定义不同类型的调度计划(schedule);

DateBuilder类包含很多方法,可以很方便地构造表示不同时间点的java.util.Date实例(如定义下一个小时为偶数的时间点,如果当前时间为9:43:27,则定义的时间为10:00:00)。


Job


一个job就是一个实现了Job接口的类,该接口只有一个方法:

package org.quartz;

public interface Job {
void execute(JobExecutionContext var1) throws JobExecutionException;
}

当job的一个trigger被触发后,execute()方法会被scheduler的一个工作线程调用;传递给execute()方法的JobExecutionContext对象中保存着该job运行时的一些信息 ,执行job的scheduler的引用,触发job的trigger的引用,JobDetail对象引用,以及一些其它信息。JobDetail对象是在将job加入scheduler时,由客户端程序创建的。它包含job的各种属性设置,以及用于存储job实例状态信息的JobDataMap,例如

JobDetail jobDetail = newJob(HelloJob.class)
.withIdentity("myJob" + myJob.getJobId())
.usingJobData("myJob", JSONObject.toJSONString(myJob))
.build();

Trigger


Trigger用于触发Job的执行。当你准备调度一个job时,你创建一个Trigger的实例,然后设置调度相关的属性。Trigger也有一个相关联的JobDataMap,用于给Job传递一些触发相关的参数。Quartz自带了各种不同类型的Trigger,最常用的主要是SimpleTrigger和CronTrigger。SimpleTrigger主要用于一次性执行的Job(只在某个特定的时间点执行一次),或者Job在特定的时间点执行,重复执行N次,每次执行间隔T个时间单位。CronTrigger在基于日历的调度上非常有用,如“每个星期五的正午”,或者“每月的第十天的上午10:15”等。为什么既有Job,又有Trigger呢?很多任务调度器并不区分Job和Trigger。有些调度器只是简单地通过一个执行时间和一些job标识符来定义一个Job;其它的一些调度器将Quartz的Job和Trigger对象合二为一。例如,Job被创建后,可以保存在Scheduler中,与Trigger是独立的,同一个Job可以有多个Trigger;这种松耦合的另一个好处是,当与Scheduler中的Job关联的trigger都过期时,可以配置Job稍后被重新调度,而不用重新定义Job;还有,可以修改或者替换Trigger,而不用重新定义与之关联的Job。

Key


将Job和Trigger注册到Scheduler时,可以为它们设置key,配置其身份属性。Job和Trigger的key(JobKey和TriggerKey)可以用于将Job和Trigger放到不同的分组(group)里,然后基于分组进行操作。同一个分组下的Job或Trigger的名称必须唯一,即一个Job或Trigger的key由名称(name)和分组(group)组成。

JobDetail


public class HelloJob implements Job {

public HelloJob() {
}

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

可以看到,我们传给scheduler一个JobDetail实例,因为我们在创建JobDetail时,将要执行的job的类名传给了JobDetail,所以scheduler就知道了要执行何种类型的job;每次当scheduler执行job时,在调用其execute(…)方法之前会创建该类的一个新的实例;执行完毕,对该实例的引用就被丢弃了,实例会被垃圾回收;这种执行策略带来的一个后果是,job必须有一个无参的构造函数(当使用默认的JobFactory时);另一个后果是,在job类中,不应该定义有状态的数据属性,因为在job的多次执行中,这些属性的值不会保留。

那么如何给job实例增加属性或配置呢?如何在job的多次执行中,跟踪job的状态呢?
答案就是:JobDataMap,JobDetail对象的一部分。


JobDataMap


JobDataMap中可以包含不限量的(序列化的)数据对象,在job实例执行的时候,可以使用其中的数据;JobDataMap是Java Map接口的一个实现,额外增加了一些便于存取基本类型的数据的方法。将job加入到scheduler之前,在构建JobDetail时,可以将数据放入JobDataMap,如下示例:

JobDetail jobDetail = newJob(HelloJob.class)
.withIdentity("myJob" + myJob.getJobId())
.usingJobData("myJob", JSONObject.toJSONString(myJob))
//可以写入多个数据
.usingJobData("jobSays", "Hello World!")
.build();

usingJobData方法


public JobBuilder usingJobData(String dataKey, String value) {
this.jobDataMap.put(dataKey, value);
return this;
}

在job的执行过程中,可以从JobDataMap中取出数据,如下示例:


public class HelloJob implements Job {

@Autowired
private ProjectService projectService;
@Autowired
private RegularService regularService;
@Autowired
private CompareService compareService;
@Autowired
private InterfaceService interfaceService;

@Override
public void execute(JobExecutionContext context) {
//获取trigger
Trigger trigger = context.getTrigger();
//获取数据
JobDataMap jobDataMap = context.getJobDetail().getJobDataMap();
String jobJsonString = jobDataMap.getString("myJob");
//fastJson 解析成具体实体类对象
cn.XXX.ict.entity.Job job = JSONObject.parseObject(jobJsonString, cn.XXX.ict.entity.Job.class);
//通过trigger获取job标识
JobKey jobKey = trigger.getJobKey();

//主要任务
......
}
}

注意


如果你使用的是持久化的存储机制,在决定JobDataMap中存放什么数据的时候需要小心,因为JobDataMap中存储的对象都会被序列化,因此很可能会导致类的版本不一致的问题;

如果你在job类中,为JobDataMap中存储的数据的key增加set方法那么Quartz的默认JobFactory实现在job被实例化的时候会自动调用这些set方法,这样你就不需要在execute()方法中显式地从map中取数据了。

在Job执行时,JobExecutionContext中的JobDataMap为我们提供了很多的便利。它是JobDetail中的JobDataMap和Trigger中的JobDataMap的并集,但是如果存在相同的数据,则后者会覆盖前者的值。


Job的其它特性
通过JobDetail对象,可以给job实例配置的其它属性有:


Durability:如果一个job是非持久的,当没有活跃的trigger与之关联的时候,会被自动地从scheduler中删除。也就是说,非持久的job的生命期是由trigger的存在与否决定的;RequestsRecovery:如果一个job是可恢复的,并且在其执行的时候,scheduler发生硬关闭(hard shutdown)(比如运行的进程崩溃了,或者关机了),则当scheduler重新启动的时候,该job会被重新执行。此时,该job的JobExecutionContext.isRecovering() 返回true。

JobExecutionException


最后,是关于Job.execute(…)方法的一些额外细节。execute方法中仅允许抛出一种类型的异常(包括RuntimeExceptions),即JobExecutionException。因此,应该将execute方法中的所有内容都放到一个”try-catch”块中。
本项目中代码实例

线程池配置



quartz:
properties:
org:
quartz:
threadPool:
class: org.quartz.simpl.SimpleThreadPool
threadCount: 50
threadPriority: 5
threadsInheritContextClassLoaderOfInitializingThread: true

如何为Quartz的Job自动装配Spring容器Bean


Quartz配置


@Configuration
public class QuartzConfig {

@Autowired
private MyJobFactory myJobFactory;

public QuartzConfig(MyJobFactory jobFactory){
this.myJobFactory = jobFactory;
}

/**
* 配置SchedulerFactoryBean
* 将一个方法产生为Bean并交给Spring容器管理
* @return
*/
@Bean
public SchedulerFactoryBean schedulerFactoryBean() {
// Spring提供SchedulerFactoryBean为Scheduler提供配置信息,并被Spring容器管理其生命周期
SchedulerFactoryBean factory = new SchedulerFactoryBean();
// 设置自定义Job Factory,用于Spring管理Job bean
factory.setJobFactory(myJobFactory);
return factory;
}

@Bean(name = "scheduler")
public Scheduler scheduler() {
return schedulerFactoryBean().getScheduler();
}
}


MyJobFactory


@Component
public class MyJobFactory extends AdaptableJobFactory {

//这个对象Spring会帮我们自动注入进来,也属于Spring技术范畴.
@Autowired
private AutowireCapableBeanFactory capableBeanFactory;

@Override
protected Object createJobInstance(TriggerFiredBundle bundle) throws Exception {
//调用父类的方法
Object jobInstance = super.createJobInstance(bundle);
//进行注入,这属于Spring的技术,不清楚的可以查看Spring的API.
capableBeanFactory.autowireBean(jobInstance);
return jobInstance;
}
}

依赖:



org.quartz-scheduler
quartz
2.2.3


org.quartz-scheduler
quartz-jobs
2.2.3


org.springframework
spring-context-support
5.1.5.RELEASE


Controller层调用


@CrossOrigin
@RestController
@RequestMapping("/job")
@Api(tags = "4", description = "定时器管理")
public class JobController {
@Autowired
private RegularService regularService;
@Autowired
private JobService jobService;
@Autowired
private ProjectService projectService;
@Autowired
private JobTask jobTask;
@PostMapping("/create")
@ApiOperation(value = "创建定时任务", notes = "创建定时任务")
@Transactional
public Result create(String projectName, String startTime, String finishTime, String frequency, String srcVersion) {
//实体类
MyJob myJob = new MyJob ();
String jobId = UUID.randomUUID().toString();
myJob .setJobId(jobId);
myJob .setProjectId(projectId);
myJob .setStartTime(DateUtil.parse(startTime, DateUtil.YCHAR_06));
myJob .setFinishTime(DateUtil.parse(finishTime, DateUtil.YCHAR_06));
myJob .setFrequency(frequency);
myJob .setSrcVersion(srcVersion);
jobService.save(myJob );

//执行定时任务
jobTask.addJob(myJob);
return Result.success();
}
}

JobTask


@Component
@Slf4j
public class JobTask {

@Autowired
private JobService jobService;
@Autowired
private Scheduler scheduler;
public void updateJob(Job myJob) {
removeJob(myJob);
addJob(myJob);
}

public void removeJob(MyJob myJob) {
try {
TriggerKey triggerKey = TriggerKey.triggerKey("myTrigger" + myJob.getJobId(), "group1");

scheduler.pauseTrigger(triggerKey);
scheduler.unscheduleJob(triggerKey);
} catch (SchedulerException e) {
log.error("error", e);
}
}

public void addJob(MyJob myJob) {

try {
JobDetail jobDetail = newJob(HelloJob.class)
.withIdentity("myJob" + myJob.getJobId())
.usingJobData("myJob", JSONObject.toJSONString(myJob))
.build();
Date startTime = myJob.getStartTime();

Date endTime = myJob.getFinishTime();
CronTrigger trigger = TriggerBuilder.newTrigger()
.withIdentity("myTrigger" + myJob.getJobId(), "group1")
.startAt(startTime)
.endAt(endTime)
.withSchedule(CronScheduleBuilder.cronSchedule(myJob.getFrequency()))
.build();


//将trigger和jobDetail加入这个调度
scheduler.scheduleJob(jobDetail, trigger);

//启动scheduler
scheduler.start();
} catch (Exception e) {
log.error("error", e);
}
}
}

HelloJob


public class HelloJob implements Job {

@Autowired
private ProjectService projectService;
@Autowired
private RegularService regularService;
@Autowired
private CompareService compareService;
@Autowired
private InterfaceService interfaceService;

@Override
public void execute(JobExecutionContext context) {
//获取trigger
Trigger trigger = context.getTrigger();
//获取数据
JobDataMap jobDataMap = context.getJobDetail().getJobDataMap();
String jobJsonString = jobDataMap.getString("myJob");
//fastJson 解析成具体实体类对象
cn.XXX.ict.entity.Job job = JSONObject.parseObject(jobJsonString, cn.XXX.ict.entity.Job.class);
//通过trigger获取job标识
JobKey jobKey = trigger.getJobKey();

//主要任务
......
}
}

相关文档

  • 2016年少儿歌唱方法指导
  • 农历2020年观世音菩萨成道日是什么时候 具体时间
  • 溜冰的乐趣作文400字
  • 2020年北京高考命题和考察方向原则上无变化
  • 快乐的劳动节400字
  • 孟子见梁襄王原文翻译及知识点
  • 人体传感器
  • pdd直播的时候玩的steam的小游戏叫什么
  • 保护环境小学生手抄报资料
  • 2017年河南省退休金上调和企业退休工资标准
  • 精品市场调查报告4篇
  • WPSOFFICE2016怎样将docx转换为doc
  • 《旋风孝子》观后感
  • 银行风险管理部工作总结
  • 书,我的好伙伴作文600字
  • 异常:ApkProvisionException: No outputs for the main artifact of variant: debug
  • 中职德育课教学计划
  • 敷面膜后皮肤紧绷是怎么回事,敷完面膜脸紧绷,敷完面膜后感觉脸紧绷是怎么回事
  • 测试性格的问题
  • 罗布麻茶的副作用
  • 食品厂暑假实习报告范文3篇
  • 幼儿园小班教育随笔
  • 电影剪辑技巧口诀
  • 电脑不能正常关机无法重启怎么办
  • Pycharm中文输入法不跟随问题(转载)
  • 日本史学家白鸟库吉的评价
  • c语言 结构体_C语言学习之结构体
  • 认真学习贯彻信访条例积极探索信访创新
  • 投资小利润大的生意有哪些
  • 人工智能(mysql)?? mysql高级查询(索引、多表、连接)
  • 猜你喜欢

  • 房建类(仓库)施工组织设计
  • 高三政治一轮复*第三单元中华文化与民族精神6我们的中华文化课件新人教必修3
  • 2020年度学校师德建设工作计划
  • 2020版高考地理一轮总复*第九章农业地域的形成与发展第2讲农业地域类型练*课件新人教版
  • FLIR E60红外热像仪特性描述和资料下载
  • 韩国泡菜的腌制方法
  • 万科客户关系管理
  • 女人不能缺少的十种养颜食物
  • 云南省2018年保育员三级考试试题试卷及答案
  • 杭州峰波网络科技有限公司企业信息报告-天眼查
  • 中医执业医师中医诊断学基础练*题及答案
  • 怎么把订单记录删除
  • 周溪小学预防食物中毒和疫情处理工作预案
  • 2016-2021年中国高低铬合金球市场深度调研及投资策略分析报告(目录)
  • 筑梦情缘在哪个台播出筑梦情缘剧情介绍
  • 给外星人的一封信_三年级书信作文_2
  • 江苏巡抚程德全有哪些黑历史?称其为英雄一点不为过
  • 八大国画宗师传人真迹典藏
  • 怎么把眼睛弄大
  • 2011年明细账模板(2)
  • 2019届高考英语一轮复*第一部分教材课文要点Module5ALessoninaLab语篇解题微技巧
  • 瑜伽6式 让脊椎恢复年轻态
  • 第十章 国际商事仲裁法
  • Error:java: 无效的标记 -version 编译错误的解决办法
  • LVDS(低电压差分信号)原理简介
  • 岐山县唯一无纺布制品有限公司企业信用报告-天眼查
  • 2016猜歌王答案
  • 高考复*文言文(文言翻译)PPT课件15
  • 亳州市谯城区盛佳中药材种植农民专业合作社企业信息报告-天眼查
  • ‘我的逍遥学伴’在电影产业的困境中征求赞助
  • 2019首师大小学科学六上《11.人体的运动》word教案(1)
  • 高级护理专业简历模板20XX
  • 尚普咨询:太阳能热发电发展潜力巨大
  • 医学-*回收技术的临床应用
  • 最新-市人大机关扶贫工作计划 精品
  • 泪水作文400字
  • 沈阳恒佳皮业有限公司企业信用报告-天眼查
  • OSP在印刷电路板的应用
  • 公司户外活动有哪些
  • ASP聊天室实验报告
  • 软件项目计划书
  • 路由器和调制解调器的区别_调制解调器路由器和接入点有什么区别
  • 电脑版