这篇文章主要分以下几个方面来说定时任务:
Spring
的TaskExecutor
Spring
的TaskSchedule
Spring
的Trigger
Spring
的TriggerContext
Spring
的SimpleTriggerContext
Spring
的CronTrigger
Spring
的CronSequenceGenerator
Spring
的PeriodicTrigger
Spring
的@Async
Spring
的@Scheduled
XML
配置Quartz
的相关信息
Spring Framework
使用TaskExecutor
和TaskScheduler
接口为异步执行和任务调度提供抽象,同时 Spring
还具有支持线程池或在应用程序服务器环境中委托给CommonJ
的接口的实现。最终,在公共接口背后使用这些实现抽象出了Java SE 5
,Java SE 6
和Java EE
环境之间的差异。
Spring
还具有集成类,支持使用Timer
(JDK自1.3以来的一部分)和Quartz Scheduler
(https://www.quartz-scheduler.org/)进行调度。 您可以使用FactoryBean
同时分别对Timer
或Trigger
实例进行可选引用来设置这两个调度程序。 此外,还提供了Quartz Scheduler
和Timer
的便捷类,它允许您调用现有目标对象的方法(类似于正常的MethodInvokingFactoryBean
操作)。
TaskExecutor
接口
(1)该接口位于org.springframework.core.task
包下;
(2)该接口继承自Executor
接口,Executor
接口位于java.util.concurrent
包下;
(3)它的子接口有:AsyncListenableTaskExecutor, AsyncTaskExecutor, SchedulingTaskExecutor
(4)实现该接口的类有:ConcurrentTaskExecutor, ConcurrentTaskScheduler, DefaultManagedTaskExecutor, DefaultManagedTaskScheduler, SimpleAsyncTaskExecutor, SimpleThreadPoolTaskExecutor, SyncTaskExecutor, TaskExecutorAdapter, ThreadPoolTaskExecutor, ThreadPoolTaskScheduler, WorkManagerTaskExecutor, WorkManagerTaskExecutor
(5)该接口是一个函数式接口,因此可以用作lambda
表达式或者方法引用的赋值目标。
(6)该接口中只存在一个抽象方法,且无返回值,方法名叫execute
,具体见下面的代码:
package org.springframework.core.task;
import java.util.concurrent.Executor;
//标识该接口是一个抽象接口,且是一个函数式接口
@FunctionalInterface
public abstract interface TaskExecutor
extends Executor
{
//该方法只有一个参数,是一个Runnable类型
//该方法用来执行指定的任务
//如果实现用了一种异步的执行策略或者在同步执行的情况下阻塞,此时就会立即返回。
//如果指定的任务不被接受,那么就会抛出TaskRejectedException异常。
public abstract void execute(Runnable paramRunnable);
}
(7)简单的任务执行器接口,用于抽象Runnable
的执行
(8)实现可以使用各种不同的执行策略,例如:同步,异步,使用线程池等
(9)相当于JDK 1.5
的Executor
接口; 现在在Spring 3.0
中扩展它,以便客户端可以声明对Executor
的依赖并接收任何TaskExecutor
实现。 此接口与标准Executor
接口保持独立,主要是为了向后兼容Spring 2.x
中的JDK 1.4
。
注明:这部分中的代码参考Spring-core 5.1.7.jar
。
补充:
Executors
是JDK
中线程池概念中的名字。这个executor
的名字是由于实际没有办法保证它的底层实现是一个池。一个执行器可能是一个单线程或者是同步的。Spring
的抽象讲Java SE
和Java EE Environments
之间的实现细节隐藏掉了。
Spring
中的TaskExecutor
接口与java.util.concurrent.Executor
接口是等价的。事实上,它存在的主要原因是在使用线程池时抽象出对Java 5
的需求。该接口只有一个方法execute(Runnable task)
,接受基于线程池的语义和配置执行的任务。
最初创建TaskExecutor
是为了给其他Spring
组件提供所需的线程池抽象。比如ApplicationEventMulticaster
,JMS
的AbstractMessageListenerContainer
和Quartz
集成之类的组件都使用TaskExecutor
抽象来池化线程。 但是,如果您的bean
需要线程池行为,您也可以根据自己的需要使用此抽象。
TaskExecutor
的类型:
Spring
包含大量预先创建的TaskExecutor
的实现,绝大多数的情况下,您是不需要自己去实现这些的,下面是Spring
提供的一些:
SyncTaskExecutor
:此实现不会异步执行调用。 相反,每次调用都发生在调用线程中。 它主要用于不需要多线程的情况,例如在简单的测试用例中。
SimpleAsyncTaskExecutor
:此实现不会重用任何线程。 相反,它为每次调用启动一个新线程。 但是,它确实支持并发限制,该限制会阻止任何超出限制的调用,直到释放一个插槽。
ConcurrentTaskExecutor
:此实现是java.util.concurrent.Executor
实例的适配器。 有一个替代(ThreadPoolTaskExecutor
)将Executor
配置参数设置为自己的bean
属性。 很少需要直接使用ConcurrentTaskExecutor
。 但是,如果ThreadPoolTaskExecutor
不够灵活,无法满足您的需求,ConcurrentTaskExecutor
是另一种选择。
ThreadPoolTaskExecutor
:这个实现是最常用的一种。它公开了bean
属性,用于配置java.util.concurrent.ThreadPoolExecutor
并将其包装在TaskExecutor
中。 如果您需要适应不同类型的java.util.concurrent.Executor
,我们建议您使用ConcurrentTaskExecutor
。
WorkManagerTaskExecutor
:此实现使用CommonJ WorkManager
作为其后端服务提供程序,并且是在Spring
应用程序上下文中在WebLogic
或WebSphere
上设置基于CommonJ
的线程池集成的中心便利类。
DefaultManagedTaskExecutor
::此实现在JSR-236兼容的运行时环境(例如Java EE 7+应用程序服务器)中使用JNDI-obtained
的ManagedExecutorService
,目的是替换CommonJ
中 WorkManager
。
2.TaskSchedule
接口
(1)该接口位于spring-context.jar
中的org.springframework.scheduling
包下
(2)所有实现了该接口的类有:ConcurrentTaskScheduler, DefaultManagedTaskScheduler, ThreadPoolTaskScheduler, TimerManagerTaskScheduler
(3)任务调度程序接口,它根据不同类型的触发器抽象Runnables的调度。
(4)该接口与SchedulingTaskExecutor
有所不同,因为它通常代表一种不同类型的后端,比如,一个具有不同属性和性能的线程池。如果它可以处理两种属性性能,那么实现是可以实现这两种接口的。
(5)默认的实现是ThreadPoolTaskScheduler
,包装了一个本地的ScheduledExecutorService
,并且添加扩展触发功能。
(6)此接口大致相当于Java EE 7
环境中支持的JSR-236
ManagedScheduledExecutorService
,但与Spring
的TaskExecutor
模型一致
该接口中的方法定义如下所示
@Nullable
public abstract ScheduledFuture<?> schedule(Runnable paramRunnable, Trigger paramTrigger);
<1>调度给定的Runnable,每当触发器指示下一个执行时间时调用它。
<2>当定时关闭或者返回的ScheduledFuture
被取消的时候,定时执行就会结束。
<3>Runnable
参数标识当触发开启的时候就会去执行这个任务;Trigger
表示一个实现Trigger
接口的实现,比如,一个CronTrigger
对象包裹一个cron
表达式。
<4>返回值:表示任务挂起完成的ScheduledFuture
,如果给定的Trigger
对象永远不会触发,则返回null
(即从Trigger.nextExecutionTime
(org.springframework.scheduling.TriggerContext
)返回null
)
<5>如果指定的任务不被接受,则会抛出TaskRejectedException
异常。
public ScheduledFuture<?> schedule(Runnable task, Instant startTime)
{
return schedule(task, Date.from(startTime));
}
<1>调度给定的Runnable,每当触发器指示下一个执行时间时调用它。
<2>当定时关闭或者返回的ScheduledFuture
被取消的时候,定时执行就会结束。
<3>参数Runnable
参数标识当触发开启的时候就会去执行这个任务;startTime
含义是:任务所需的执行时间(如果是在过去,则任务将立即执行,即尽快执行),其中该参数类型是一个Instant
类型
<4>返回值是一个ScheduledFuture
类型,表示任务有待完成。
<5>如果指定的任务不被接受,则会抛出TaskRejectedException
异常。
public ScheduledFuture<?> scheduleAtFixedRate(Runnable task, Instant startTime, Duration period)
{
return scheduleAtFixedRate(task, Date.from(startTime), period.toMillis());
}
<1>调度给定的Runnable,每当触发器指示下一个执行时间时调用它。
<2>当定时关闭或者返回的ScheduledFuture
被取消的时候,定时执行就会结束。
<3>参数task
参数标识当触发开启的时候就会去执行这个任务;period
表示连续执行任务之间的间隔。
<4>返回值是一个ScheduledFuture
类型,表示任务有待完成。
<5>如果指定的任务不被接受,则会抛出TaskRejectedException
异常。
public ScheduledFuture<?> scheduleAtFixedRate(Runnable task, Duration period)
{
return scheduleAtFixedRate(task, period.toMillis());
}
<1>调度给定的Runnable,每当触发器指示下一个执行时间时调用它。
<2>当定时关闭或者返回的ScheduledFuture
被取消的时候,定时执行就会结束。
<3>参数task
参数标识当触发开启的时候就会去执行这个任务;period
表示连续执行任务之间的间隔。
<4>返回值是一个ScheduledFuture
类型,表示任务有待完成。
<5>如果指定的任务不被接受,则会抛出TaskRejectedException
异常。
ScheduledFuture<?> scheduleAtFixedRate(Runnable task,long period)
<1>调度给定的Runnable,每当触发器指示下一个执行时间时调用它。
<2>当定时关闭或者返回的ScheduledFuture
被取消的时候,定时执行就会结束。
<3>参数task
参数标识当触发开启的时候就会去执行这个任务;period
表示连续执行任务之间的间隔(毫秒级别)。
<4>返回值是一个ScheduledFuture
类型,表示任务有待完成。
<5>如果指定的任务不被接受,则会抛出TaskRejectedException
异常。
default ScheduledFuture<?> scheduleWithFixedDelay(Runnable task,Instant startTime,Duration delay)
<1>调度给定的Runnable,在指定的执行时间调用它,然后在一次执行完成和下一次执行开始之间给定延迟。
<2>当定时关闭或者返回的ScheduledFuture
被取消的时候,定时执行就会结束。
<3>参数task
参数标识当触发开启的时候就会去执行这个任务;startTime
含义是:任务所需的执行时间(如果是在过去,则任务将立即执行,即尽快执行);delay
表示完成的任务和下次即将开始的任务之间的延时。
<4>返回值是一个ScheduledFuture
类型,表示任务有待完成。
<5>如果指定的任务不被接受,则会抛出TaskRejectedException
异常。
default ScheduledFuture<?> scheduleWithFixedDelay(Runnable task,Instant startTime,long delay)
<1>调度给定的Runnable,在指定的执行时间调用它,然后在一次执行完成和下一次执行开始之间给定延迟。
<2>当定时关闭或者返回的ScheduledFuture
被取消的时候,定时执行就会结束。
<3>参数task
参数标识当触发开启的时候就会去执行这个任务;startTime
含义是:任务所需的执行时间(如果是在过去,则任务将立即执行,即尽快执行);delay
表示完成的任务和下次即将开始的任务之间的延时(毫秒级别)。
<4>返回值是一个ScheduledFuture
类型,表示任务有待完成。
<5>如果指定的任务不被接受,则会抛出TaskRejectedException
异常。
default ScheduledFuture<?> scheduleWithFixedDelay(Runnable task,Duration delay)
<1>调度给定的Runnable,在指定的执行时间调用它,然后在一次执行完成和下一次执行开始之间给定延迟。
<2>当定时关闭或者返回的ScheduledFuture
被取消的时候,定时执行就会结束。
<3>参数task
参数标识当触发开启的时候就会去执行这个任务;delay
表示完成的任务和下次即将开始的任务之间的延时。
<4>返回值是一个ScheduledFuture
类型,表示任务有待完成。
<5>如果指定的任务不被接受,则会抛出TaskRejectedException
异常。
default ScheduledFuture<?> scheduleWithFixedDelay(Runnable task,long delay)
<1>调度给定的Runnable,在指定的执行时间调用它,然后在一次执行完成和下一次执行开始之间给定延迟。
<2>当定时关闭或者返回的ScheduledFuture
被取消的时候,定时执行就会结束。
<3>参数task
参数标识当触发开启的时候就会去执行这个任务;delay
表示完成的任务和下次即将开始的任务之间的延时(毫秒级别)。
<4>返回值是一个ScheduledFuture
类型,表示任务有待完成。
<5>如果指定的任务不被接受,则会抛出TaskRejectedException
异常。
补充:
与Spring
的TaskExecutor
抽象一样,TaskScheduler
安排的主要好处是应用程序的调度需求与部署环境分离。在部署到不应由应用程序本身直接创建线程的应用程序服务器环境时,此抽象级别尤其重要。对于这样的场景,Spring
提供了一个TimerManagerTaskScheduler
,它委托给WebLogic
或WebSphere
上的CommonJ
TimerManager
,以及一个委托给Java EE 7+
环境中的JSR-236
ManagedScheduledExecutorService
的更新的DefaultManagedTaskScheduler
。 两者通常都配置有JNDI
查找。
每当外部线程管理不是必需的时候,更简单的替代方案是应用程序中的本地ScheduledExecutorService
设置,可以通过Spring
的ConcurrentTaskScheduler
进行调整。 为方便起见,Spring
还提供了一个ThreadPoolTaskScheduler
,它在内部委托给ScheduledExecutorService
,以提供沿ThreadPoolTaskExecutor
的通用的bean
样式配置。 这些变体适用于宽松应用程序服务器环境中的本地嵌入式线程池设置,特别是在Tomcat
和Jetty
上。
Trigger
接口
Trigger
接口基本上受到JSR-236
的启发,从Spring 3.0
开始,它尚未正式实现。 触发器的基本思想是可以基于过去的执行结果或甚至任意条件来确定执行时间。 如果这些确定确实考虑了前面执行的结果,那么该信息在TriggerContext
中可用。
(1)该接口位于org.springframework.scheduling
包下面,位于spring-context.jar
中,该接口的源码如下:
public abstract interface Trigger
{
@Nullable
public abstract Date nextExecutionTime(TriggerContext paramTriggerContext);
}
它是一个抽象接口,里面只有一个方法抽象的返回类型是Date
类型,参数是一个TriggerContext
类型。
(2)Trigger
有两种实现,分别是CronTrigger
和PeriodicTrigger
,其中最常用的是CronTrigger
,因为它的任务执行时给予cron
表达式的;PeriodicTrigger
接受一个固定的时间间隔,一个可选择的初始延迟值和一个boolean
类型的值,来确定这个时间间隔是fixed-rate
还是fix-delay
。由于TaskScheduler接口已经定义了以fixed-rate
或fixed-delay
调度任务的方法,因此应尽可能直接使用这些方法。PeriodicTrigger
实现的值是您可以在依赖于Trigger
抽象的组件中使用它。
TriggerContext
接口,其中TriggerContext
源码如下:
public abstract interface TriggerContext
{
//上一次计划执行的时间
@Nullable
public abstract Date lastScheduledExecutionTime();
//上一次实际的执行时间
@Nullable
public abstract Date lastActualExecutionTime();
//上一次完成的时间
@Nullable
public abstract Date lastCompletionTime();
}
<1>TriggerContext
接口的实现类是SimpleTriggerContext
;
<2>这个接口的上下文对象中包含了定时任务上一次的执行时间和完成时间;
SimpleTriggerContext
接口,TriggerContext
接口的实例类SimpleTriggerContext
(位于org.springframework.scheduling.support
包下)源码如下:
//继承自Object类,实现了TriggerContext接口
public class SimpleTriggerContext
implements TriggerContext
{
//以下三个变量Date类型均采用volatile修饰,保证变量的可见性,不可以保证变量的原子性
@Nullable
private volatile Date lastScheduledExecutionTime;
@Nullable
private volatile Date lastActualExecutionTime;
@Nullable
private volatile Date lastCompletionTime;
//无参构造函数
public SimpleTriggerContext() {}
//带参数的构造函数,参数分别是:上次计划执行日期,上次实际执行日期,上次完成日期
public SimpleTriggerContext(Date lastScheduledExecutionTime, Date lastActualExecutionTime, Date lastCompletionTime)
{
this.lastScheduledExecutionTime = lastScheduledExecutionTime;
this.lastActualExecutionTime = lastActualExecutionTime;
this.lastCompletionTime = lastCompletionTime;
}
//根据最近的时间值来更新持有状态
public void update(Date lastScheduledExecutionTime, Date lastActualExecutionTime, Date lastCompletionTime)
{
this.lastScheduledExecutionTime = lastScheduledExecutionTime;
this.lastActualExecutionTime = lastActualExecutionTime;
this.lastCompletionTime = lastCompletionTime;
}
@Nullable
public Date lastScheduledExecutionTime()
{
return this.lastScheduledExecutionTime;
}
@Nullable
public Date lastActualExecutionTime()
{
return this.lastActualExecutionTime;
}
@Nullable
public Date lastCompletionTime()
{
return this.lastCompletionTime;
}
}
CronTrigger
类源码分析:
package org.springframework.scheduling.support;
import java.util.Date;
import java.util.TimeZone;
import org.springframework.scheduling.Trigger;
import org.springframework.scheduling.TriggerContext;
//实现Trigger接口
public class CronTrigger
implements Trigger
{
private final CronSequenceGenerator sequenceGenerator;
//构造函数 参数是一个字符串 cron表达式,时区默认:
public CronTrigger(String expression)
{
//将cron表达式传入给CronSequenceGnerator
this.sequenceGenerator = new CronSequenceGenerator(expression);
}
//构造函数2, 参数是cron表达式和一个时区
public CronTrigger(String expression, TimeZone timeZone)
{
this.sequenceGenerator = new CronSequenceGenerator(expression, timeZone);
}
//返回构建此trigger的cron表达式
public String getExpression()
{
return this.sequenceGenerator.getExpression();
}
//根据trigger上下文来决定下次的执行时间;下次的执行时间是依赖于前一次执行完成的时间的。因此,是不会发生重叠执行。
public Date nextExecutionTime(TriggerContext triggerContext)
{
//获取上次执行完成时间
Date date = triggerContext.lastCompletionTime();
if (date != null)
{
//上次执行完成时间不为null时,获取上次计划执行时间
Date scheduled = triggerContext.lastScheduledExecutionTime();
//上次计划执行时间不为null,并且完成时间早于计划时间
if ((scheduled != null) && (date.before(scheduled))) {
date = scheduled;
}
}
else
{
date = new Date();
}
return this.sequenceGenerator.next(date);
}
//重写了equals和hashCode及toString方法
public boolean equals(Object other)
{
return (this == other) || (((other instanceof CronTrigger)) &&
(this.sequenceGenerator.equals(((CronTrigger)other).sequenceGenerator)));
}
public int hashCode()
{
return this.sequenceGenerator.hashCode();
}
public String toString()
{
return this.sequenceGenerator.toString();
}
}
7.CronSequenceGenerator
类介绍
介绍CronTrigger
类的同时,在介绍一个类CronSequenceGenerator
,这里就先不分析这个类的源码了,直接看一下这个类的内容:
<1>位于org.springframework.scheduling.support
包下
<2>Crontab
模式的日期序列生成器,允许客户端指定序列匹配的模式。
该模式是六个单独的空格分隔字段的列表:表示秒,分钟,小时,日,月,工作日。 月份和工作日名称可以作为英文名称的前三个字母。
例子:
0 0 * * * * 每天每小时的最高点。
*/10 * * * * * 每十秒钟。
0 0 8-10 * * * 每天8点,9点和10点。
0 0 6,19 * * * 每天早上6:00和晚上7:00。
0 0/30 8-10 * * * 8:00,8:30,9:00,9:30,10:00和10:30。
0 0 9-17 * * MON-FRI 工作日的9到5小时
0 0 0 25 12? 每个圣诞节午夜
PeriodicTrigger
类
<1> 位于org.springframework.scheduling.support
包下,实现了Trigger
接口
<2>周期性任务执行的触发器.时间间隔可以按照fixed-rate
或者fixed-delay
,可以配置一个初始延迟值。默认的初始延迟值是0,默认的行为是fixed-delay
。(从每个完成时间开始测量连续执行之间的间隔。 要测量每次执行的计划开始时间之间的间隔,请将fixedRate
属性设置为true
)
<3>构造方法有两个:
//根据给定的时间间隔毫秒数创建一个trigger
public PeriodicTrigger(long period)
{
this(period, null);
}
//根据给定的时间间隔毫秒数和时间单元 创建一个trigger
public PeriodicTrigger(long period, @Nullable TimeUnit timeUnit)
{
Assert.isTrue(period >= 0L, "period must not be negative");
this.timeUnit = (timeUnit != null ? timeUnit : TimeUnit.MILLISECONDS);
this.period = this.timeUnit.toMillis(period);
}
<4>方法有:
//返回trigger的时间间隔
public long getPeriod();
//返回trigger的时间单元,默认是毫秒
public TimeUnit getTimeUnit()
//指定初始执行的延迟。 它将根据此触发器的TimeUnit进行评估。 如果在实例化时未明确提供时间单位,则默认值为毫秒
public void setInitialDelay(long initialDelay)
//返回初始延迟的值,如果没有指定,返回0
public long getInitialDelay()
//指定是否应在计划的开始时间之间而不是在实际完成时间之间测量周期性间隔。 后者“固定延迟”行为是默认行为
public void setFixedRate(boolean fixedRate)
//判断这个trigger使用的是fixed-rate就返回true,如果使用的是fixed-delay就返回false
public boolean isFixedRate()
//返回任务下一次运行的时间 参数是一个triggerContext类型
public Date nextExecutionTime(TriggerContext triggerContext)
9.对于Scheduling
和异步执行的注解支持
Spring
提供了注解来支持异步方法执行和定时任务。
<1>开启Scheduling
注解
为了开启`@Schedule`和`@Async`,你可以在你添加`@Configuration`注解的类上添加`@EnableScheduling`和`@EnableAsync`注解。例如:
```
@Configuration
@EnableAsync
@EnableScheduling
public class AppConfig {
}
```
如果使用XML
进行配置,则可以按照下面的方式进行配置:
<task:annotation-driven executor="myExecutor" scheduler="myScheduler"/>
<task:executor id="myExecutor" pool-size="5"/>
<task:scheduler id="myScheduler" pool-size="10"/>
需要注意的是,上面的XML
配置中,executor
处理的是带有@Async
注解的方法,scheduler
处理的是带有@scheduled
注解的方法。
处理@Async
注释的默认建议模式是代理,它允许仅通过代理拦截调用。 同一类中的本地调用不能以这种方式截获。 对于更高级的拦截模式,请考虑结合编译时或加载时编织切换到aspectj模式。
@Scheduled
注解:
您可以将@Scheduled
注释与触发器元数据一起添加到方法中。 例如,以固定延迟每五秒调用以下方法,这意味着该周期是从每个前一次调用的完成时间开始测量的。
@Scheduled(fixedDelay=5000)
public void doSomething() {
// do something
}
如果需要按照fixed-rate
执行,则可以更改注释中指定的属性名称。 每五秒调用以下方法(在每次调用的连续开始时间之间测量):
@Scheduled(fixedRate=5000)
public void doSomething() {
// do something
}
对于初始延迟的指定,你可以通过在@Scheduled
注解中添加initialDelay
来指定第一次初始延迟的值,对于fixed-rate
和fixed-Delay
都适用:
@Scheduled(initialDelay=1000, fixedRate=5000)
public void doSomething() {
// do something
}
可以使用cron
表达式,比如下面的例子只在工作日执行:
@Scheduled(cron="*/5 * * * * MON-FRI")
public void doSomething() {
// do something
}
您还可以使用zone
属性指定解析cron
表达式的时区。
需要注意的是,使用@Scheduled
注解的方法,返回值必须是void
类型,而且不可以有任何参数。如果该方法需要与应用程序上下文中的其他对象进行交互,则通常会通过依赖项注入来提供这些对象。
从Spring Framework 4.3
开始,任何范围的bean
都支持@Scheduled
方法。
确保您没有在运行时初始化同一个@Scheduled
注释类的多个实例,除非您确实要为每个此类实例安排回调。 与此相关,请确保不对使用@Scheduled
注释的bean
类使用@Configurable
,并将其注册为带容器的常规Spring bean
。 否则,您将获得双初始化(一次通过容器,一次通过@Configurable
方面),每个@Scheduled
方法的结果被调用两次。
@Async
注解
您可以在方法上提供@Async
注解,以便异步调用该方法。 换句话说,调用程序在调用时立即返回,而方法的实际执行发生在已提交给Spring TaskExecutor
的任务中。
@Async
void doSomething() {
// this will be executed asynchronously
}
与使用@Scheduled
注解的方法不同,这些方法可以拥有参数,因为它们在运行时由调用者以“正常”方式调用,而不是由容器管理的调度任务调用。 例如,以下代码是@Async
注解的合法应用程序:
@Async
void doSomething(String s) {
// this will be executed asynchronously
}
甚至可以异步调用返回值的方法。 但是,这些方法需要具有Future
类型的返回值。 这仍然提供了异步执行的好处,以便调用者可以在调用Future
上的get()
之前执行其他任务。 以下示例显示如何在返回值的方法上使用@Async
。
@Async
Future<String> returnSomething(int i) {
// this will be executed asynchronously
}
@Async
方法不仅可以声明常规的java.util.concurrent.Future
返回类型,还可以声明Spring
的org.springframework.util.concurrent.ListenableFuture
,或者从Spring 4.2
开始,声明JDK 8
的java.util.concurrent.CompletableFuture
,以实现更丰富的交互 使用异步任务并通过进一步的处理步骤立即组合。
您不能将@Async
与生命周期回调结合使用,例如@PostConstruct
。 要异步初始化Spring bean
,您当前必须使用单独的初始化Spring bean
,然后在目标上调用@Async
带注释的方法,如以下示例所示:
public class SampleBeanImpl implements SampleBean {
@Async
void doSomething() {
// ...
}
}
public class SampleBeanInitializer {
private final SampleBean bean;
public SampleBeanInitializer(SampleBean bean) {
this.bean = bean;
}
@PostConstruct
public void initialize() {
bean.doSomething();
}
}
这里没有@Async
的直接XML
等价物,因为这些方法应该首先设计用于异步执行,而不是外部重新声明为异步。 但是,您可以使用Spring AOP
手动设置Spring
的AsyncExecutionInterceptor
,并结合自定义切入点.
默认情况下,在方法上指定@Async
时,使用的执行程序是启用异步支持时配置的执行程序,即如果使用XML
或AsyncConfigurer
实现(如果有),则为“annotation-driven
”元素。 但是,如果需要指示在执行给定方法时应使用非默认执行程序,则可以使用@Async
批注的value
属性。 以下示例说明了如何执行此操作。
@Async("otherExecutor")
void doSomething(String s) {
// this will be executed asynchronously by "otherExecutor"
}
在这种情况下,“otherExecutor
”可以是Spring
容器中任何Executor bean
的名称,也可以是与任何Executor
关联的限定符的名称(例如,使用<qualifier>
元素或Spring
的@Qualifier
注释指定))。
使用@Async
进行异常管理
在方法上使用@Async
注解,并且会有一个Future
类型的返回值,那么在方法执行期间如果有异常抛出,是很容易管理的,因为这个被抛出的异常时在Future
的结果调用get
方法的时候抛出的。然而,当返回类型是void
的时候,异常时不可以被捕获并且不可以被发送。你可以提供一个AsyncUncaughtExceptionHandler
来处理这样的异常。下面的例子就展示了如何来做:
public class MyAsyncUncaughtExceptionHandler implements AsyncUncaughtExceptionHandler {
@Override
public void handleUncaughtException(Throwable ex, Method method, Object... params) {
// handle exception
}
}
默认情况下,仅记录异常。 您可以使用AsyncConfigurer
或<task:annotation-driven />
XML
元素定义自定义AsyncUncaughtExceptionHandler
。
task
命名空间
从3.0版开始,Spring
包含一个用于配置TaskExecutor
和TaskScheduler
实例的XML
命名空间。 它还提供了一种方便的方法来配置要使用触发器安排的任务。
scheduler
元素
下面的元素创建了一个ThreadPoolTaskScheduler
的实例并且使用具体的线程池大小。
<task:scheduler id="scheduler" pool-size="10"/>
为id
属性提供的值将用作池中线程名称的前缀。 scheduler
元素相对简单。 如果未提供pool-size
属性,则默认线程池只有一个线程。scheduler
没有其他配置选项。
executor
元素
下面是创建一个ThreadPoolTaskExecutor
的实例:
<task:executor id="executor" pool-size="10" />
与上一节中显示的scheduler
一样,为id
属性提供的值将用作池中线程名称的前缀。 就池大小而言,executor
元素支持比scheduler
元素更多的配置选项。 首先,ThreadPoolTaskExecutor
的线程池本身更易于配置。 执行程序的线程池可以具有不同的核心值和最大大小,而不仅仅是单个大小。 如果提供单个值,则执行程序具有固定大小的线程池(核心和最大大小相同)。 但是,executor
元素的pool-size
属性也接受min-max
形式的范围。 以下示例将最小值设置为5,将最大值设置为25:
<task:executor
id="executorWithPoolSizeRange"
pool-size="5-25"
queue-capacity="100"/>
在前面的配置中,还提供了队列容量值queue-capacity
。 还应根据执行程序的队列容量来考虑线程池的配置。 有关池大小和队列容量之间关系的完整描述,请参阅ThreadPoolExecutor
的文档。 主要思想是,当提交任务时,如果当前活动线程的数量小于核心县城数量,则executor
首先尝试使用空闲线程。 如果活动线程数量已达到核心线程数量大小,则只要尚未达到其容量,任务就会添加到队列中。 只有这样,如果已达到队列的容量,执行程序是否会创建超出核心大小的新线程。 如果还达到了最大大小,则执行程序拒绝该任务。
默认情况下,队列是无限制的,但很少这样在项目中配置,因为如果在所有池线程均处于忙碌的情况下将足够的任务添加到该队列,则可能导致OutOfMemoryErrors
。 此外,如果队列是无界的,则最大大小根本没有影响。 由于执行程序总是在创建超出核心大小的新线程之前尝试队列,因此队列必须具有有限的容量,以使线程池增长超出核心大小(这就是为什么固定大小的池是使用时唯一合理的情况 一个无限的队列)。
考虑一下上面提到的那种情况,当一个任务被拒绝。默认,当一个任务被拒绝,一个线程池的excutor
会抛出TaskRejectedException
异常。然而,实际中拒绝策略是可以进行配置的。当使用默认拒绝策略的时候异常被抛出,这种策略是实现了AbortPolicy
。对于可以在高负载下跳过某些任务的应用程序,您可以改为配置DiscardPolicy
或DiscardOldestPolicy
。 另一个适用于需要在高负载下限制提交任务的应用程序的选项是CallerRunsPolicy
。 该策略不是抛出异常或丢弃任务,而是强制调用submit
方法的线程自己运行任务。 这个想法是这样的,调用者在运行该任务时很忙,并且不能立即提交其他任务。 因此,它提供了一种简单的方法来限制传入的负载,同时保持线程池和队列的限制。 通常,这允许执行程序“赶上”它正在处理的任务,从而释放队列,池中或两者中的一些容量。 您可以从executor
元素上的rejection-policy
属性的可用值枚举中选择任何这些选项。
<task:executor
id="executorWithCallerRunsPolicy"
pool-size="5-25"
queue-capacity="100"
rejection-policy="CALLER_RUNS"/>
最后,keep-alive
设置确定线程在终止之前可以保持空闲的时间限制(以秒为单位)。 如果池中当前有多个线程核心数,则在等待这段时间而不处理任务后,多余的线程将被终止。 时间值为零会导致多余线程在执行任务后立即终止,而不会在任务队列中保留后续工作。 以下示例将keep-alive值设置为两分钟
<task:executor
id="executorWithKeepAlive"
pool-size="5-25"
keep-alive="120"/>
scheduled-tasks
元素:
Spring
的任务命名空间最强大的功能是支持在Spring Application Context
中配置要安排的任务。 这遵循类似于Spring
中的其他“方法调用者”的方法,例如由JMS
名称空间提供的用于配置消息驱动的POJO
的方法。 基本上,ref
属性可以指向任何Spring
管理的对象,method
属性提供要在该对象上调用的方法的名称。 以下清单显示了一个简单的示例:
<task:scheduled-tasks scheduler="myScheduler">
<task:scheduled ref="beanA" method="methodA" fixed-delay="5000"/>
</task:scheduled-tasks>
<task:scheduler id="myScheduler" pool-size="10"/>
scheduler
由外部元素引用,每个单独的任务包括其触发器元数据的配置。 在前面的示例中,该元数据定义了具有fixed-delay
的周期性触发,该延迟指示在每个任务执行完成之后等待的毫秒数。 另一种选择是fixed-rate
,表示无论先前执行多长时间,该方法应执行的频率。 此外,对于fixed-rate
和fixed-delay
任务,您可以指定“initial-delay
”参数,指示在首次执行方法之前等待的毫秒数。 为了获得更多控制,您可以改为提供cron
属性。 以下示例显示了这些其他选项:
<task:scheduled-tasks scheduler="myScheduler">
<task:scheduled ref="beanA" method="methodA" fixed-delay="5000" initial-delay="1000"/>
<task:scheduled ref="beanB" method="methodB" fixed-rate="5000"/>
<task:scheduled ref="beanC" method="methodC" cron="*/5 * * * * MON-FRI"/>
</task:scheduled-tasks>
<task:scheduler id="myScheduler" pool-size="10"/>
Quartz
做任务调度
Quartz
使用Trigger
,Job
和JobDetail
对象来实现各种作业的调度。 有关Quartz
背后的基本概念,请参阅https://www.quartz-scheduler.org/。 为方便起见,Spring
提供了几个简化在基于Spring
的应用程序中使用Quartz
的类。
使用JobDetailFactoryBean
:
Quartz
的JobDetail
对象包含了需要运行一个任务的所有信息。Spring
提供了一个JobDetailFactoryBean
,它提供了一个XML
形式来配置Bean
。考虑下面的例子:
<bean name="exampleJob" class="org.springframework.scheduling.quartz.JobDetailFactoryBean">
<property name="jobClass" value="example.ExampleJob"/>
<property name="jobDataAsMap">
<map>
<entry key="timeout" value="5"/>
</map>
</property>
</bean>
作业详细信息配置具有运行作业所需的所有信息(ExampleJob)。 超时在作业数据映射中指定。 作业数据映射可通过JobExecutionContext
(在执行时传递给您)获得,但JobDetail
也从映射到作业实例属性的作业数据中获取其属性。 因此,在以下示例中,ExampleJob
包含名为timeout
的bean
属性,JobDetail
会自动应用它:
package example;
public class ExampleJob extends QuartzJobBean {
private int timeout;
/**
* Setter called after the ExampleJob is instantiated
* with the value from the JobDetailFactoryBean (5)
*/
public void setTimeout(int timeout) {
this.timeout = timeout;
}
protected void executeInternal(JobExecutionContext ctx) throws JobExecutionException {
// do the actual work
}
}
您也可以使用作业数据图中的所有其他属性。
通过使用name
和group
属性,您可以分别修改作业的名称和组。 默认情况下,作业的名称与JobDetailFactoryBean
的bean
名称匹配(上例中的exampleJob
)
使用MethodInvokingJobDetailFactoryBean
通常,您只需要在特定对象上调用方法。 通过使用MethodInvokingJobDetailFactoryBean
,您可以完成此操作,如以下示例所示:
<bean id="jobDetail" class="org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean">
<property name="targetObject" ref="exampleBusinessObject"/>
<property name="targetMethod" value="doIt"/>
</bean>
前面的示例导致在exampleBusinessObject
方法上调用doIt
方法,如以下示例所示:
public class ExampleBusinessObject {
// properties and collaborators
public void doIt() {
// do the actual work
}
}
<bean id="exampleBusinessObject" class="examples.ExampleBusinessObject"/>
通过使用MethodInvokingJobDetailFactoryBean
,您无需创建仅调用方法的单行作业。 您只需创建实际的业务对象并连接详细信息对象。
默认情况下,Quartz Jobs
是无状态的,导致作业相互干扰的可能性。 如果为同一JobDetail
指定两个触发器,则可能在第一个作业完成之前,第二个作业开始。 如果JobDetail
类实现Stateful
接口,则不会发生这种情况。 第二个作业在第一个作业完成之前没有开始。 要使MethodInvokingJobDetailFactoryBean
生成的作业不是并发的,请将concurrent
标志设置为false
,如以下示例所示:
<bean id="jobDetail" class="org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean">
<property name="targetObject" ref="exampleBusinessObject"/>
<property name="targetMethod" value="doIt"/>
<property name="concurrent" value="false"/>
</bean
默认情况下,作业将以并发方式运行。
使用Trigger
和SchedulerFactoryBean
连接作业:
我们已经创建了作业细节和工作。 我们还回顾了简单的实体,它允许您调用特定对象的方法。 当然,我们仍然需要自己安排作业。 这是通过使用trigger
和SchedulerFactoryBean
完成的。 Quartz
中提供了几个触发器,Spring
提供了两个带有方便默认值的Quartz FactoryBean
实现:CronTriggerFactoryBean
和SimpleTriggerFactoryBean
。
需要安排触发器。 Spring
提供了一个SchedulerFactoryBean
,它公开了要设置为属性的触发器。 SchedulerFactoryBean
使用这些触发器调度实际作业。
以下清单使用SimpleTriggerFactoryBean
和CronTriggerFactoryBean
:
<bean id="simpleTrigger" class="org.springframework.scheduling.quartz.SimpleTriggerFactoryBean">
<property name="jobDetail" ref="jobDetail"/>
<!-- 10 seconds -->
<property name="startDelay" value="10000"/>
<!-- repeat every 50 seconds -->
<property name="repeatInterval" value="50000"/>
</bean>
<bean id="cronTrigger" class="org.springframework.scheduling.quartz.CronTriggerFactoryBean">
<property name="jobDetail" ref="exampleJob"/>
<!-- run every morning at 6 AM -->
<property name="cronExpression" value="0 0 6 * * ?"/>
</bean>
上面的示例设置了两个触发器,一个每50秒运行一次,启动延迟为10秒,每天早上6点运行一个触发器。 要完成所有操作,我们需要设置SchedulerFactoryBean
,如以下示例所示:
<bean class="org.springframework.scheduling.quartz.SchedulerFactoryBean">
<property name="triggers">
<list>
<ref bean="cronTrigger"/>
<ref bean="simpleTrigger"/>
</list>
</property>
</bean>
SchedulerFactoryBean
可以使用更多属性,例如作业详细信息使用的日历,自定义Quartz
的属性等。 有关更多信息,请参阅SchedulerFactoryBean javadoc