34. 任务执行和调度

34.1 简介

Spring Framework 分别为TaskExecutorTaskScheduler接口提供异步执行和任务调度的抽象。 Spring 还 features implementations 那些支持线程池或在 application 服务器环境中委托给 CommonJ 的接口。最终,在 common 接口后面使用这些_implement 将抽象出 Java SE 5,Java SE 6 和 Java EE 环境之间的差异。

Spring 还 features integration classes 用于支持使用Timer(自 1.3 以来的 JDK 的一部分)和 Quartz Scheduler(http://quartz-scheduler.org)的调度。这两个调度程序都使用FactoryBean设置,可选 references 分别为TimerTrigger实例。此外,Quartz Scheduler 和Timer的便捷 class 可用,它允许您调用现有目标 object 的方法(类似于正常的MethodInvokingFactoryBean操作)。

34.2 Spring TaskExecutor 抽象

执行程序是线程池概念的 JDK name。 “执行程序”命名是由于无法保证底层 implementation 实际上是一个池;执行者可能是 single-threaded 甚至是同步的。 Spring 的抽象隐藏了 Java SE 和 Java EE 环境之间的 implementation 细节。

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

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

34.2.1 TaskExecutor 类型

Spring 发行版中包含了许多 pre-built _1 实现的TaskExecutor。很可能,你永远不需要实现自己的。 common out-of-the-box 变种是:

  • SyncTaskExecutor此 implementation 不会异步执行调用。相反,每次调用都发生在调用线程中。它主要用于不需要 multi-threading 的情况,例如在简单的测试用例中。

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

  • ConcurrentTaskExecutor此 implementation 是java.util.concurrent.Executor实例的适配器。有一个替代方法ThreadPoolTaskExecutor,它将Executor configuration 参数公开为 bean properties。很少需要直接使用ConcurrentTaskExecutor,但如果ThreadPoolTaskExecutor不够灵活以满足您的需求,则ConcurrentTaskExecutor是另一种选择。

  • ThreadPoolTaskExecutor这个 implementation 是最常用的。它公开 bean properties 以配置java.util.concurrent.ThreadPoolExecutor并将其包装在TaskExecutor中。如果您需要适应不同类型的java.util.concurrent.Executor,建议您改用ConcurrentTaskExecutor

  • WorkManagerTaskExecutor此 implementation 使用 CommonJ WorkManager作为其后备服务提供程序,并且是 Spring application context 中 WebLogic/WebSphere 上设置 CommonJ-based 线程池 integration 的中心便捷 class。

  • DefaultManagedTaskExecutor此 implementation 在 JSR-236 兼容的运行时环境(例如 Java EE 7 application 服务器)中使用 JNDI-obtained ManagedExecutorService,为此目的替换 CommonJ WorkManager。

34.2.2 使用 TaskExecutor

Spring 的TaskExecutor implementations 用作简单的 JavaBeans。在下面的 example 中,我们定义一个 bean,它使用ThreadPoolTaskExecutor来异步打印出一组消息。

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 properties。

<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,其中包含各种方法,可以在将来的某个时刻将任务调度到 run。

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

最简单的方法是名为'schedule'的方法,仅采用RunnableDate。这将导致任务在指定的 time 之后运行一次。所有其他方法都能够重复调度任务。 fixed-rate 和 fixed-delay 方法用于简单的定期执行,但接受触发器的方法更灵活。

34.3.1 触发界面

Trigger界面基本上受 JSR-236 的启发,从 Spring 3.0 开始,尚未正式实施。 Trigger的基本 idea 是执行时间可以根据过去的执行结果甚至任意条件来确定。如果这些确定确实考虑了前面执行的结果,则该信息在TriggerContext内可用。 Trigger接口本身很简单:

public interface Trigger {

    Date nextExecutionTime(TriggerContext triggerContext);
}

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

public interface TriggerContext {

    Date lastScheduledExecutionTime();

    Date lastActualExecutionTime();

    Date lastCompletionTime();
}

34.3.2 触发实现

Spring 提供了Trigger接口的两个 implementations。最有趣的是CronTrigger。它支持基于 cron 表达式调度任务。例如,以下任务计划在每小时后运行 15 分钟,但仅限于工作日的 9-to-5“营业时间”。

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

另一个 out-of-the-box implementation 是一个PeriodicTrigger,它接受一个固定的句点,一个可选的初始延迟 value 和一个 boolean 来指示该句点是应该被解释为 fixed-rate 还是 fixed-delay。由于TaskScheduler接口已经定义了在 fixed-rate 或 fixed-delay 上调度任务的方法,因此应尽可能直接使用这些方法。 PeriodicTrigger implementation 的 value 是它可以在依赖Trigger抽象的组件中使用。例如,允许定期触发器,cron-based 触发器甚至自定义触发器 implementations 可以互换使用。这样的 component 可以利用依赖注入,这样Triggers可以在外部配置,因此很容易修改或扩展。

34.3.3 TaskScheduler implementations

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

每当外部线程 management 不是必需的时候,更简单的替代方案是 application 中的本地ScheduledExecutorService设置,可以通过 Spring 的ConcurrentTaskScheduler进行调整。为方便起见,Spring 还提供了ThreadPoolTaskScheduler,它在内部委托给ScheduledExecutorService,沿着ThreadPoolTaskExecutor的 lines 提供 common bean-style configuration。这些变体对于宽松的 application 服务器环境中的本地嵌入式线程池设置也非常好,特别是在 Tomcat 和 Jetty 上。

34.4 Annotation 支持调度和异步执行

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

34.4.1 启用调度注释

要启用对@Scheduled@Async 注释的支持,请将@EnableScheduling@EnableAsync添加到@Configuration classes 中的一个:

@Configuration
@EnableAsync
@EnableScheduling
public class AppConfig {
}

您可以自由选择 application 的相关注释。例如,如果您只需要支持@Scheduled,则只需省略@EnableAsync。对于更多 fine-grained 控件,您还可以实现SchedulingConfigurer and/or AsyncConfigurer接口。有关详细信息,请参阅 javadocs。

如果您更喜欢 XML configuration,请使用<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 提供了一个 executor reference,用于处理与带有@Async annotation 的方法相对应的任务,并提供了 scheduler reference 来管理那些用@Scheduled注释的方法。

处理@Async annotations 的默认建议模式是“proxy”,它允许仅通过代理拦截 calls;同一 class 中的 local calls 不能以这种方式截获。对于更高级的拦截模式,请考虑结合 compile-time 或 load-time 编织切换到“aspectj”模式。

34.4.2 @Scheduled annotation

可以将@Scheduled annotation 与触发器元数据一起添加到方法中。对于 example,将使用固定延迟每 5 秒调用以下方法,这意味着将从每个前一次调用的完成 time 开始测量该时间段。

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

如果需要固定速率执行,只需更改 annotation 中指定的 property name。在每次调用的连续开始时间之间测量的每 5 秒执行以下操作。

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

对于 fixed-delay 和 fixed-rate 任务,可以指定初始延迟,指示在第一次执行该方法之前等待的毫秒数。

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

如果简单的周期性调度不够表达,则可以提供 cron 表达式。对于 example,以下内容仅在工作日执行。

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

您还可以使用zone属性指定将在其中解析 cron 表达式的 time zone。

请注意,要调度的方法必须具有 void 返回,并且不得指望任何 arguments。如果该方法需要与 Application Context 中的其他 object 交互,那么通常会通过依赖注入提供这些对象。

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

确保您没有在运行时初始化同一@Scheduled annotation class 的多个实例,除非您确实要为每个此类实例安排回调。与此相关,请确保不要在每个@Scheduled方法被调用两次的结果。

34.4.3 @Async annotation

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

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

与使用@Scheduled annotation 注释的方法不同,这些方法可以期望 arguments,因为它们将在运行时由调用者以“正常”方式调用,而不是由容器管理的调度任务调用。对于 example,以下是@Async annotation 的合法 application。

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

甚至可以异步调用 return value 的方法。但是,此类方法需要具有Future类型的 return value。这仍然提供了异步执行的好处,以便调用者可以在该 Future 上调用get()之前执行其他任务。

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

@Async方法不仅可以声明常规的java.util.concurrent.Future return 类型,还可以声明 Spring 的org.springframework.util.concurrent.ListenableFuture,或者 Spring 4.2,JDK 8 的java.util.concurrent.CompletableFuture:用于与异步任务的更丰富的交互以及通过进一步处理步骤立即组合。

@Async不能与@PostConstruct等生命周期回调一起使用。要异步初始化 Spring beans,您当前必须使用单独的初始化 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 等价物,因为这些方法应该首先设计用于异步执行,而不是外部 re-declared 是异步的。但是,您可以使用 Spring AOP 手动设置 Spring 的AsyncExecutionInterceptor,并结合自定义切入点。

34.4.4 执行者资格 @Async

默认情况下,在方法上指定@Async时,将使用的执行程序是提供给'annotation-driven'元素的执行程序,如上所述。但是,当需要指示在执行给定方法时应使用非默认执行程序时,可以使用@Async annotation 的value属性。

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

在这种情况下,“otherExecutor”可以是 Spring 容器中任何Executor bean 的 name,也可以是与任何Executor,e.g 关联的限定符的 name。与<qualifier>元素或 Spring 的@Qualifier annotation 一起指定。

34.4.5 Exception management with @Async

@Async方法具有Future typed return value 时,很容易管理在方法执行期间抛出的 exception,因为在Future结果上调用get时将抛出此 exception。但是,使用 void return 类型时,exception 未被捕获且无法传输。对于这些情况,可以提供AsyncUncaughtExceptionHandler来处理这样的 exceptions。

public class MyAsyncUncaughtExceptionHandler implements AsyncUncaughtExceptionHandler {

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

默认情况下,只记录 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'属性提供的 value 将用作池中线程名称的前缀。 'scheduler'元素相对简单。如果未提供“pool-size”属性,则默认线程池将只有一个线程。调度程序没有其他 configuration 选项。

34.5.2'executor'元素

以下将创建一个ThreadPoolTaskExecutor实例:

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

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

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

从该 configuration 可以看出,还提供了'queue-capacity'value。还应根据执行程序的队列容量来考虑线程池的配置。有关池大小和队列容量之间关系的完整描述,请参阅ThreadPoolExecutor的文档。主要的是,当提交任务时,如果 active 线程的数量当前小于核心大小,执行程序将首先尝试使用空闲线程。如果已达到核心大小,则任务将作为 long 添加到队列中,因为尚未达到其容量。只有这样,如果已达到队列的容量,执行程序是否会创建超出核心大小的新线程。如果还达到了最大大小,则执行程序将拒绝该任务。

默认情况下,队列是无限制的,但这很少是所需的 configuration,因为如果在所有池线程都忙的情况下将足够的任务添加到该队列,它可能会导致OutOfMemoryErrors。此外,如果队列是无界的,那么最大大小根本没有影响。由于执行程序将始终在创建超出核心大小的新线程之前尝试队列,因此队列必须具有有限的容量,以使线程池增长超出核心大小(这就是为什么固定大小的池是使用时唯一合理的情况一个无限的队列)。

在 moment 中,我们将检查 keep-alive 设置的效果,这增加了在提供池大小 configuration 时要考虑的另一个因素。首先,如上所述,让我们考虑一个任务被拒绝的情况。默认情况下,当任务被拒绝时,线程池执行程序将抛出TaskRejectedException。但是,拒绝 policy 实际上是可配置的。使用默认的拒绝 policy(AbortPolicy implementation)时会抛出 exception。对于可以在重负载下跳过某些任务的 applications,可以配置DiscardPolicyDiscardOldestPolicy。另一个适用于需要在重负载下限制提交的任务的应用程序的选项是CallerRunsPolicy。 policy 不会抛出 exception 或丢弃任务,而只是强制调用 submit 方法的线程 run 任务本身。 idea 是这样的调用者在执行该任务时会很忙,并且无法立即提交其他任务。因此,它提供了一种简单的方法来限制传入的负载,同时保持线程池和队列的限制。通常,这允许执行程序“赶上”它正在处理的任务,从而释放队列,池中或两者中的一些容量。可以从'executor'元素的'rejection-policy'属性的可用值枚举中选择任何这些选项。

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

最后,keep-alive设置确定线程在终止之前可以保持 idle 的 time 限制(以秒为单位)。如果池中当前有多个线程核心数,则在等待此数量的 time 而不处理任务后,多余的线程将被终止。 time value 为零将导致多余的线程在执行任务后立即终止,而不会在任务队列中保留 follow-up 工作。

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

34.5.3'scheduled-tasks'元素

Spring 任务命名空间最强大的 feature 是支持在 Spring Application Context 中配置要安排的任务。这遵循类似于 Spring 中的其他“method-invokers”的方法,例如由 JMS 名称空间提供的用于配置 Message-driven POJO 的方法。基本上,“ref”属性可以指向任何 Spring-managed object,“method”属性提供要在该对象上调用的方法的 name。这是一个简单的 example。

<task:scheduled-tasks scheduler="myScheduler">
    <task:scheduled ref="beanA" method="methodA" fixed-delay="5000"/>
</task:scheduled-tasks>

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

如您所见,调度程序由外部元素引用,每个单独的任务包括其触发器元数据的 configuration。在前面的示例中,该元数据定义了具有固定延迟的周期性触发,该延迟指示在每个任务执行完成之后等待的毫秒数。另一个选项是'fixed-rate',表示无论先前执行的执行时间长度如何都应该执行该方法的频率。此外,对于 fixed-delay 和 fixed-rate 任务,可以指定'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"/>

34.6 使用 Quartz 调度程序

Quartz 使用TriggerJobJobDetail objects 来实现各种作业的调度。有关 Quartz 背后的基本概念,请查看http://quartz-scheduler.org。为方便起见,Spring 提供了几个 classes,它们简化了 Spring-based applications 中 Quartz 的使用。

34.6.1 使用 JobDetailFactoryBean

Quartz JobDetail objects 包含_jun job 所需的所有信息。 Spring 提供JobDetailFactoryBean,它提供 bean-style properties 用于 XML configuration 目的。我们来看一个 example:

<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>

job detail configuration 具有_jun job(ExampleJob)所需的所有信息。超时在 job data map 中指定。 job 数据 map 可通过JobExecutionContext(在执行 time 时传递给您)获得,但JobDetail也从 job 数据中获取 properties 映射到 job 实例的 properties。所以在这种情况下,如果ExampleJob包含名为timeout的 bean property,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
    }

}

job data map 中的所有其他 properties 当然也可供您使用。

使用namegroup properties,您可以分别修改 job 的 name 和 group。默认情况下,job 的 name 与JobDetailFactoryBean的 bean name 匹配(在上面的 example 中,这是exampleJob)。

34.6.2 使用 MethodInvokingJobDetailFactoryBean

通常,您只需要在特定的 object 上调用方法。使用MethodInvokingJobDetailFactoryBean你可以做到这一点:

<bean id="jobDetail" class="org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean">
    <property name="targetObject" ref="exampleBusinessObject"/>
    <property name="targetMethod" value="doIt"/>
</bean>

上面的 example 将导致在exampleBusinessObject方法上调用doIt方法(见下文):

public class ExampleBusinessObject {

    // properties and collaborators

    public void doIt() {
        // do the actual work
    }
}
<bean id="exampleBusinessObject" class="examples.ExampleBusinessObject"/>

使用MethodInvokingJobDetailFactoryBean,您不需要创建只调用方法的 one-line 作业,而只需创建实际的业务 object 并连接详细信息 object。

默认情况下,Quartz Jobs 是 stateless,导致作业可能互相干扰。如果为同一个JobDetail指定了两个触发器,则可能在第一个 job 完成之前,第二个触发器将启动。如果JobDetail classes 实现Stateful接口,则不会发生这种情况。第二个 job 在第一个 job 完成之前不会启动。要通过MethodInvokingJobDetailFactoryBean non-concurrent 创建作业,请将concurrent flag 设置为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>

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

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

我们已经创建了 job 细节和工作。我们还回顾了 easy bean,它允许您在特定的 object 上调用方法。当然,我们仍然需要自己安排工作。这是使用触发器和SchedulerFactoryBean完成的。 Quartz 中提供了几个触发器,Spring 提供了两个 Quartz FactoryBean implementations,具有方便的默认值:CronTriggerFactoryBeanSimpleTriggerFactoryBean

需要安排触发器。 Spring 提供SchedulerFactoryBean,公开要设置为 properties 的触发器。 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设置更多 properties,例如 job 详细信息使用的日历,properties 以自定义 Quartz 等。有关详细信息,请查看SchedulerFactoryBean javadocs