任务执行和调度

Spring框架分别用TaskExecutor和TaskScheduler接口抽象了任务的异步执行和调度。Spring还提供了一些接口的实现,这些接口在应用服务器环境中支持线程池或委托给CommonJ(CommonJ WorkManager)。

Spring还提供了一些集成类来支持与Timer(JDK)和Quartz调度器(Quartz Scheduler https://www.quartz-scheduler.org/)的调度。

一、TaskExecutor

执行器是JDK中的线程池概念。被叫作“executor”是因为它实际是一个池,并不能保证底层实现。执行器可以是单线程的,甚至是同步的。Spring的抽象隐藏了java SE和java EE环境之间的实现细节。

Spring的TaskExecutor接口与java.util.concurrent.Executor接口最初存在的主要原因是在使用线程池时抽象出对Java5的需求。(接受一个任务池的可运行语义,并基于该任务池的执行语义)。

创建TaskExecutor的初衷是为其他Spring组件提供线程池的抽象。诸如ApplicationEventMulticaster、JMS的AbstractMessageListenerContainer和Quartz集成等组件都使用TaskExecutor抽象来池化线程。但是,如果bean需要线程池行为,你也可以根据自己的需要使用这种抽象。

任务执行器类型

Spring提供许多预构建的TaskExecutor实现,你基本不用需要实现你自己的。Spring提供的变体如下:

SyncTaskExecutor

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

SimpleAsyncTaskExecutor

此实现不重用任何线程。它会为每次调用启动一个新线程。但是,它确实支持一个并发限制,该限制阻止任何超过该限制的调用,直到释放了一个插槽。如果要查找真正的池,可以参考ThreadPoolTaskExecutor。

ConcurrentTaskExecutor

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

ThreadPoolTaskExecutor

这个实现是最常用的。它公开bean属性以配置java.util.concurrent.ThreadPoolExecutor并将其包装在TaskExecutor中。如果你需要适应另一种java.util.concurrent.Executor,我们建议你改用ConcurrentTaskExecutor。

WorkManagerTaskExecutor

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

DefaultManagedTaskExecutor

此实现使用在JSR-236兼容的运行时环境(例如JavaEE7+应用服务器)中获得的JNDI ManagedExecutor服务,以此取代CommonJ WorkManager。

使用TaskExecutor 
public class TaskExecutorExample {


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

    private class MessagePrinterTask implements Runnable {

        private String message;

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

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

将TaskExecutorExample声明为Spring IoC的一个Bean,参数是TaskExecutor的一个实现ThreadPoolTaskExecutor

@Bean
public ThreadPoolTaskExecutor poolTaskExecutor(){
    ThreadPoolTaskExecutor poolTaskExecutor = new ThreadPoolTaskExecutor();
    poolTaskExecutor.setCorePoolSize(5);
    poolTaskExecutor.setMaxPoolSize(10);
    poolTaskExecutor.setQueueCapacity(25);
    return poolTaskExecutor;
}

@Bean
public TaskExecutorExample taskExecutorExample(ThreadPoolTaskExecutor poolTaskExecutor){
    return new TaskExecutorExample(poolTaskExecutor);
}

单元测试

@RunWith(SpringRunner.class)
@WebAppConfiguration
@ContextHierarchy({
        @ContextConfiguration(classes = SpringConfig.class),
        @ContextConfiguration(classes = SpringMVCConfig.class)
})
public class ExecutorTest {

    @Autowired
    private TaskExecutorExample taskExecutorExample;

    @Test
    public void test(){
        taskExecutorExample.printMessages();
    }
}

二、TaskScheduler 

除了TaskExecutor接口之外,spring3.0还引入了一个TaskScheduler,它具有多种方法来调度将来某个时刻运行的任务。

public interface TaskScheduler {

    ScheduledFuture schedule(Runnable task, Trigger trigger);

    ScheduledFuture schedule(Runnable task, Instant startTime);

    ScheduledFuture schedule(Runnable task, Date startTime);

    ScheduledFuture scheduleAtFixedRate(Runnable task, Instant startTime, Duration period);

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

    ScheduledFuture scheduleAtFixedRate(Runnable task, Duration period);

    ScheduledFuture scheduleAtFixedRate(Runnable task, long period);

    ScheduledFuture scheduleWithFixedDelay(Runnable task, Instant startTime, Duration delay);

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

    ScheduledFuture scheduleWithFixedDelay(Runnable task, Duration delay);

    ScheduledFuture scheduleWithFixedDelay(Runnable task, long delay);
}

最简单的方法是一个名为schedule的方法,它只需要一个Runnable和一个日期。使任务在指定时间后运行一次。所有其他方法都能够安排任务重复运行。固定速率和固定延迟方法用于简单的周期性执行,但是接受触发器的方法要灵活得多。

Trigger接口

Trigger 接口基本上是受到JSR-236的启发,在spring3.0中,JSR-236还没有正式实现。Trigger 的基本思想是,可以根据过去的执行结果甚至任意条件来确定执行时间。如果这些决定确实考虑了前面执行的结果,那么该信息在TriggerContext中是可用的。触发器接口本身非常简单,如下表所示:

public interface Trigger {

    Date nextExecutionTime(TriggerContext triggerContext);
}

Trigger 上下文是最重要的部分。它封装了所有相关的数据,如果需要,可以在将来进行扩展。TriggerContext是一个接口(默认情况下使用SimpleTriggerContext实现)。

public interface TriggerContext {

    Date lastScheduledExecutionTime();

    Date lastActualExecutionTime();

    Date lastCompletionTime();
}
Trigger 实现

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

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

另一个实现是PeriodicTrigger,它接受一个固定的周期、一个可选的初始延迟值,以及一个布尔值来指示该周期是应该被解释为固定速率还是固定延迟。由于TaskScheduler接口已经定义了以固定速率或固定延迟调度任务的方法,因此应该尽可能直接使用这些方法。PeriodicTrigger实现的价值在于,你可以在依赖触发器抽象的组件中使用它。例如,允许周期性触发器、基于cron的触发器,甚至自定义触发器实现可以互换使用,这可能比较方便。这样的组件可以利用依赖注入,这样您就可以在外部配置这样的触发器,从而轻松地修改或扩展它们。

TaskScheduler实现

与Spring的TaskExecutor接口一样,TaskScheduler的主要好处是应用程序的调度需求与部署环境分离。当部署到不应由应用程序本身直接创建线程的应用程序服务器环境时,此抽象级别特别相关。对于这种情况,Spring提供了一个TimerManagerTaskScheduler,它委托给WebLogic或WebSphere上的CommonJ TimerManager,以及一个更新的DefaultManagedTaskScheduler,它委托给java EE7+环境中的JSR-236 ManagedScheduledExecutorService。这两种方法通常都配置有JNDI查找。

只要不需要外部线程管理,一个更简单的替代方法就是在应用程序中设置本地ScheduledExecutorService,它可以通过Spring的ConcurrentTaskScheduler进行调整。为了方便起见,Spring还提供了一个ThreadPoolTaskScheduler,它在内部委托给ScheduledExecutorService,以按照ThreadPoolTaskExecutor的方式提供公共bean样式的配置。这些变体非常适合在宽松的应用程序服务器环境中进行本地嵌入式线程池设置,尤其是在Tomcat和Jetty上。

三、注解版调度和异步执行

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

开启调度和异步执行

要启用对@Scheduled和@Async注解的支持,可以将@EnableScheduling和@enableSync添加到@Configuration类中。

@Configuration
@EnableAsync
@EnableScheduling
public class SpringConfig {
}

你可以选择应用程序的相关注释。例如,如果只需要对@Scheduled的支持,则可以省略@Enablesync。对于更细粒度的控制,您可以另外实现SchedulingConfigurer接口和/或AsyncConfigurer接口。

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

这里提供了一个executor引用来处理那些带有@Async注解的方法相对应的任务,而scheduler引用则用于管理那些用@Scheduled注解的方法。

注意:处理@Async注解的默认通知模式是proxy,它只允许通过代理拦截调用。同一个类中的本地调用不能以这种方式被拦截。

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

实现SchedulingConfigurer接口,重写configureTasks方法:

@Schedule注解的一个缺点就是其定时时间不能动态更改,它适用于具有固定任务周期的任务,若要修改任务执行周期,只能走“停服务→修改任务执行周期→重启服务”这条路。而基于 SchedulingConfigurer 接口方式可以做到。SchedulingConfigurer 接口可以实现在@Configuration等注解类上。

ScheduledTaskRegistrar类包括以下几个重要方法: 

void addTriggerTask(Runnable task, Trigger trigger) 
void addTriggerTask(TriggerTask task)
void addCronTask(Runnable task, String expression)
void addCronTask(CronTask task)
void addFixedRateTask(Runnable task, long interval)
void addFixedRateTask(IntervalTask task)
void addFixedDelayTask(Runnable task, long delay)
void addFixedDelayTask(IntervalTask task)

具体实现参考如下:

@Component
public class TestTask implements SchedulingConfigurer {
 
    @Override
    public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
        taskRegistrar.addTriggerTask(new Runnable() {
            @Override
            public void run() {
                // 定时任务要执行的内容
                System.out.println("【开始执行定时任务。。。】");
            }
        }, new Trigger() {
            @Override
            public Date nextExecutionTime(TriggerContext triggerContext) {
                // 定时任务触发,可修改定时任务的执行周期
                String cron = "0 0/5 * * * ?"; //可以将表达式配置在数据库中
                CronTrigger trigger = new CronTrigger(cron);
                Date nextExecDate = trigger.nextExecutionTime(triggerContext);
                return nextExecDate;
            }
        });
    }
}

提示: 如果在数据库修改时格式出现错误,则定时任务会停止,即使重新修改正确;此时只能重新启动项目才能恢复。

如果确实可能存在数据库表达式错误的,可以是使用org.springframework.scheduling.support.CronSequenceGenerator的isValidExpression验证下,错误的给个默认执行表达式。

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

 从springframework4.3开始,任何范围的bean都支持@Scheduled方法。

请确保你没有在运行时初始化同一@Scheduled注解类的多个实例,除非你确实希望为每个实例安排回调。与此相关的是,请确保不要在带有@Scheduled注解并在容器中注册为常规springbean的bean类上使用@Configurable。否则,您将获得双重初始化(一次通过容器,一次通过@Configurable方面),结果是每个@Scheduled方法被调用两次。

@Async

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

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

与使用@Scheduled注解的方法不同,这些方法可能需要参数,因为它们是由调用方在运行时以“正常”方式调用的,而不是从容器管理的计划任务中调用。

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

即使是有返回值的方法也可以异步调用。但是,这些方法需要是未来类型化的返回值。这仍然提供了异步执行的好处,因此调用者可以在以后调用get()之前执行其他任务(即可以先去做其他的,需要返回值的时候再调用get方法)。

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

@Async不仅可以应用在返回值是 java.util.concurrent.Future的方法上,还可以org.springframework.util.concurrent.ListenableFuture。从Spring4.2,JDK8开始,提供了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,并结合自定义切入点。

@Async上的Executor

默认情况下,在方法上指定@Async时,使用的执行器是启用异步支持时配置的执行器。但是,如果需要指示在执行给定方法时应使用默认执行器,则可以使用@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类型的返回值时,很容易管理在方法执行期间引发的异常,因为在对未来结果调用get时会引发此异常。但是,对于void返回类型,异常是未捕获的,无法传输。你可以提供AsyncUncaughtExceptionHandler来处理此类异常。 

public class MyAsyncUncaughtExceptionHandler implements AsyncUncaughtExceptionHandler {

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

默认情况下,只记录异常。可以使用AsyncConfigurer或<task:annotation-driven/>元素。

task 命名空间

从3.0版开始,Spring包含一个XML命名空间,用于配置TaskExecutor和TaskScheduler实例。它还提供了一种方便的方法来配置使用触发器调度的任务。

<task:scheduler/>

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

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

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

<task:executor/>

下面将创建一个ThreadPoolTaskExecutor实例:

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

与上一节中显示的调度程序一样,为id属性提供的值用作池中线程名称的前缀。就池大小而言,executor元素比scheduler元素支持更多的配置选项。首先,ThreadPoolTaskExecutor的线程池本身是可配置的。执行器的线程池可以有不同的核心值和最大值,而不是单一大小。如果提供单个值,则executor有一个固定大小的线程池(核心和最大大小相同)。但是,executor元素的pool size属性也接受min-max形式的范围。下面的示例将最小值设置为5,最大值设置为25:

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

在前面的配置中,还提供了队列容量值。线程池的配置也应该根据执行器的队列容量来考虑。其主要思想是,当任务被提交时,如果当前活动线程的数量小于核心大小,则执行器首先尝试使用空闲线程。如果已达到核心大小,则只要尚未达到其容量,任务就会添加到队列中。只有这样,如果队列的容量已经达到,执行器才会创建一个超出核心大小的新线程。如果已达到最大大小,则执行器将拒绝该任务。

默认情况下,队列是无边界的,但这很少是所需的配置,因为如果在所有池线程都很忙的情况下向该队列添加足够的任务,则会导致OutOfMemoryErrors。此外,如果队列是无界的,则最大大小根本不起作用。由于执行器总是在创建超出核心大小的新线程之前尝试队列,因此队列必须具有有限的容量,以便线程池增长到超过核心大小(这就是为什么在使用无界队列时,固定大小的池是唯一合理的情况)。

考虑一下当任务被拒绝时的情况。默认情况下,当任务被拒绝时,线程池执行器将抛出TaskRejectedException。然而,拒绝策略实际上是可配置的。当使用默认拒绝策略(AbortPolicy实现)时,将引发异常。对于某些任务可以在负载下跳过的应用程序,可以改为配置DiscardPolicy或DiscardOldestPolicy。对于需要在高负载下限制已提交任务的应用程序,另一个选项是CallerRunsPolicy。该策略不会引发异常或丢弃任务,而是强制调用submit方法的线程运行任务本身。其思想是这样的一个调用者在运行该任务时很忙,不能立即提交其他任务。因此,它提供了一种简单的方法来限制传入的负载,同时保持线程池和队列的限制。通常,这允许执行器“追赶”它正在处理的任务,从而释放队列、池中或两者的一些容量。您可以从executor元素的“拒绝策略”属性的可用值枚举中选择这些选项中的任何一个。

下面的示例显示了一个executor元素,该元素具有多个用于指定各种行为的属性:

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

最后,keep alive设置确定线程在被终止之前可以保持空闲的时间限制(以秒为单位)。如果当前池中的线程数超过了核心数,那么在等待这段时间之后,没有处理任务,多余的线程将被终止。时间值为零会导致多余的线程在执行任务后立即终止,而不会在任务队列中保留后续工作。以下示例将“保持活动”值设置为两分钟:

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

Spring任务名称空间最强大的特性是支持在Spring应用程序上下文中配置任务。基本上,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"/>

调度程序由外部元素引用,每个单独的任务都包含其触发器元数据的配置。在前面的示例中,该元数据定义了一个周期性触发器,该触发器具有固定的延迟,指示每个任务执行完成后要等待的毫秒数。另一个选项是fixed rate,它指示不管以前的执行需要多长时间,都应该执行该方法的频率。此外,对于固定延迟和固定速率任务,您可以指定“初始延迟”参数,该参数指示在首次执行该方法之前要等待的毫秒数。为了获得更多的控制,可以改为提供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使用Trigger、Job和JobDetail对象来实现各种作业的调度。为了方便起见,Spring提供了两个类,它们简化了在基于Spring的应用程序中使用Quartz。

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

作业详细信息配置包含运行作业所需的所有信息(例如作业)。超时在作业数据映射中指定。作业数据映射通过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
    }

}

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

使用MethodInvokingJobDetailFactoryBean

通常您只需要调用特定对象上的方法。通过使用方法invokingJobDetailFactoryBean。

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

默认情况下,Quartz作业是无状态的,这会导致作业相互干扰。如果为同一JobDetail指定两个触发器,则在第一个作业完成之前,第二个作业可能会启动。如果JobDetail类实现有状态接口,则不会发生这种情况。第二项工作在第一项工作完成之前不会开始。要使由方法invokingJobDetailFactoryBean生成的作业成为非并发的,请将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>

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

使用触发器和SchedulerFactoryBean连接作业

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的属性等。

原文地址:https://www.cnblogs.com/myitnews/p/13302335.html