首页 > 其他分享 >Sring核心技术与最佳实践- 9.2 集成任务调度服务

Sring核心技术与最佳实践- 9.2 集成任务调度服务

时间:2022-10-12 12:37:55浏览次数:71  
标签:Sring 00 任务调度 定义 任务 Job Quartz 执行 9.2

Sring核心技术与最佳实践 第9章 9.2 集成任务调度服务

9.2 集成任务调度服务

并发所有的任务都是有用户发启请求而产生的,大多数系统都需要周期性地运行一些调度任务,比如,需要定时分析每天的日志记录,然后自动发送报告给系统管理员。要在JavaEE应用程序中实现这些调度任务,Spring提供了一个非常实用的调度器,可以方便的配置任务并定期执行。
根据Spring“不重新发明轮子”的原则,Spring框架没有自己实现调度器,而是提供了一个抽象层,它封装了JDK的Timer类和开源的Quartz调度器。


9.2.1 实用Timer调度任务

从JDK1.3 开始,提供了一个Timer类,可以实现周期性地执行某个任务,Spring对其包装为ScheduledTimerTask,设置起来非常简单。
为了能让系统周期性的分析日志,并发送邮件给管理员,在Eclipse中建立ReportTimerTask工程。

首先,需要定义一个任务,它派生自TimerTask。
public class ReportTimerTask extends TimerTask{

@Override
public void run() {
"read from log file at " +new Date();
System.out.println(log);
// analyse...
try {
Thread.sleep(1000);
}
catch(InterruptedException e) {
e.printStackTrace();
}
// send mail...
}
}


我们用Thread.sleep()模拟一个耗时的任务。至于发送邮件,前面有些文章有介绍,再此不说了,为了ReportTimerTask注入一个定义好的JavaMailSender对象,读者可以自己添加相关的依赖注入属性,完整地实现发送邮件的功能。


下一步是在XML配置文件中定义好reportTask和scheduledTask,其配置都相当的简单。

<!-- 单一任务 -->
<beanid="reportTask"class="com.zsw.timertask.ReportTimerTask"/>

<!-- 周期性任务 -->
<beanid="scheduledTask"class="org.springframework.scheduling.timer.ScheduledTimerTask">
<!-- 启动后等待10秒,然后开始执行 -->
<propertyname="delay"value="10000"/>
<!-- 每60秒执行一次 -->
<propertyname="period"value="1000"/>
<propertyname="timerTask"ref="reportTask"/>
</bean>

reportTask定义了一个独立的任务,而scheduledTask定义了周期性的任务。最后,通过定义一个timerFactory来启动这个周期性任务。

<!-- 启动任务的工厂 -->
<beanid="timerFactory"class="org.springframework.scheduling.timer.TimerFactoryBean">
<propertyname="scheduledTimerTasks">
<list>
<refbean="scheduledTask"/>
</list>
</property>
</bean>

在测试程序中,我们启动Spring容器,然后等待5分钟结束程序,
public static void main(String[] args) {
BeanFactory factory = new ClassPathXmlApplicationContext("config.xml");
// 等待5分钟,观察输出:
try {
Thread.sleep(3000000);
}
catch(InterruptedException e) {}
System.exit(0);
}

观察输出,可以看到,从容器启动后10秒开始,ReportTask以1分钟的间隔周期性运行。

如果志向运行一次任务,可以设定period的值为0。
另一个创建ScheduledTask的方法更简单,即使用MethodInvokingTimerTaskFactoryBean,它甚至不需要reportTask扩展TimerTask,只需要指定方法名称即可。

<beanid=""class="org.springframework.scheduling.timer.MethodInvokingTimerTaskFactoryBean">
<propertyname="targetObject"ref="reportTask"/>
<propertyname="targetMethod"value="run"/>
</bean>

注意:这个MethodInvokingTimerTaskFacotryBean是一个FactoryBean,因此Spring容器创建的实际对象类型是ScheduledTimerTask。

由于JDK提供的Timer功能有限,只能以固定的周期运行任务。如果希望以更灵活的方式运行任务,例如,希望每天凌晨3:00向管理员发送报告,每周星期一至星期五早上6:00向用户发送新闻,可以使用Quartz来实现更复杂的调度任务。


9.2.2 使用Quartz调度任务

Quartz是一个功能极为强大的任务调度器,可以任意指定周期性的任务,Quartz还支持将任务存储到数据库中,这样即使重启服务器也可以继续上次没有执行的任务。要使用Quartz,请从​​http://quartz-scheduler.org/downloads/catalog​​下载最新版本。


Quartz实现任务调度的几个关键概念如下:
(1) Job:定义一个任务,Job只管执行,不管什么时候执行,也不管执行多少次。
(2) Trigger:定义一个触发器,表示应当如何触发一个Job的执行。Quartz提供了许多简单的Trigger,可以实现某一时刻触发、周期性触发等多种触发方式,并且一个Job可以和多个Trigger关联,这样能更加灵活地执行Job。
(3) Scheduler:真正调度任务的调度器,通过scheduleJob(Job,Trigger)方法就把一个关联了Trigger的Job对象放入了调度器中执行。

用Quartz实现了一个Timer版本的日志发送童谣非常简单,却可以设置更多的调度方式。在Eclipse中建立了ReportQuartzTask工程。


Spring提供了两种方式来封装Quartz的Job,一种是直接派生自QuartzJobBean。例如,定义了一个Report对象向管理员发送报告,并且可以注入管理员的名字。
public class Report extends QuartzJobBean {

private Stringname;
public void setName(String name) {
this.name
}

@Override
protected void executeInternal(JobExecutionContext context) throws JobExecutionException {
System.out.println("Send report to " +name +" at " +new Date());
try {
Thread.sleep(1000);
}catch(InterruptedException e) {
e.printStackTrace();
}
}
}

在XML配置文件中将Report包装秤JobDetailBean

<beanname="reportJob"class="org.springframework.scheduling.quartz.JobDetailBean">
<propertyname="jobClass"value="com.zsw.quartz.Report"/>
<propertyname="jobDataAsMap">
<map>
<entrykey="name"value="Micheal"/>
</map>
</property>
</bean>

Spring会自动将jobDataAsMap属性中注入的Map按照key注入到Report对象的响应的属性中。

另一种方式是完全用最简单的POJO实现,例如,设计一个检查磁盘剩余空间的CheckDiskFreeSpace对象。
public class CheckDiskFreeSpace {

public void check() {
// get disk free space:
long freeSpace = Math.random() > 0.5 ? 100000000 : 200000000;
System.out.println("Check disk free space at " +new Date());
if(freeSpace<100*1024*1024) {// <100MB
System.out.println("Warning! Low disk free space...");
}
}
}

Check()方法是执行方法,下一步是在XML配置文件中将其也包装为 JobDetailBean。
<beanname="checkDiskFreeSpace"class="com.zsw.quartz.CheckDiskFreeSpace"/>

<beanname="checkDiskJob"class="org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean">
<propertyname="targetObject"ref="checkDiskFreeSpace"/>
<propertyname="targetMethod"value="check"/>
<propertyname="concurrent"value="false"/>
</bean>

concurrent属性指定了这个checkDiskJob是否能同时执行,默认为true;若设置为fasle,如果当前有一个checkDiskJob正在执行,则不启动相同的checkDiskJob。如果一个 Job执行时间较长,执行频率有比较高,定义concurrent为false可以避免多个相同的Job同时执行。
下一步是定义Trigger,它定义了在何时触发Job的执行。Spring封装了两种常用的TriggerBean,一种是SimpleTriggerBean,可以周期性的执行Job,为了让checkDisJob每个5分钟执行一次,配置如下:
<!-- 周期性运行checkDiskJob -->
<beanid="repeatTrigger"class="org.springframework.scheduling.quartz.SimpleTriggerBean">
<propertyname="jobDetail"ref="checkDiskJob"/>
<!-- 1分钟后启动 -->
<propertyname="startDelay"value="60000"/>
<!—1秒检查一次 -->
<propertyname="repeatInterval"value="1000"/>
</bean>

另一种CroTriggerBean功能更为强大,能指定何时执行Job。例如,希望reportJob在周一至周五中午12:00执行,就可以在XML配置文件中按如下方式定义。
<!-- 定时运行reportJob -->
<beanid="cronTrigger"class="org.springframework.scheduling.quartz.CronTriggerBean">
<propertyname="jobDetail"ref="reportJob"/>
<!-- 每周周一至周五中午12:00执行 -->
<propertyname="cronExpression"value="0 0 12 ? * MON-FRI"/>
</bean>

cronExpression表达式定义了Job的执行规则,稍后我们将详细讨论。现在,我们希望启动这些定义好的Job,在XML配置文件中定义一个Scheduler,然后将所有的TriggerBean都注入进去即可。

<!-- 启动调度器 -->
<beanid="scheduler"class="org.springframework.scheduling.quartz.SchedulerFactoryBean">
<propertyname="triggers">
<list>
<refbean="repeatTrigger"/>
<refbean="cronTrigger"/>
</list>
</property>
</bean>

最后,编写main()方法启动Spring容器。
public class Main {
public static void main(String[] args) {
new ClassPathXmlApplicationContext("config.xml");
}
}

运行程序,观察输出.
Check disk free space at Sat Apr 21 01:37:34 CST 2012
Check disk free space at Sat Apr 21 01:37:35 CST 2012
Check disk free space at Sat Apr 21 01:37:36 CST 2012
Warning! Low disk free space...

可以看到checkDiskJob按照1秒的间隔运行,而reportJob在周二的中午12:00执行了!为了能看到执行效果,你可能需要修改执行时间,否则最长要等到24小时才能看到reportJob第一次被执行。

现在我们来讨论一下cronExpression的写法。cronExpression用于定义触发Job的时间,由7各部分组成,分别代表:
(1) 秒;
(2) 分;
(3) 小时;
(4) 一月中的某天;
(5) 月份;
(6) 星期;
(7) 年份(可选,通常不用指定,因为以年为周期的执行任务通常不需要Quartz来完成)

例如,“0 0 12 ? * MON”表示周一12:00:00执行,?表示忽略月份中的天数,因为通常要么按照日期执行,要么按照星期指定,*表示全部,即每月都执行。

可以使用“,”指定一个列表,例如,“0 0 6 1,3,5 * ?”表示每月1号、3号和5号的早上6:00执行,还可以使用“-”指定一个范围,例如,“0 0 6 ? * MON-FRI”表示周一至周五早上6:00执行。

可以使用“/”指定时间间隔,例如,设置分钟为“0/15”表示从0分钟开始,每隔15分钟执行一次,而”3/20”表示从3分钟开始,每隔20分钟执行一次,这和“3,23,43”的效果是完全一样的。

以下是一些常用的表达式。
“0 0/5 * * * ?”表示每隔5分钟执行一次;
“10 0/5 * * *?”表示每隔5分钟,在10秒时执行,例如:00:10、05:10;
“0 30 10-20 ?* WED,FRI”表示每周三和每周五在10:30、11:30、12:30执行;
“0 0/30 9-171-5,10 * ?” 表示在每月1号到5号,以及10号这几天,每天从9:00到17:00每隔30分钟执行一次;

一些更复杂的调度可能无法以一个cronExpression表示出来,此时,可以考虑将其分拆为几个cronExpression,然后将这几个CrontriggerBean都添加到Scheduler中。

标签:Sring,00,任务调度,定义,任务,Job,Quartz,执行,9.2
From: https://blog.51cto.com/u_15739274/5749377

相关文章

  • 分布式定时任务调度框架实践
    分布式任务调度框架几乎是每个大型应用必备的工具,本文介绍了任务调度框架使用的需求背景和痛点,对业界普遍使用的开源分布式任务调度框架的使用进行了探究实践,并分析了这几种......
  • leetcode-621. 任务调度器
    621.任务调度器假设有任务["A","A","A","B","B","B"],n=2,可以画图表示CPU的时间分配MT表示maxTime,这个任务列表中出现次数最大的任务数量,这里就是MT=3MC表示maxCou......
  • 分布式任务调度开源框架
    背景无论是互联网应用或者企业级应用,都充斥着大量的批处理任务。我们常常需要一些任务调度系统帮助我们解决问题。随着微服务化架构的逐步演进,单体架构逐渐演变为分布式、......
  • CentOS 7.9 安装 rocketmq-4.9.2
    一、CentOS7.9安装rocketmq-4.9.2地址:https://rocketmq.apache.orghttps://github.com/apache/rocketmqhttps://archive.apache.org/dist/rocketmq/4.9.2/rocketmq......
  • 并发学习记录16:任务调度线程池
    在任务调度池功能加入之前,可以使用java.util.Timer来实现定时功能,Timer的优点在于简单易用,但由于所有任务都是由同一个线程来调度,因此所有任务都是串行执行的,同一时间只能......
  • 9.29题目大赏
    2022-9-29前两道真的很水,然而我写挂了QAQT1智力大冲浪简单得不能再简单的贪心。先将每个游戏按扣钱为第一关键字降序,时刻为第二关键字升序排列。因为我们希望无法完成......
  • 9.22面试题
    请你说说内存管理?linux操作系统采用段页式内存管理方式页式内存管理方式可以有效的提高内存利用率段式内存管理能反映程序的逻辑结构并有利于段的共享段页式存储管......
  • P4939 大师(9.20)
    题面:戳这里题意:给你n个数,让你找出差分序列的个数并取模(直接说人话)思路:常用的解题步骤:第一步:确定子问题。对于本题子问题即为当前有i个塔,他的方案数为多少。第二步:......
  • P4040保龄球(9.26)
    题面:戳这里题意概括:有一种叫做保龄球的运动,它有以下几种规则①每一回合都分上下两轮,每轮都能投回球,每回都能打中一定数量的木瓶②每一回合的得分为当前这个回合的得分......
  • React源码解读之任务调度
    前言简单说下为什么React选择函数式组件,主要是class组件比较冗余、生命周期函数写法不友好,骚写法多,functional组件更符合React编程思想等等等。更具体的可以拜读dan大神的b......