写点什么

Spring 之定时调度

作者:andy
  • 2022-10-27
    北京
  • 本文字数:4344 字

    阅读完需:约 14 分钟

一、定时调度


定时调度指的是在一定时间条件下,系统自动完成特定功能的运行机制。传统的定时调度分为两种:


  • 定时调度:在一定的时间点自动执行程序功能;

  • 间隔调度:以一定的时间间隔,不断自动执行程序功能。


对于定时调度而言,所依赖的便是计算机系统底层的时钟发生器。在 Java 技术中,提供了两个操作类:Timer、TimerTask,其中,TimerTask 用于定义定时调度所要执行的任务和功能,线程运行时调用。


范例:


new Timer().scheduleAtFixedRate(new TimerTask() {			private Logger logger = LoggerFactory.getLogger(TimerTask.class);			@Override			public void run() {				logger.info(new Date().toString());			}		}, new Date(), 2000);
复制代码


随着任务调度的复杂度提升,单纯地使用以上基础类进行编写已显得非常麻烦,因此,对于企业级项目而言,提供了以下两种方式实现任务调度。

Quartz:第三方的任务调度组件,需要进行配置;

Spring Task:spring 提供了任务调度模块,配置简单,可使用 annotation 实现。


二、Quartz


项目 pom.xml 文件配置 Quartz 组件。在实际开发中,Quartz 提供了两种实现方式:


  • 直接继承父类实现;

  • 配置方式实现任务调度,不需要继承父类。


建议不要使用直接继承父类的方式,这样会增加过多的硬编码,也同时增加了代码量,不利于维护。

一下介绍基于 spring 配置实现 Quartz 任务调度。


2.1、继承 QuartzJobBean 实现定时任务


定义任务处理类(org.springframework.scheduling.quartz.QuartzJobBean)


package org.fuys.own.test;import java.util.Date;import org.quartz.JobExecutionContext;import org.quartz.JobExecutionException;import org.slf4j.Logger;import org.slf4j.LoggerFactory;import org.springframework.context.ApplicationContext;import org.springframework.context.support.ClassPathXmlApplicationContext;import org.springframework.scheduling.quartz.QuartzJobBean;/** * test QuartzJobBean class to execute time task * @author ys */public class QuartzJob extends QuartzJobBean{	private Logger logger = LoggerFactory.getLogger(QuartzJob.class); 	@Override	protected void executeInternal(JobExecutionContext arg0) throws JobExecutionException {		logger.info(new Date().toString());	}}
复制代码


配置任务调度类(org.springframework.scheduling.quartz.JobDetailFactoryBean),即任务的调度由工厂类实现


<bean id="jobFactory" class="org.springframework.scheduling.quartz.JobDetailFactoryBean">		<property name="jobClass" value="org.fuys.own.test.QuartzJob"/>		<property name="jobDataMap">			<map>				<entry key="timeout" value="0"/>			</map>		</property>	</bean>
复制代码


配置任务调度的触发类(org.springframework.scheduling.quartz.SimpleTriggerFactoryBean),并配置触发时间


<bean id="jobTrigger" class="org.springframework.scheduling.quartz.SimpleTriggerFactoryBean">		<property name="jobDetail" ref="jobFactory"/>		<property name="startDelay" value="0"/>		<property name="repeatInterval" value="2000"/>	</bean>
复制代码


配置日程调度类(org.springframework.scheduling.quartz.SchedulerFactoryBean),即启动任务调度的触发


<bean id="schedulerFactory" class="org.springframework.scheduling.quartz.SchedulerFactoryBean">		<property name="triggers">			<array>				<ref bean="jobTrigger"/>			</array>		</property>	</bean>
复制代码


配置好以上信息,定时程序交由 spring 容器自动处理。


2.2、使用 Cron 实现定时任务


Quartz 最大的好处在于使用 Cron 表达式进行定时任务的触发。


Cron 表达式的语法格式有两种:

  • 秒 分 时 日 月 年

  • 秒 分 时 日 月 周


具体的规则可以查看网络资料,无需记忆,具体问题时再查看即可。

对于程序而言,只需要将定时任务的触发类修改为支持 Cron 表达式的类即可,以下展示 Quartz 和 Cron 一起实现触发的比较。


<bean id="jobTrigger" class="org.springframework.scheduling.quartz.SimpleTriggerFactoryBean">		<property name="jobDetail" ref="jobFactory"/>		<property name="startDelay" value="1"/>		<property name="repeatInterval" value="7000"/>	</bean>	<bean id="cronTrigger" class="org.springframework.scheduling.quartz.CronTriggerFactoryBean">		<property name="jobDetail" ref="jobFactory"/>		<property name="cronExpression" value="0/30 * * * * ?"/>	</bean>	<bean id="schedulerFactory" class="org.springframework.scheduling.quartz.SchedulerFactoryBean">		<property name="triggers">			<array>				<ref bean="jobTrigger"/>				<ref bean="cronTrigger"/>			</array>		</property>	</bean>
复制代码


2.3、无需继承父类的定时任务实现


无需继承父类的定时任务实现,只需要变更任务调度类,将其修改为以下类:


org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean
复制代码


配置信息如下:


<bean id="methodInvokingJobFactory" class="org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean">		<property name="targetObject">			<bean class="org.fuys.own.test.TimerDemo"/>		</property>		<property name="targetMethod" value="executeTimer"/>	</bean>
复制代码


三、Spring Task


基于第三方组件 Quartz 的定时任务实现,配置过于复杂,不利于简便开发,因此,spring 提供了 task 组件,为开发者快速简便的进行定时任务实现提供了工具。


Task 组件的实现有两种方式:

  • 基于 applicationContext.xml 的配置

  • 基于 Annotation 的配置


配置文件添加 task 规则:


xmlns:task="http://www.springframework.org/schema/task"xsi:schemaLocation="http://www.springframework.org/schema/taskhttp://www.springframework.org/schema/task/spring-task.xsd"
复制代码


3.1、基于配置文件的实现


Java 类:


package org.fuys.own.test;import java.util.Date;import org.slf4j.Logger;import org.slf4j.LoggerFactory;public class SpringTaskDemo {	private Logger logger = LoggerFactory.getLogger(SpringTaskDemo.class);	public void executeTask(){		logger.info("===>" + new Date().toString() + "<===");	}}
复制代码


applicationContext.xml 文件配置:


<bean id="springTask" class="org.fuys.own.test.SpringTaskDemo"/>	<task:scheduled-tasks>		<!-- 间隔执行 -->		<task:scheduled ref="springTask" method="executeTask" fixed-delay="3000"/>		<!-- Cron表达式 -->		<task:scheduled ref="springTask" method="executeTask" cron="0/8 * * * * ?"/>	</task:scheduled-tasks>
复制代码


3.2、基于 Annotation 的实现


因为有了 Annotation 这一特点,spring 容器的支持,所以,不需要配置那么多复杂的继承结构。使用 Spring Task 的注解配置,需要在配置文件中添加 task 的注解驱动支持。


配置文件:


<task:annotation-driven/>
复制代码


Java 程序:


package org.fuys.own.test;import java.util.Date;import org.slf4j.Logger;import org.slf4j.LoggerFactory;import org.springframework.scheduling.annotation.Scheduled;import org.springframework.stereotype.Component;@Componentpublic class SpringTaskAnnotationDemo {	private Logger logger = LoggerFactory.getLogger(SpringTaskAnnotationDemo.class);	// 定时调度	@Scheduled(cron="0/6 * * * * ?")	public void executeSpringTask(){		logger.info("$$$ " + new Date().toString() + " $$$");	}	// 间隔调度	@Scheduled(fixedRate=4000)	public void executeTaskAnnotation(){		logger.info(">>> " + new Date().toString() + " <<<");	}}
复制代码


3.3、任务调度池


下面看下程序代码,分析其情况。


Java 代码:


package org.fuys.own.test;import java.util.Date;import org.slf4j.Logger;import org.slf4j.LoggerFactory;import org.springframework.scheduling.annotation.Scheduled;import org.springframework.stereotype.Component;@Componentpublic class SpringTaskAnnotationDemo {	private Logger logger = LoggerFactory.getLogger(SpringTaskAnnotationDemo.class);	// 定时调度	@Scheduled(cron="0/6 * * * * ?")	public void executeSpringTask(){		logger.info("$$$ " + new Date().toString() + " $$$");		try {			Thread.sleep(10000);		} catch (InterruptedException e) {			e.printStackTrace();		}	}	// 间隔调度	@Scheduled(fixedRate=4000)	public void executeTaskAnnotation(){		logger.info(">>> " + new Date().toString() + " <<<");	}}
复制代码


程序执行后的结果为:


INFO  SpringTaskAnnotationDemo - >>> Wed Jan 03 03:48:58 CST 2018 <<<INFO  SpringTaskAnnotationDemo - $$$ Wed Jan 03 03:49:00 CST 2018 $$$INFO  SpringTaskAnnotationDemo - >>> Wed Jan 03 03:49:10 CST 2018 <<<INFO  SpringTaskAnnotationDemo - >>> Wed Jan 03 03:49:10 CST 2018 <<<INFO  SpringTaskAnnotationDemo - >>> Wed Jan 03 03:49:10 CST 2018 <<<INFO  SpringTaskAnnotationDemo - $$$ Wed Jan 03 03:49:12 CST 2018 $$$INFO  SpringTaskAnnotationDemo - >>> Wed Jan 03 03:49:22 CST 2018 <<<INFO  SpringTaskAnnotationDemo - >>> Wed Jan 03 03:49:22 CST 2018 <<<INFO  SpringTaskAnnotationDemo - >>> Wed Jan 03 03:49:22 CST 2018 <<<INFO  SpringTaskAnnotationDemo - $$$ Wed Jan 03 03:49:24 CST 2018 $$$
复制代码


由此得知,对于 Spring task 组件而言,定时任务是顺序调度执行,当上一个程序没有执行完时,剩下的程序只能进行挂起等待。因此,这对于实际应用而言,是非常不利的,故 Spring 提出了任务调度池的解决方案,可以并行执行多个程序。


添加以下配置即可实现任务调度池。


<task:scheduler id="schedulerPool" pool-size="10"/>
复制代码


将以上 Java 程序执行之后的结果为:


INFO  SpringTaskAnnotationDemo - >>> Wed Jan 03 03:58:37 CST 2018 <<<INFO  SpringTaskAnnotationDemo - >>> Wed Jan 03 03:58:41 CST 2018 <<<INFO  SpringTaskAnnotationDemo - $$$ Wed Jan 03 03:58:42 CST 2018 $$$INFO  SpringTaskAnnotationDemo - >>> Wed Jan 03 03:58:45 CST 2018 <<<INFO  SpringTaskAnnotationDemo - >>> Wed Jan 03 03:58:49 CST 2018 <<<INFO  SpringTaskAnnotationDemo - >>> Wed Jan 03 03:58:53 CST 2018 <<<INFO  SpringTaskAnnotationDemo - $$$ Wed Jan 03 03:58:54 CST 2018 $$$
复制代码


用户头像

andy

关注

还未添加个人签名 2019-11-21 加入

还未添加个人简介

评论

发布
暂无评论
Spring之定时调度_andy_InfoQ写作社区