34. 任务执行和计划

34.1 Introduction

Spring 框架分别提供了TaskExecutorTaskScheduler接口的异步执行和任务调度的抽象。 Spring 还提供了那些接口的实现,这些接口在应用程序服务器环境中支持线程池或委托给 CommonJ。最终,在公共接口后面使用这些实现可以抽象化 Java SE 5,Java SE 6 和 Java EE 环境之间的差异。

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

34.2 Spring TaskExecutor 抽象

执行程序是线程池概念的 JDK 名称。 “执行程序”的命名是由于不能保证基础实现实际上是一个池;执行程序可能是单线程的,甚至是同步的。 Spring 的抽象隐藏了 Java SE 和 Java EE 环境之间的实现细节。

Spring 的TaskExecutor界面与java.util.concurrent.Executor界面相同。实际上,最初,其存在的主要原因是在使用线程池时抽象出对 Java 5 的需求。该接口具有单个方法execute(Runnable task),该方法根据线程池的语义和配置接受要执行的任务。

TaskExecutor最初是为了在需要时为其他 Spring 组件提供线程池抽象而创建的。 ApplicationEventMulticaster,JMS 的AbstractMessageListenerContainer和 Quartz 集成之类的组件都使用TaskExecutor抽象来池化线程。但是,如果您的 bean 需要线程池行为,则可以根据自己的需要使用此抽象。

34.2.1 TaskExecutor 类型

Spring 发行版中包含许多TaskExecutor的预构建实现。您极有可能无需实现自己的方法。常见的即用型变体是:

  • SyncTaskExecutor此实现不会异步执行调用。而是,每个调用都在调用线程中进行。它主要用于不需要多线程的情况下,例如在简单的测试案例中。

  • SimpleAsyncTaskExecutor此实现不会重用任何线程,而是会为每次调用启动一个新线程。但是,它确实支持并发限制,该限制将阻止超出限制的所有调用,直到释放插槽为止。如果您正在寻找 true 的池,请参见下面的ThreadPoolTaskExecutor

  • ConcurrentTaskExecutor此实现是java.util.concurrent.Executor实例的适配器。还有一个替代方案ThreadPoolTaskExecutor,将Executor配置参数公开为 bean 属性。很少需要直接使用ConcurrentTaskExecutor,但是如果ThreadPoolTaskExecutor不够灵活不能满足您的需求,则可以选择ConcurrentTaskExecutor

  • ThreadPoolTaskExecutor此实现是最常用的实现。它公开了用于配置java.util.concurrent.ThreadPoolExecutor的 bean 属性,并将其包装在TaskExecutor中。如果需要适应其他类型的java.util.concurrent.Executor,建议改用ConcurrentTaskExecutor

  • WorkManagerTaskExecutor此实现使用 CommonJ WorkManager作为其支持服务提供者,并且是在 Spring 应用程序上下文中的 WebLogic/WebSphere 上设置基于 CommonJ 的线程池集成的中心便利类。

  • DefaultManagedTaskExecutor此实现在兼容 JSR-236 的运行时环境(例如 Java EE 7 应用程序服务器)中使用 JNDI 获得的ManagedExecutorService,为此目的替换了 CommonJ WorkManager。

34.2.2 使用 TaskExecutor

Spring 的TaskExecutor实现用作简单的 JavaBean。在下面的示例中,我们定义了一个使用ThreadPoolTaskExecutor异步打印出一组消息的 bean。

import org.springframework.core.task.TaskExecutor;

public class TaskExecutorExample {

    private class MessagePrinterTask implements Runnable {

        private String message;

        public MessagePrinterTask(String message) {
            this.message = message;
        }

        public void run() {
            System.out.println(message);
        }
    }

    private TaskExecutor taskExecutor;

    public TaskExecutorExample(TaskExecutor taskExecutor) {
        this.taskExecutor = taskExecutor;
    }

    public void printMessages() {
        for(int i = 0; i < 25; i++) {
            taskExecutor.execute(new MessagePrinterTask("Message" + i));
        }
    }
}

如您所见,您无需将Runnable添加到队列中,而TaskExecutor使用其内部规则来确定何时执行任务,而不是从池中检索线程并执行自己。

要配置TaskExecutor将使用的规则,已经公开了简单的 bean 属性。

<bean id="taskExecutor" class="org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor">
    <property name="corePoolSize" value="5"/>
    <property name="maxPoolSize" value="10"/>
    <property name="queueCapacity" value="25"/>
</bean>

<bean id="taskExecutorExample" class="TaskExecutorExample">
    <constructor-arg ref="taskExecutor"/>
</bean>

34.3 Spring TaskScheduler 抽象

除了TaskExecutor抽象之外,Spring 3.0 还引入了TaskScheduler,它具有多种用于计划任务在将来某个 Moment 运行的方法。

public interface TaskScheduler {

    ScheduledFuture schedule(Runnable task, Trigger trigger);

    ScheduledFuture schedule(Runnable task, Date startTime);

    ScheduledFuture scheduleAtFixedRate(Runnable task, Date startTime, long period);

    ScheduledFuture scheduleAtFixedRate(Runnable task, long period);

    ScheduledFuture scheduleWithFixedDelay(Runnable task, Date startTime, long delay);

    ScheduledFuture scheduleWithFixedDelay(Runnable task, long delay);
}

最简单的方法是一个名为“日程表”的方法,它仅接受RunnableDate。这将导致任务在指定时间后运行一次。所有其他方法都可以安排任务重复运行。固定速率和固定延迟方法用于简单的定期执行,但是接受触发器的方法则更加灵活。

34.3.1 触发界面

Trigger接口实质上受 JSR-236 的启发,该 JSR-236 在 Spring 3.0 之前尚未正式实现。 Trigger的基本思想是可以根据过去的执行结果甚至任意条件来确定执行时间。如果这些确定确实考虑了先前执行的结果,则该信息在TriggerContext内可用。 Trigger接口本身非常简单:

public interface Trigger {

    Date nextExecutionTime(TriggerContext triggerContext);
}

如您所见,TriggerContext是最重要的部分。它封装了所有相关数据,如有必要,将来可以扩展。 TriggerContext是一个接口(默认情况下使用SimpleTriggerContext实现)。在这里,您可以看到Trigger实现可用的方法。

public interface TriggerContext {

    Date lastScheduledExecutionTime();

    Date lastActualExecutionTime();

    Date lastCompletionTime();
}

34.3.2 触发实施

Spring 提供了Trigger接口的两种实现。最有趣的是CronTrigger。它启用了基于 cron 表达式的任务调度。例如,以下任务计划在每小时的 15 分钟后运行,但仅在工作日的 9 到 5 个“工作时间”内运行。

scheduler.schedule(task, new CronTrigger("0 15 9-17 * * MON-FRI"));

另一种现成的实现是一个PeriodicTrigger,它接受一个固定的周期,一个可选的初始延迟值和一个布尔值,以指示该周期是应解释为固定速率还是固定延迟。由于TaskScheduler接口已经定义了用于以固定速率或固定延迟计划任务的方法,因此应尽可能直接使用这些方法。 PeriodicTrigger实现的价值在于它可以在依赖Trigger抽象的组件中使用。例如,允许周期性触发器,基于 cron 的触发器,甚至自定义触发器实现可互换使用可能很方便。这样的组件可以利用依赖注入的优势,以便可以在外部配置Triggers,因此可以轻松地对其进行修改或扩展。

34.3.3 TaskScheduler 的实现

像 Spring 的TaskExecutor抽象一样,TaskScheduler安排的主要好处是应用程序的调度需求与部署环境分离。当部署到不应由应用程序本身直接创建线程的应用程序服务器环境时,此抽象级别特别重要。对于此类情况,Spring 在 Java EE 7 环境中提供了TimerManagerTaskScheduler委派给 WebLogic/WebSphere 上的 CommonJ TimerManager 以及最近的DefaultManagedTaskScheduler委派给 JSR-236 ManagedScheduledExecutorService,通常都配置了 JNDI 查找。

每当不需要外部线程 Management 时,一个更简单的选择就是在应用程序中进行本地ScheduledExecutorService设置,可以通过 Spring 的ConcurrentTaskScheduler进行调整。为了方便起见,Spring 还提供了一个ThreadPoolTaskScheduler,该ThreadPoolTaskScheduler在内部委托给ScheduledExecutorService,从而沿ThreadPoolTaskExecutor的方式提供了通用的 bean 样式配置。这些变体也适用于宽松的应用程序服务器环境中的本地嵌入式线程池设置,尤其是在 Tomcat 和 Jetty 上。

34.4 计划和异步执行的 Comments 支持

Spring 为任务调度和异步方法执行提供 Comments 支持。

34.4.1 启用计划 Comments

要启用对@Scheduled@AsyncComments 的支持,请将@EnableScheduling@EnableAsync添加到您的@Configuration类中:

@Configuration
@EnableAsync
@EnableScheduling
public class AppConfig {
}

您可以自由选择适合您的应用程序的相关 Comments。例如,如果您只需要@Scheduled的支持,则只需省略@EnableAsync即可。为了获得更细粒度的控制,您可以另外实现SchedulingConfigurer和/或AsyncConfigurer接口。有关完整的详细信息,请参见 javadocs。

如果您更喜欢 XML 配置,请使用<task:annotation-driven>元素。

<task:annotation-driven executor="myExecutor" scheduler="myScheduler"/>
<task:executor id="myExecutor" pool-size="5"/>
<task:scheduler id="myScheduler" pool-size="10"/>

请注意,使用上述 XML 时,提供了执行程序引用来处理与带有@Async注解的方法相对应的那些任务,并且提供了调度程序引用来 Management 带有@ScheduledComments 的那些方法。

Note

处理@Async注解的默认建议模式是“代理”,它仅允许通过代理进行呼叫拦截。同一类中的本地调用无法以这种方式被拦截。对于更高级的侦听模式,请考虑结合编译时或加载时编织切换到“ aspectj”模式。

34.4.2 @Scheduled 注解

@ScheduledComments 可以与触发器元数据一起添加到方法中。例如,以下方法将每隔 5 秒钟以固定的延迟被调用一次,这意味着将从每个先前调用的完成时间开始计算该时间段。

@Scheduled(fixedDelay=5000)
public void doSomething() {
    // something that should execute periodically
}

如果需要固定汇率执行,只需更改 Comments 中指定的属性名称。在每次调用的连续开始时间之间,每 5 秒执行一次以下操作。

@Scheduled(fixedRate=5000)
public void doSomething() {
    // something that should execute periodically
}

对于固定延迟和固定速率的任务,可以指定初始延迟,以指示在第一次执行该方法之前要 await 的毫秒数。

@Scheduled(initialDelay=1000, fixedRate=5000)
public void doSomething() {
    // something that should execute periodically
}

如果简单的周期性调度不足以表现出来,则可以提供 cron 表达式。例如,以下仅在工作日执行。

@Scheduled(cron="*/5 * * * * MON-FRI")
public void doSomething() {
    // something that should execute on weekdays only
}

Tip

您还可以使用zone属性指定将解析 cron 表达式的时区。

请注意,要调度的方法必须具有空返回值,并且不能期望任何参数。如果该方法需要与应用程序上下文中的其他对象进行交互,则通常将通过依赖项注入来提供这些对象。

Note

从 Spring Framework 4.3 开始,任何范围的 bean 都支持@Scheduled方法。

确保不要在运行时初始化同一@ScheduledComments 类的多个实例,除非您确实希望为每个此类实例计划回调。与此相关,请确保不要在用@ScheduledComments 并已在容器中注册为常规 Spring Bean 的 Bean 类上使用@Configurable:否则,您将获得双重初始化,一次通过容器,一次通过@Configurable方面,通过每个@Scheduled方法被调用两次的结果。

34.4.3 @Async 注解

可以在方法上提供@Async注解,以便该方法的调用将异步发生。换句话说,调用者将在调用后立即返回,并且该方法的实际执行将发生在已提交给 Spring TaskExecutor的任务中。在最简单的情况下,可以将 Comments 应用于_3 返回方法。

@Async
void doSomething() {
    // this will be executed asynchronously
}

与用@ScheduledCommentsComments 的方法不同,这些方法可以使用参数,因为它们将在运行时由调用者以“常规”方式调用,而不是从容器 Management 的计划任务中调用。例如,以下是@AsyncComments 的合法应用。

@Async
void doSomething(String s) {
    // this will be executed asynchronously
}

即使返回值的方法也可以异步调用。但是,此类方法必须具有Future类型的返回值。这仍然提供了异步执行的好处,因此调用方可以在该 Future 上调用get()之前执行其他任务。

@Async
Future<String> returnSomething(int i) {
    // this will be executed asynchronously
}

Tip

@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带 Comments 的方法。

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();
    }

}

Note

@Async没有直接的 XML 等效项,因为此类方法应首先设计用于异步执行,而不是在外部重新声明为异步。但是,您可以结合使用自定义切入点,通过 Spring AOP 手动设置 Spring 的AsyncExecutionInterceptor

34.4.4 具有@Async 的执行人资格

默认情况下,在方法上指定@Async时,将使用的执行程序是如上所述提供给“Comments 驱动”元素的执行程序。但是,当需要指示在执行给定方法时应使用默认值以外的 Actuator 时,可以使用@Async注解的value属性。

@Async("otherExecutor")
void doSomething(String s) {
    // this will be executed asynchronously by "otherExecutor"
}

在这种情况下,“ otherExecutor”可以是 Spring 容器中任何Executor bean 的名称,也可以是与任何Executor相关联的* qualifier *的名称,例如由<qualifier>元素或 Spring 的@QualifierComments 指定。

34.4.5 使用@Async 进行异常 Management

@Async方法具有Future类型的返回值时,很容易 Management 在方法执行期间引发的异常,因为在Future结果上调用get时将引发此异常。但是,对于 void 返回类型,该异常不会被捕获,并且无法传输。对于这些情况,可以提供AsyncUncaughtExceptionHandler来处理此类异常。

public class MyAsyncUncaughtExceptionHandler implements AsyncUncaughtExceptionHandler {

    @Override
    public void handleUncaughtException(Throwable ex, Method method, Object... params) {
        // handle exception
    }
}

默认情况下,仅记录异常。可以通过AsyncConfigurertask:annotation-driven XML 元素定义自定义AsyncUncaughtExceptionHandler

34.5 任务名称空间

从 Spring 3.0 开始,存在用于配置TaskExecutorTaskScheduler实例的 XML 名称空间。它还提供了一种方便的方法来配置要通过触发器安排的任务。

34.5.1'scheduler'元素

以下元素将创建具有指定线程池大小的ThreadPoolTaskScheduler实例。

<task:scheduler id="scheduler" pool-size="10"/>

为'id'属性提供的值将用作池中线程名称的前缀。 “调度程序”元素相对简单。如果不提供“ pool-size”属性,则默认线程池将只有一个线程。调度程序没有其他配置选项。

34.5.2'executor'元素

以下将创建一个ThreadPoolTaskExecutor实例:

<task:executor id="executor" pool-size="10"/>

与上面的调度程序一样,为'id'属性提供的值将用作池中线程名称的前缀。就池的大小而言,“ executor”元素比“ scheduler”元素支持更多的配置选项。一方面,ThreadPoolTaskExecutor的线程池本身更具可配置性。执行者的线程池可能不仅具有单个大小,而且具有* core max *大小的不同值。如果提供单个值,则执行程序将具有固定大小的线程池(核心大小和最大大小相同)。但是,“执行程序”元素的“池大小”属性也接受“最小-最大”形式的范围。

<task:executor
        id="executorWithPoolSizeRange"
        pool-size="5-25"
        queue-capacity="100"/>

从该配置中可以看到,还提供了“队列容量”值。还应根据执行者的队列容量来考虑线程池的配置。有关池大小和队列容量之间关系的完整描述,请参阅ThreadPoolExecutor的文档。主要思想是,在提交任务时,如果活动线程的数量当前小于核心大小,则执行程序将首先尝试使用空闲线程。如果已达到核心大小,则只要尚未达到其容量,任务就会被添加到队列中。只有这样,如果达到了队列的容量*,执行程序才会创建超出核心大小的新线程。如果还达到了最大大小,那么执行者将拒绝任务。

默认情况下,队列是* unbounded *,但这很少是所需的配置,因为如果在所有池线程都忙时将足够的任务添加到该队列中,它将导致OutOfMemoryErrors。此外,如果队列是无界的,则最大大小完全没有影响。由于执行程序将始终在创建超出核心大小的新线程之前尝试队列,因此队列必须具有有限的容量,线程池才能超过核心大小(这就是为什么“固定大小”池是唯一明智的情况使用无限队列时)。

稍后,我们将回顾保持活动设置的影响,该设置增加了在提供池大小配置时要考虑的另一个因素。首先,让我们考虑一下上面提到的拒绝任务的情况。默认情况下,当任务被拒绝时,线程池执行程序将抛出TaskRejectedException。但是,拒绝策略实际上是可配置的。使用默认拒绝策略(即AbortPolicy实现)时会引发异常。对于在重负载下可以跳过某些任务的应用程序,可以改为配置DiscardPolicyDiscardOldestPolicy。对于需要在重负载下限制提交的任务的应用程序,另一个很好的选择是CallerRunsPolicy。该策略不会引发异常或放弃任务,而只会强制调用提交方法的线程运行任务本身。想法是这样的调用者在运行该任务时会很忙,而无法立即提交其他任务。因此,它提供了一种在保持线程池和队列限制的同时限制传入负载的简单方法。通常,这使执行程序可以“赶上”它正在处理的任务,从而释放队列,池中或两者中的某些容量。这些选项中的任何一个都可以从对'executor'元素的'rejection-policy'属性可用的值的枚举中选择。

<task:executor
        id="executorWithCallerRunsPolicy"
        pool-size="5-25"
        queue-capacity="100"
        rejection-policy="CALLER_RUNS"/>

最后,keep-alive设置确定线程终止之前可以保持空闲状态的时间限制(以秒为单位)。如果当前池中的线程数超过核心数,则在 await 此时间而不处理任务之后,多余的线程将被终止。时间值为零将导致多余的线程在执行任务后立即终止,而不会在任务队列中保留后续工作。

<task:executor
        id="executorWithKeepAlive"
        pool-size="5-25"
        keep-alive="120"/>

34.5.3“计划任务”元素

Spring 任务名称空间最强大的功能是支持配置要在 Spring Application Context 中调度的任务。这遵循类似于 Spring 中其他“方法调用者”的方法,例如 JMS 命名空间提供的用于配置消息驱动的 POJO 的方法。基本上,“ ref”属性可以指向任何 SpringManagement 的对象,“ 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"/>

如您所见,调度程序由外部元素引用,每个单独的任务都包括其触发元数据的配置。在前面的示例中,该元数据定义了一个具有固定延迟的周期性触发器,该延迟指示了每个任务执行完成后要 await 的毫秒数。另一个选项是“固定速率”,它表示应该执行该方法的频率,而不管之前执行的时间如何。另外,对于固定延迟和固定速率任务,都可以指定“初始延迟”参数,该参数指示在第一次执行该方法之前要 await 的毫秒数。为了更好地控制,可以改为提供“ 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"/>

34.6 使用 Quartz Scheduler

Quartz 使用TriggerJobJobDetail对象来实现各种作业的调度。有关 Quartz 的基本概念,请查看http://quartz-scheduler.org。为了方便起见,Spring 提供了两个类来简化 Quartz 在基于 Spring 的应用程序中的使用。

34.6.1 使用 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)。超时在作业数据 Map 中指定。作业数据 Map 可通过JobExecutionContext(在执行时传递给您)获得,但是JobDetail也会从 Map 到作业实例属性的作业数据中获取其属性。因此,在这种情况下,如果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
    }

}

当然,您也可以使用工作数据图中的所有其他属性。

Note

使用namegroup属性,您可以分别修改作业的名称和组。默认情况下,作业的名称与JobDetailFactoryBean的 bean 名称匹配(在上面的示例中为exampleJob)。

34.6.2 使用 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 是 Stateless 的,从而导致作业相互干扰的可能性。如果为相同的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>

Note

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

34.6.3 使用触发器和 SchedulerFactoryBean 连接作业

我们已经创建了工作详细信息和工作。我们还回顾了便捷 bean,该 bean 使您可以在特定对象上调用方法。当然,我们仍然需要自己安排工作。使用触发器和SchedulerFactoryBean完成此操作。 Quartz 中有几个触发器可用,Spring 提供了两个 Quartz FactoryBean实现,它们带有方便的默认值:CronTriggerFactoryBeanSimpleTriggerFactoryBean

触发器需要安排。 Spring 提供了一个SchedulerFactoryBean,它公开了要设置为属性的触发器。 SchedulerFactoryBean通过这些触发器计划实际的作业。

在下面找到几个示例:

<bean id="simpleTrigger" class="org.springframework.scheduling.quartz.SimpleTriggerFactoryBean">
    <!-- see the example of method invoking job above -->
    <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设置更多属性,例如作业详细信息所使用的 calendar,用于自定义 Quartz 的属性等。有关更多信息,请查看SchedulerFactoryBean javadocs