ARTS Review8 Spring 定时任务

这篇文章主要分以下几个方面来说定时任务:

  1. SpringTaskExecutor
  2. SpringTaskSchedule
  3. SpringTrigger
  4. SpringTriggerContext
  5. SpringSimpleTriggerContext
  6. SpringCronTrigger
  7. SpringCronSequenceGenerator
  8. SpringPeriodicTrigger
  9. Spring@Async
  10. Spring@Scheduled
  11. XML配置
  12. Quartz的相关信息

Spring Framework使用TaskExecutorTaskScheduler接口为异步执行和任务调度提供抽象,同时 Spring还具有支持线程池或在应用程序服务器环境中委托给CommonJ的接口的实现。最终,在公共接口背后使用这些实现抽象出了Java SE 5Java SE 6Java EE环境之间的差异。

Spring还具有集成类,支持使用Timer(JDK自1.3以来的一部分)和Quartz Scheduler(https://www.quartz-scheduler.org/)进行调度。 您可以使用FactoryBean同时分别对TimerTrigger实例进行可选引用来设置这两个调度程序。 此外,还提供了Quartz SchedulerTimer的便捷类,它允许您调用现有目标对象的方法(类似于正常的MethodInvokingFactoryBean操作)。

  1. 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.5Executor接口; 现在在Spring 3.0中扩展它,以便客户端可以声明对Executor的依赖并接收任何TaskExecutor实现。 此接口与标准Executor接口保持独立,主要是为了向后兼容Spring 2.x中的JDK 1.4

注明:这部分中的代码参考Spring-core 5.1.7.jar

补充:

ExecutorsJDK中线程池概念中的名字。这个executor的名字是由于实际没有办法保证它的底层实现是一个池。一个执行器可能是一个单线程或者是同步的。Spring的抽象讲Java SEJava EE Environments之间的实现细节隐藏掉了。

Spring中的TaskExecutor接口与java.util.concurrent.Executor接口是等价的。事实上,它存在的主要原因是在使用线程池时抽象出对Java 5的需求。该接口只有一个方法execute(Runnable task),接受基于线程池的语义和配置执行的任务。

最初创建TaskExecutor是为了给其他Spring组件提供所需的线程池抽象。比如ApplicationEventMulticasterJMSAbstractMessageListenerContainerQuartz集成之类的组件都使用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应用程序上下文中在WebLogicWebSphere上设置基于CommonJ的线程池集成的中心便利类。

DefaultManagedTaskExecutor::此实现在JSR-236兼容的运行时环境(例如Java EE 7+应用程序服务器)中使用JNDI-obtainedManagedExecutorService,目的是替换CommonJWorkManager

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,但与SpringTaskExecutor模型一致

该接口中的方法定义如下所示

@Nullable
public abstract ScheduledFuture<?> schedule(Runnable paramRunnable, Trigger paramTrigger);

<1>调度给定的Runnable,每当触发器指示下一个执行时间时调用它。
<2>当定时关闭或者返回的ScheduledFuture被取消的时候,定时执行就会结束。
<3>Runnable参数标识当触发开启的时候就会去执行这个任务;Trigger表示一个实现Trigger接口的实现,比如,一个CronTrigger对象包裹一个cron表达式。
<4>返回值:表示任务挂起完成的ScheduledFuture,如果给定的Trigger对象永远不会触发,则返回null(即从Trigger.nextExecutionTimeorg.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异常。

补充:

SpringTaskExecutor抽象一样,TaskScheduler安排的主要好处是应用程序的调度需求与部署环境分离。在部署到不应由应用程序本身直接创建线程的应用程序服务器环境时,此抽象级别尤其重要。对于这样的场景,Spring提供了一个TimerManagerTaskScheduler,它委托给WebLogicWebSphere上的CommonJ TimerManager,以及一个委托给Java EE 7+环境中的JSR-236 ManagedScheduledExecutorService的更新的DefaultManagedTaskScheduler。 两者通常都配置有JNDI查找。

每当外部线程管理不是必需的时候,更简单的替代方案是应用程序中的本地ScheduledExecutorService设置,可以通过SpringConcurrentTaskScheduler进行调整。 为方便起见,Spring还提供了一个ThreadPoolTaskScheduler,它在内部委托给ScheduledExecutorService,以提供沿ThreadPoolTaskExecutor的通用的bean样式配置。 这些变体适用于宽松应用程序服务器环境中的本地嵌入式线程池设置,特别是在TomcatJetty上。

  1. 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有两种实现,分别是CronTriggerPeriodicTrigger,其中最常用的是CronTrigger,因为它的任务执行时给予cron表达式的;PeriodicTrigger接受一个固定的时间间隔,一个可选择的初始延迟值和一个boolean类型的值,来确定这个时间间隔是fixed-rate还是fix-delay。由于TaskScheduler接口已经定义了以fixed-ratefixed-delay调度任务的方法,因此应尽可能直接使用这些方法。PeriodicTrigger实现的值是您可以在依赖于Trigger抽象的组件中使用它。

  1. 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>这个接口的上下文对象中包含了定时任务上一次的执行时间和完成时间;

  1. 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;
  }
}

  1. 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? 每个圣诞节午夜
  1. 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-ratefixed-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返回类型,还可以声明Springorg.springframework.util.concurrent.ListenableFuture,或者从Spring 4.2开始,声明JDK 8java.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手动设置SpringAsyncExecutionInterceptor,并结合自定义切入点.

默认情况下,在方法上指定@Async时,使用的执行程序是启用异步支持时配置的执行程序,即如果使用XMLAsyncConfigurer实现(如果有),则为“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包含一个用于配置TaskExecutorTaskScheduler实例的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。对于可以在高负载下跳过某些任务的应用程序,您可以改为配置DiscardPolicyDiscardOldestPolicy。 另一个适用于需要在高负载下限制提交任务的应用程序的选项是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-ratefixed-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使用TriggerJobJobDetail对象来实现各种作业的调度。 有关Quartz背后的基本概念,请参阅https://www.quartz-scheduler.org/。 为方便起见,Spring提供了几个简化在基于Spring的应用程序中使用Quartz的类。

使用JobDetailFactoryBean

QuartzJobDetail对象包含了需要运行一个任务的所有信息。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包含名为timeoutbean属性,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
    }

}

您也可以使用作业数据图中的所有其他属性。

通过使用namegroup属性,您可以分别修改作业的名称和组。 默认情况下,作业的名称与JobDetailFactoryBeanbean名称匹配(上例中的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

默认情况下,作业将以并发方式运行。

使用TriggerSchedulerFactoryBean连接作业:

我们已经创建了作业细节和工作。 我们还回顾了简单的实体,它允许您调用特定对象的方法。 当然,我们仍然需要自己安排作业。 这是通过使用triggerSchedulerFactoryBean完成的。 Quartz中提供了几个触发器,Spring提供了两个带有方便默认值的Quartz FactoryBean实现:CronTriggerFactoryBeanSimpleTriggerFactoryBean

需要安排触发器。 Spring提供了一个SchedulerFactoryBean,它公开了要设置为属性的触发器。 SchedulerFactoryBean使用这些触发器调度实际作业。

以下清单使用SimpleTriggerFactoryBeanCronTriggerFactoryBean:

<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

在这里插入图片描述

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值