Spring(5.1.6)官方文档之Task Execution and Scheduling
7.任务执行和调度
Spring框架分别使用TaskExecutor和TaskScheduler这两个接口来为异步执行和任务调度提供抽象。Spring还具有支持线程池或在应用程序服务器环境中委托给CommonJ的接口的实现。最终,在公共接口后使用这些实现抽象出了Java SE 5,Java SE 6和Java EE环境之间的差异。
Spring还可以集成类来通过Timer和Quartz Scheduler(https://www.quartz-scheduler.org/)支持任务调度。你可以通过使用一个对Timer或者Trigger实例可选择引用的FactoryBean来分别建立前面的两种调度器。此外,还有一个对Quartz Scheduler和Timer都有效的convenience类让你可以调用已存在的目标对象的方法(类似正常的MethodInvokingFactoryBean 操作)。
7.1 Spring TaskExecutor抽象
Executors是JDK与线程池概念有关的名字。命名为Executor是因为事实上无法保证在底层实现上是真的池子。一个Executor可以是单线程甚至是同步的。Spring的抽象隐藏了Java SE和Java EE环境之间的实现细节。
Spring的TaskExecutor接口和java.util.concurrent.Executor接口是相同的。实际上在最初的时候,它存在的主要原因是在使用线程池的时候抽象化对Java 5的需求。这个接口只有一个方法(execute(Runnable task)),该方法基于线程池的语义和配置来接受要执行的任务。
最初创建TaskExecutor是为了给其他Spring组件提供需要的线程池抽象。像ApplicationEventMulticaster,JMS中的AbstractMessageListenerContainer和Quartz集成这些Spring组件都是用Executor抽象来构造线程池。但是,如果你自己的bean需要线程池功能,你也可以根据自己的需要来使用这个抽象。
7.1.1 TaskExecutor类型
Spring包含了许多TaskExecutor的预构建实现,很可能你永远都不需要自己的实现。Spring提供的变体如下:
--SyncTaskExecutor:此实现不会异步执行调用,相反,每个调用都发生在调用线程中,它主要用于不需要多线程的情况,例如简单的测试用例中
--SimpleAsyncTaskExecutor:此实现不会重用任何进程,相反,它为每次调用启动一个新线程。但是,它确实支持并发限制,该限制会阻止任何超出限制的调用,直到释放一个插槽。如果你正在寻找真正的池,请参阅列表后面的ThreadPoolTaskExecutor。
--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 application context中,基于CommonJ建立在weblogic和websphere上的中心convenience类。
--DefaultManagedTaskExecutor:这个实现在JSR-236可兼容运行环境中使用JNDI获取的ManagedExecutorService,用来代替CommonJ WorkMannager。
7.1.2 使用一个TaskExecutor
Spring的TaskExecutor实现被当作一个简单的JavaBean使用。在下面的这个例子中,我们定义了一个使用ThreadPoolTaskExecutor异步打印一串信息的bean
1 @Component 2 public class TaskExecutorExample { 3 4 private class MessagePrinterTask implements Runnable { 5 private String message; 6 7 public MessagePrinterTask(String message) { 8 this.message = message; 9 } 10 11 @Override 12 public void run() { 13 System.out.println(message); 14 } 15 } 16 17 private TaskExecutor taskExecutor; 18 19 public TaskExecutorExample(TaskExecutor taskExecutor) { 20 this.taskExecutor = taskExecutor; 21 } 22 23 public void printMessage() { 24 for(int i = 0; i < 25; i++) { 25 taskExecutor.execute(new MessagePrinterTask("Message" + i)); 26 } 27 } 28 }
测试方法如下(我自己写的,非官方内容):
1 @RunWith(SpringRunner.class) 2 @SpringBootTest 3 public class TaskExecutorExampleTest { 4 5 @Autowired 6 private TaskExecutorExample taskExecutorExample; 7 8 @Test 9 public void printMessage() { 10 taskExecutorExample.printMessage(); 11 } 12 }
输出结果:
7.2 Spring的TaskScheduler抽象
除了TaskExecutor抽象之外,Spring3.0还引入了一个TaskScheduler,它具有各种方法,可以在将来的某个时刻调度任务,一下清单显示了TaskScheduler接口定义:
1 public interface TaskScheduler { 2 3 ScheduledFuture schedule(Runnable task, Trigger trigger); 4 5 ScheduledFuture schedule(Runnable task, Instant startTime); 6 7 ScheduledFuture schedule(Runnable task, Date startTime); 8 9 ScheduledFuture scheduleAtFixedRate(Runnable task, Instant startTime, Duration period); 10 11 ScheduledFuture scheduleAtFixedRate(Runnable task, Date startTime, long period); 12 13 ScheduledFuture scheduleAtFixedRate(Runnable task, Duration period); 14 15 ScheduledFuture scheduleAtFixedRate(Runnable task, long period); 16 17 ScheduledFuture scheduleWithFixedDelay(Runnable task, Instant startTime, Duration delay); 18 19 ScheduledFuture scheduleWithFixedDelay(Runnable task, Date startTime, long delay); 20 21 ScheduledFuture scheduleWithFixedDelay(Runnable task, Duration delay); 22 23 ScheduledFuture scheduleWithFixedDelay(Runnable task, long delay); 24 }
最简单的方法是一个名为schedule的方法,它只接受Runnable和Date。这会导致任务在指定时间后运行一次。其他所有方法都能够安排任务重复运行。固定速率和固定延迟方法用于简单的定期执行,但接受触发的方法更灵活。
7.2.1 Trigger接口
Trigger接口基本上受到JSR-236的启发,在Spring3.0开始时它还未正式实现。触发器的基本思想是可以基于过去的执行结果或者任意的条件来确定执行时间。如果在确定过程中确实考虑了前面执行的结果,那么该信息在TriggerContext中可用。Trigger接口本身非常简单,如下所示
1 public interface Trigger { 2 3 Date nextExecutionTime(TriggerContext triggerContext); 4 }
TriggerContext是最重要的部分。它封装了所有相关数据,如有必要,将来可以进行扩展。TriggerContext是一个接口(默认情况下使用SimpleTriggerContext实现)。下面显示了Trigger的可实现方法
1 public interface TriggerContext { 2 3 Date lastScheduledExecutionTime(); 4 5 Date lastActualExecutionTime(); 6 7 Date lastCompletionTime(); 8 }
7.2.2 Trigger实现
Spring提供了Trigger接口的两个实现。最有趣的是CronTrigger,它支持使用cron表达式调度任务。例如,以下任务计划在每小时15分钟时运行,但仅在工作日的上午九点到下午五点运行
1 scheduler.schedule(task, new CronTrigger("0 15 9-17 * * MON-FRI"));
另一个实现是PeriodicTrigger,它接受固定周期,可选的初始延迟值和布尔值,以指示周期是应该被解释为固定速率还是固定延迟。由于TaskScheduler接口已经定义了以固定速率或固定延迟来调度任务的方法,因此尽可能直接使用这些方法。PeriodicTrigger实现的价值是可以在依赖Trigger抽象的组件中使用它。例如,允许定期触发,基于cron的触发器甚至自定义触发器实现可互换使用是非常方便的。这样的组件可以使用依赖注入,以便可以在外部配置此类触发器,因此可以轻松地修改或者扩展它们。
7.2.3 TaskScheduler实现
与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上。
7.3 任务调度和异步执行的注解支持
Spring为任务调度和异步执行都提供了注解支持
7.3.1 启用调度注解
要启用对@Scheduled和@Async注解的支持,可以将@EnableScheduling和@EnableAsync添加到其中一个@Configuration类中,如以下示例所示:
1 @Configuration 2 @EnableAsync 3 @EnableScheduling 4 public class AppConfig { 5 }
您可以为您的应用选择相关注解。 例如,如果只需要支持@Scheduled,则可以省略@EnableAsync。 对于更细粒度的控制,您还可以实现SchedulingConfigurer接口,AsyncConfigurer接口或两者。 有关完整详细信息,请参阅SchedulingConfigurer和AsyncConfigurer javadoc。
7.3.2 @Scheduled注解
您可以将@Scheduled注解与触发器元数据一起添加到方法中。 例如,以固定延迟每五秒调用以下方法,这意味着该周期是从每个前一次调用的完成时间开始测量的:
1 @Scheduled(fixedDelay=5000) 2 public void doSomething() { 3 // something that should execute periodically 4 }
如果需要固定速率执行,则可以更改注释中指定的属性名称。 每五秒调用以下方法(在每次调用的连续开始时间之间测量):
1 @Scheduled(fixedRate=5000) 2 public void doSomething() { 3 // something that should execute periodically 4 }
对于固定延迟和固定速率任务,您可以通过指示在第一次执行方法之前等待的毫秒数来指定初始延迟,如下面的fixedRate示例所示:
1 @Scheduled(initialDelay=1000, fixedRate=5000) 2 public void doSomething() { 3 // something that should execute periodically 4 }
如果简单的定期调度表达不够,则可以提供cron表达式。 例如,以下仅在工作日执行:
1 @Scheduled(cron="*/5 * * * * MON-FRI") 2 public void doSomething() { 3 // something that should execute on weekdays only 4 }
请注意,要调度的方法必须具有void返回,并且不得指望任何参数。 如果该方法需要与应用程序上下文中的其他对象进行交互,则通常会通过依赖项注入来提供这些对象。
确保您没有在运行时初始化同一个@Scheduled注释类的多个实例,除非您确实要为每个此类实例安排回调。 与此相关,请确保不对使用@Scheduled注释的bean类使用@Configurable,并将其注册为带容器的常规Spring bean。 否则,您将获得双初始化(一次通过容器,一次通过@Configurable方面),每个@Scheduled方法的结果被调用两次。
7.3.3 异步注解
您可以在方法上提供@Async注解,以便异步调用该方法。 换句话说,调用程序在调用时立即返回,而方法的实际执行发生在已提交给Spring TaskExecutor的任务中。 在最简单的情况下,您可以将注解应用于返回void的方法,如以下示例所示:
1 @Async 2 void doSomething() { 3 // this will be executed asynchronously 4 }
与使用@Scheduled注解的方法不同,这些方法可以期望参数,因为它们在运行时由调用者以“正常”方式调用,而不是由容器管理的调度任务调用。 例如,以下代码是@Async注解的合法应用程序:
1 @Async 2 void doSomething(String s) { 3 // this will be executed asynchronously 4 }
甚至可以异步调用返回值的方法。 但是,这些方法需要具有Future类型的返回值。 这仍然提供了异步执行的好处,以便调用者可以在调用Future上的get()之前执行其他任务。 以下示例显示如何在返回值的方法上使用@Async:
1 @Async 2 Future<String> returnSomething(int i) { 3 // this will be executed asynchronously 4 }
@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带注释的方法,如以下示例所示:
1 public class SampleBeanImpl implements SampleBean { 2 3 @Async 4 void doSomething() { 5 // ... 6 } 7 8 } 9 10 public class SampleBeanInitializer { 11 12 private final SampleBean bean; 13 14 public SampleBeanInitializer(SampleBean bean) { 15 this.bean = bean; 16 } 17 18 @PostConstruct 19 public void initialize() { 20 bean.doSomething(); 21 } 22 23 }
7.3.4 使用@Async注解区分Executor
默认情况下,在方法上指定@Async时,使用的执行程序是启用异步支持时配置的执行程序,即如果使用XML或AsyncConfigurer实现(如果有),则为“annotation-driven”元素。 但是,如果需要指示在执行给定方法时应使用非默认执行程序,则可以使用@Async批注的value属性。 以下示例显示了如何执行此操作:
1 @Async("otherExecutor") 2 void doSomething(String s) { 3 // this will be executed asynchronously by "otherExecutor" 4 }
在这种情况下,“otherExecutor”可以是Spring容器中任何Executor bean的名称,也可以是与任何Executor关联的限定符的名称(例如,使用<qualifier>元素或Spring的@Qualifier注释指定))。
7.3.5 用@Async进行异常管理
当@Async方法具有Future-typed返回值时,很容易管理在方法执行期间抛出的异常,因为在调用get on Future结果时抛出此异常。 但是,对于void返回类型,异常未被捕获且无法传输。 您可以提供AsyncUncaughtExceptionHandler来处理此类异常。 以下示例显示了如何执行此操作:
1 public class MyAsyncUncaughtExceptionHandler implements AsyncUncaughtExceptionHandler { 2 3 @Override 4 public void handleUncaughtException(Throwable ex, Method method, Object... params) { 5 // handle exception 6 } 7 }
默认情况下,仅记录异常。 您可以使用AsyncConfigurer或<task:annotation-driven /> XML元素定义自定义AsyncUncaughtExceptionHandler。
7.4 Task 命名空间
从3.0版开始,Spring包含一个用于配置TaskExecutor和TaskScheduler实例的XML命名空间。 它还提供了一种方便的方法来配置要使用触发器安排的任务。
7.4.1 scheduler元素
以下元素使用指定的线程池大小创建ThreadPoolTaskScheduler实例:
1 <task:scheduler id="scheduler" pool-size="10"/>
为id属性提供的值将用作池中线程名称的前缀。 调度程序元素相对简单。 如果未提供pool-size属性,则默认线程池只有一个线程。 调度程序没有其他配置选项。
7.4.2 executor元素
下面创建了一个ThreadPoolTaskExecutor实例
1 <task:executor id="executor" pool-size="10"/>
与上一节中显示的调度程序一样,为id属性提供的值将用作池中线程名称的前缀。 就池大小而言,executor元素支持比scheduler元素更多的配置选项。 首先,ThreadPoolTaskExecutor的线程池本身更易于配置。 执行程序的线程池可以具有不同的核心值和最大大小,而不仅仅是单个大小。 如果提供单个值,则执行程序具有固定大小的线程池(核心和最大大小相同)。 但是,executor元素的pool-size属性也接受min-max形式的范围。 以下示例将最小值设置为5,将最大值设置为25
1 <task:executor 2 id="executorWithPoolSizeRange" 3 pool-size="5-25" 4 queue-capacity="100"/>
在前面的配置中,还提供了队列容量值。 还应根据执行程序的队列容量来考虑线程池的配置。 有关池大小和队列容量之间关系的完整描述,请参阅ThreadPoolExecutor的文档。 主要思想是,当提交任务时,如果活动线程的数量当前小于核心大小,则执行程序首先尝试使用空闲线程。 如果已达到核心大小,则只要尚未达到其容量,任务就会添加到队列中。 只有这样,如果已达到队列的容量,执行程序是否会创建超出核心大小的新线程。 如果还达到了最大大小,则执行程序拒绝该任务。
默认情况下,队列是无限制的,但这很少是所需的配置,因为如果在所有池线程忙的情况下将足够的任务添加到该队列,则可能导致OutOfMemoryErrors。 此外,如果队列是无界的,则最大大小根本没有影响。 由于执行程序总是在创建超出核心大小的新线程之前尝试队列,因此队列必须具有有限的容量,以使线程池增长超出核心大小(这就是为什么固定大小的池是使用时唯一合理的情况 一个无限的队列)。
如上所述,在任务被拒绝时考虑这种情况。默认情况下,当任务被拒绝时,线程池执行程序会抛出TaskRejectedException。但是,拒绝策略实际上是可配置的。使用默认拒绝策略时抛出异常,即AbortPolicy实现。对于可以在高负载下跳过某些任务的应用程序,您可以改为配置DiscardPolicy或DiscardOldestPolicy。另一个适用于需要在高负载下限制提交任务的应用程序的选项是CallerRunsPolicy。该策略不是抛出异常或丢弃任务,而是强制调用submit方法的线程自己运行任务。这个想法是这样的调用者在运行该任务时很忙,并且不能立即提交其他任务。因此,它提供了一种简单的方法来限制传入的负载,同时保持线程池和队列的限制。通常,这允许执行程序“赶上”它正在处理的任务,从而释放队列,池中或两者中的一些容量。您可以从executor元素上的rejection-policy属性的可用值枚举中选择任何这些选项。
以下示例显示了一个executor元素,其中包含许多属性以指定各种行为:
1 <task:executor 2 id="executorWithCallerRunsPolicy" 3 pool-size="5-25" 4 queue-capacity="100" 5 rejection-policy="CALLER_RUNS"/>
最后,keep-alive设置确定线程在终止之前可以保持空闲的时间限制(以秒为单位)。 如果池中当前有多个线程核心数,则在等待这段时间而不处理任务后,多余的线程将被终止。 时间值为零会导致多余线程在执行任务后立即终止,而不会在任务队列中保留后续工作。 以下示例将keep-alive值设置为两分钟:
1 <task:executor 2 id="executorWithKeepAlive" 3 pool-size="5-25" 4 keep-alive="120"/>
7.4.3 scheduled-tasks
Spring的任务命名空间最强大的功能是支持在Spring Application Context中配置要安排的任务。 这遵循类似于Spring中的其他“方法调用者”的方法,例如由JMS名称空间提供的用于配置消息驱动的POJO的方法。 基本上,ref属性可以指向任何Spring管理的对象,method属性提供要在该对象上调用的方法的名称。 以下清单显示了一个简单示例:
1 <task:scheduled-tasks scheduler="myScheduler"> 2 <task:scheduled ref="beanA" method="methodA" fixed-delay="5000"/> 3 </task:scheduled-tasks> 4 5 <task:scheduler id="myScheduler" pool-size="10"/>
调度程序由外部元素引用,每个单独的任务包括其触发器元数据的配置。 在前面的示例中,该元数据定义了具有固定延迟的周期性触发,该延迟指示在每个任务执行完成之后等待的毫秒数。 另一种选择是固定速率,表示无论先前执行多长时间,该方法应执行的频率。 此外,对于固定延迟和固定速率任务,您可以指定“initial-delay”参数,指示在首次执行方法之前等待的毫秒数。 为了获得更多控制,您可以改为提供cron属性。 以下示例显示了以下其他选项:
1 <task:scheduled-tasks scheduler="myScheduler"> 2 <task:scheduled ref="beanA" method="methodA" fixed-delay="5000" initial-delay="1000"/> 3 <task:scheduled ref="beanB" method="methodB" fixed-rate="5000"/> 4 <task:scheduled ref="beanC" method="methodC" cron="*/5 * * * * MON-FRI"/> 5 </task:scheduled-tasks> 6 7 <task:scheduler id="myScheduler" pool-size="10"/>
7.5 使用Quartz Scheduler
Quartz使用Trigger,Job和JobDetail对象来实现各种作业的调度。 有关Quartz背后的基本概念,请参阅https://www.quartz-scheduler.org/。 为方便起见,Spring提供了几个简化在基于Spring的应用程序中使用Quartz的类。
7.5.1使用JobDetailFactoryBean
Quartz JobDetail对象包含运行作业所需的所有信息。 Spring提供了一个JobDetailFactoryBean,它为XML配置提供了bean风格的属性。 请考虑以下示例:
1 <bean name="exampleJob" class="org.springframework.scheduling.quartz.JobDetailFactoryBean"> 2 <property name="jobClass" value="example.ExampleJob"/> 3 <property name="jobDataAsMap"> 4 <map> 5 <entry key="timeout" value="5"/> 6 </map> 7 </property> 8 </bean>
作业详细信息配置具有运行作业所需的所有信息(ExampleJob)。 超时在作业数据映射中指定。 作业数据映射可通过JobExecutionContext(在执行时传递给您)获得,但JobDetail也从映射到作业实例属性的作业数据中获取其属性。 因此,在以下示例中,ExampleJob包含名为timeout的bean属性,JobDetail会自动应用它:
1 package example; 2 3 public class ExampleJob extends QuartzJobBean { 4 5 private int timeout; 6 7 /** 8 * Setter called after the ExampleJob is instantiated 9 * with the value from the JobDetailFactoryBean (5) 10 */ 11 public void setTimeout(int timeout) { 12 this.timeout = timeout; 13 } 14 15 protected void executeInternal(JobExecutionContext ctx) throws JobExecutionException { 16 // do the actual work 17 } 18 19 }
您也可以使用作业数据图中的所有其他属性。
通过使用名称和组属性,您可以分别修改作业的名称和组。 默认情况下,作业的名称与JobDetailFactoryBean的bean名称匹配(上面示例中的exampleJob)。
7.5.2 使用MethodInvokingJobDetailFactoryBean
通常,您只需要在特定对象上调用方法。 通过使用MethodInvokingJobDetailFactoryBean,您可以完成此操作,如以下示例所示:
1 <bean id="jobDetail" class="org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean"> 2 <property name="targetObject" ref="exampleBusinessObject"/> 3 <property name="targetMethod" value="doIt"/> 4 </bean>
前面的示例导致在exampleBusinessObject方法上调用doIt方法,如以下示例所示:
1 public class ExampleBusinessObject { 2 3 // properties and collaborators 4 5 public void doIt() { 6 // do the actual work 7 } 8 }
1 <bean id="exampleBusinessObject" class="examples.ExampleBusinessObject"/>
通过使用MethodInvokingJobDetailFactoryBean,您无需创建仅调用方法的单行作业。 您只需创建实际的业务对象并连接详细信息对象。
默认情况下,Quartz Jobs是无状态的,导致作业相互干扰的可能性。 如果为同一JobDetail指定两个触发器,则可能在第一个作业完成之前,第二个作业开始。 如果JobDetail类实现Stateful接口,则不会发生这种情况。 第二个作业在第一个作业完成之前没有开始。 要使MethodInvokingJobDetailFactoryBean生成的作业不是并发的,请将concurrent标志设置为false,如以下示例所示:
1 <bean id="jobDetail" class="org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean"> 2 <property name="targetObject" ref="exampleBusinessObject"/> 3 <property name="targetMethod" value="doIt"/> 4 <property name="concurrent" value="false"/> 5 </bean>
7.5.3 使用Trigger和SchedulerFactoryBean连接作业
我们已经创建了工作细节和工作。 我们还回顾了easy bean,它允许您调用特定对象的方法。 当然,我们仍然需要自己安排工作。 这是通过使用触发器和SchedulerFactoryBean完成的。 Quartz中提供了几个触发器,Spring提供了两个带有方便默认值的Quartz FactoryBean实现:CronTriggerFactoryBean和SimpleTriggerFactoryBean。
需要安排触发器。 Spring提供了一个SchedulerFactoryBean,它公开了要设置为属性的触发器。 SchedulerFactoryBean使用这些触发器调度实际作业。
以下清单使用SimpleTriggerFactoryBean和CronTriggerFactoryBean:
1 <bean id="simpleTrigger" class="org.springframework.scheduling.quartz.SimpleTriggerFactoryBean"> 2 <!-- see the example of method invoking job above --> 3 <property name="jobDetail" ref="jobDetail"/> 4 <!-- 10 seconds --> 5 <property name="startDelay" value="10000"/> 6 <!-- repeat every 50 seconds --> 7 <property name="repeatInterval" value="50000"/> 8 </bean> 9 10 <bean id="cronTrigger" class="org.springframework.scheduling.quartz.CronTriggerFactoryBean"> 11 <property name="jobDetail" ref="exampleJob"/> 12 <!-- run every morning at 6 AM --> 13 <property name="cronExpression" value="0 0 6 * * ?"/> 14 </bean>
上面的示例设置了两个触发器,一个每50秒运行一次,启动延迟为10秒,每天早上6点运行一个触发器。 要完成所有操作,我们需要设置SchedulerFactoryBean,如以下示例所示:
1 <bean class="org.springframework.scheduling.quartz.SchedulerFactoryBean"> 2 <property name="triggers"> 3 <list> 4 <ref bean="cronTrigger"/> 5 <ref bean="simpleTrigger"/> 6 </list> 7 </property> 8 </bean>
SchedulerFactoryBean可以使用更多属性,例如作业详细信息使用的日历,自定义Quartz的属性等。 有关更多信息,请参阅SchedulerFactoryBean javadoc。