7.任务执行和调度

7.1 介绍

Spring框架分别为TaskExecutor和TaskScheduler接口提供异步执行和任务调度的抽象。 Spring还具有支持线程池或在应用程序服务器环境中对CommonJ进行委派的接口的实现。 最终,在通用接口背后使用这些实现将会消除Java SE 5,Java SE 6和Java EE环境之间的差异。

Spring还具有集成类,用于支持使用定时器(自1.3版JDK以来的一部分)和Quartz Scheduler(http://quartz-scheduler.org)进行调度。 这两个调度器都使用FactoryBean进行设置,分别对Timer或Trigger实例使用可选的引用。 此外,Quartz Scheduler和Timer都有一个便利的类,允许你调用现有目标对象的方法(类似于普通的MethodInvokingFactoryBean操作)。

7.2 Spring TaskExecutor抽象

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

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

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

7.2.1. TaskExecutor 类型

Spring发行版中包含了许多预先构建的TaskExecutor实现。 很可能,你永远不需要实现你自己的。

  • SimpleAsyncTaskExecutor此实现不重用任何线程,而是为每个调用启动一个新的线程。 但是,它确实支持并发限制,这会限制超出限制的任何调用,直到某个插槽被释放为止。 如果您正在寻找真正的池,请参阅下面的SimpleThreadPoolTaskExecutor和ThreadPoolTaskExecutor的讨论。

  • SyncTaskExecutor此实现步执行调用。 相反,每个调用发生在调用线程中。 它主要用于不需要多线程的情况,如简单的测试用例。

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

  • SimpleThreadPoolTaskExecutor这个实现实际上是Quartz的SimpleThreadPool的一个子类,它监听Spring的生命周期回调。 当您有一个可能需要Quartz和非Quartz组件共享的线程池时,通常会使用它。

  • ThreadPoolTaskExecutor这个实现是最常用的一个。 它公开用于配置java.util.concurrent.ThreadPoolExecutor的bean属性并将其包装在TaskExecutor中。 如果您需要适应不同类型的java.util.concurrent.Executor,则建议您使用ConcurrentTaskExecutor。

  • WorkManagerTaskExecutor

CommonJ是BEA和IBM联合开发的一套规范。 这些规范不是Java EE标准,而是BEA和IBM的应用服务器实现的标准。

此实现使用CommonJ WorkManager作为其后台实现,并且是在Spring上下文中设置CommonJ WorkManager引用的中央便利类。 类似于SimpleThreadPoolTaskExecutor,这个类实现了WorkManager接口,因此也可以直接作为WorkManager使用。

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

7.3. Spring TaskScheduler抽象

除了TaskExecutor抽象之外,Spring 3.0还引入了一个TaskScheduler,它提供了多种方法来调度将来在某个时刻运行的任务。

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”的方法,只需要一个Runnable和Date。 这将导致任务在指定时间后运行一次。 所有其他方法都能够安排重复运行的任务。 固定速率和固定延迟方法用于简单的周期性执行,但接受触发器的方法要灵活得多。

7.3.1. 触发器接口

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

public interface Trigger {

    Date nextExecutionTime(TriggerContext triggerContext);

}

正如你所看到的,TriggerContext是最重要的部分。 它封装了所有的相关数据,并在必要的时候在未来开放。 TriggerContext是一个接口(默认使用SimpleTriggerContext实现)。 在这里你可以看到触发器实现有什么方法可用。

public interface TriggerContext {

    Date lastScheduledExecutionTime();

    Date lastActualExecutionTime();

    Date lastCompletionTime();

}

7.3.2. 触发器实现

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

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

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

7.3.3. TaskScheduler 实现

与Spring的TaskExecutor抽象一样,TaskScheduler的主要优点是依赖于调度行为的代码不需要耦合到特定的调度器实现。 在应用程序服务器环境中运行时,这种提供的灵活性特别相关,在这种环境中不应该由应用程序本身直接创建线程 对于这样的情况,Spring提供了一个TimerManagerTaskScheduler,用于委托一个CommonJ TimerManager实例,通常配置了一个JNDI-lookup.。

只要不需要外部线程管理,就可以使用ThreadPoolTaskScheduler这个更简单的替代方法。 在内部,它委托给一个ScheduledExecutorService实例。 ThreadPoolTaskScheduler实际上也实现了Spring的TaskExecutor接口,因此可以尽可能快地使用单个实例来执行异步执行,以及可能的重复执行。

7.4.对调度和异步执行的注解支持

spring为任务调度和异步方法执行提供注解支持。

7.4.1.启用调度注释

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

@Configuration
@EnableAsync
@EnableScheduling
public class AppConfig {
}

您可以自由选择您的应用程序的相关注解。 例如,如果您只需要支持@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批注方法相对应的任务,并且提供了调度器引用来管理那些用@Scheduled批注的方法。

用于处理@Async注释的默认建议模式是“代理”,它只允许通过代理拦截调用; 同一个类的本地用不能被拦截。 对于更高级的拦截模式,考虑切换到“aspectj”模式结合编译时或加载时织入。

7.4.2. The @Scheduled 注解

@Scheduled注释可以与触发器元数据一起添加到方法中。 例如,下面的方法会每5秒调用一个固定的延迟,这意味着这个周期将从每个前面的调用的完成时间开始测量。

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

如果需要固定费率执行,只需更改注释中指定的属性名称即可。 在每个调用的连续开始时间之间测量,每隔5秒执行一次。

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

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

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

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

请注意,要计划的方法必须无返回值,并且不能有任何参数。 如果方法需要与应用程序上下文中的其他对象进行交互,那么这些方法通常是通过依赖注入来提供的。

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

确保你没有在运行时初始化同一个@Scheduled注解类的多个实例,除非你想为每个这样的实例安排回调。 与此相关的是,确保你没有在@Scheduled注释的bean类上使用@Configurable,并且在容器中注册为普通的Spring bean:否则,你将得到双重初始化,否则一旦通过容器并且通过@Configurable方面 每个@Scheduled方法的结果都被调用两次。

7.4.3. @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类型的返回值。 这仍然提供了异步执行的好处,以便调用者可以在未来调用get()之前执行其他任务。

@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以及自定义切入点。

7.4.4. Executor qualification with @Async

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

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

在这种情况下,“otherExecutor”可以是Spring容器中的任何Executor bean的名称,或者可以是与任何Executor相关联的限定符的名称。 如<qualifier>元素或Spring的@Qualifier注释所指定的。

7.4.5. 使用@Async进行异常管理

当@Async方法具有Future类型的返回值时,很容易管理在方法执行过程中抛出的异常,因为调用get Future结果时将引发此异常。 但是,如果使用void返回类型,那么异常是未捕获的,无法传输。 对于这些情况,可以提供一个AsyncUncaughtExceptionHandler来处理这种异常。

public class MyAsyncUncaughtExceptionHandler implements AsyncUncaughtExceptionHandler {

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

默认情况下,该异常是简单的记录。 定制的AsyncUncaughtExceptionHandler可以通过AsyncConfigurer或task:annotation-driven的XML元素来定义。

7.5. 任务的命名空间

从Spring 3.0开始,有一个用于配置TaskExecutor和TaskScheduler实例的XML名称空间。 它还提供了一种便捷的方式来配置要使用触发器进行调度的任务。

7.5.1. 'scheduler' 元素

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

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

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

7.5.2. 'executor' 元素

以下将创建一个ThreadPoolTaskExecutor实例:

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

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

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

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

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

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

最后,保持活动设置确定线程在终止之前可能保持空闲的时间限制(以秒为单位)。 如果当前池中的线程的核心数量多于此值,则在等待该时间量之后不处理任务,多余的线程将被终止。 时间值为零将导致多余的线程在执行任务后立即终止,而无需在任务队列中保留后续工作。

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

7.5.3. 'scheduled-tasks' 元素

Spring的task命名空间最强大的功能是支持配置在Spring应用程序上下文中调度的任务。 这遵循类似于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"/>

如您所见,调度程序由外部元素引用,每个单独的任务都包含其触发器元数据的配置。 在前面的例子中,该元数据定义了一个具有固定延迟的周期性触发器,指示每个任务执行完成后要等待的毫秒数。 另一种选择是“固定比率”,表示无论先前的执行花费多少时间,该方法执行的频率。 此外,对于固定延迟和固定速率任务,可以指定“初始延迟”参数,指示在方法的第一次执行之前等待的毫秒数。 为了更多的控制,可以提供“cron”属性。 这里是一个演示这些其他选项的例子。

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

7.6. 使用 Quartz 调度器

Quartz使用Trigger,Job和JobDetail对象来实现各种作业的调度。 对于Quartz背后的基本概念,请看http://quartz-scheduler.org。 为了方便起见,Spring提供了一些简化在基于Spring的应用程序中使用Quartz的类。

7.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)。 超时在作业数据图中指定。 作业数据映射可通过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
    }

}

工作数据图中的所有附加属性当然也可以提供给您。

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

7.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是无状态的,导致作业互相干扰的可能性。 如果您为相同的JobDetail指定了两个触发器,则有可能在第一个作业完成之前,第二个作业将启动。 如果JobDetail类实现了有状态接口,这不会发生。 第二份工作在第一份工作完成之前不会开始。 要使MethodInvokingJobDetailFactoryBean产生的作业不是并发的,请将并发标志设置为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>

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

7.6.3. 使用触发器和SchedulerFactoryBean来连接作业

我们已经创建了工作细节和工作。 我们还检查了便捷bean,它允许您调用特定对象上的方法。 当然,我们仍然需要自己安排工作。 这是使用触发器和SchedulerFactoryBean完成的。 在Quartz中有几个触发器可供使用,Spring提供了两个Quartz FactoryBean实现,它们具有方便的默认值:CronTriggerFactoryBean和SimpleTriggerFactoryBean。

触发器需要调度。 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有更多的属性供你设置,比如作业细节所使用的日历,用Quartz自定义的属性等。查看SchedulerFactoryBean的javadoc以获取更多信息。

results matching ""

    No results matching ""