Spring任务执行和调度(TaskExecutor,TaskSchedule)

1.介绍

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

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

2.Spring TaskExecutor抽象化

Executors 是线程池概念的JDK名称。  The "executor"命名是由于无法保证底层实现实际上是一个池;an executor可以是单线程甚至是同步的。 Spring的抽象隐藏了Java SE和Java EE环境之间的实现细节。

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

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

2.1  TaskExecutor类型

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

SimpleAsyncTaskExecutor  此实现不重用任何线程,而是为每次调用启动一个新线程。但是,它确实支持并发限制,该限制将阻止超出限制的任何调用,直到释放插槽为止。如果您正在寻找真正的池,请参阅下面的SimpleThreadPoolTask​​Executor和ThreadPoolTask​​Executor的讨论。
SyncTaskExecutor 此实现不会异步执行调用。相反,每次调用都在调用线程中进行。它主要用于不需要多线程的情况,例如简单的测试用例。
ConcurrentTaskExecutor  此实现是java.util.concurrent.Executor对象的适配器。还有一个替代方案ThreadPoolTask​​Executor,它将Executor配置参数公开为bean属性。很少需要使用ConcurrentTaskExecutor,但如果ThreadPoolTask​​Executor不够灵活,无法满足您的需求,则ConcurrentTaskExecutor 是另一种选择。
SimpleThreadPoolTask​​Executor  这个实现实际上是Quartz的SimpleThreadPool的子类,它监听Spring的生命周期回调。 当您有一个可能需要由Quartz和非Quartz组件共享的线程池时,通常会使用此方法。

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

WorkManagerTaskExecutor   CommonJ是BEA和IBM共同开发的一组规范。这些规范不是Java EE标准,而是BEA和IBM的Application Server实现的标准。 此实现使用CommonJ WorkManager作为其后备实现,并且是在Spring上下文中设置CommonJ WorkManager引用的中心便利类。与SimpleThreadPoolTask​​Executor类似,此类实现WorkManager接口,因此也可以直接用作WorkManager。

2.2 使用 TaskExecutor

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

如您所见,您不是从池中检索线程并自行执行,而是将Runnable添加到队列中,TaskExecutor使用其内部规则来决定何时执行任务。 要配置TaskExecutor将使用的规则,已公开简单的bean属性。

3  Spring 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);

}

除了TaskExecutor抽象之外,Spring 3.0还引入了一个TaskScheduler,它具有各种方法,可以在将来的某个时刻调度任务。
最简单的方法是名为'schedule'的方法,它只采用Runnable和Date。这将导致任务在指定时间后运行一次。所有其他方法都能够安排任务重复运行。固定速率和固定延迟方法用于简单的定期执行,但接受触发的方法更灵活。

3.1  触发接口(Trigger)

Trigger接口基本上受到JSR-236的启发,从Spring 3.0开始,它还没有正式实现。触发器的基本思想是可以基于过去的执行结果或甚至任意条件来确定执行时间。如果这些确定确实考虑了前面执行的结果,则该信息在TriggerContext中可用。 Trigger接口本身非常简单:
public interface Trigger {

    Date nextExecutionTime(TriggerContext triggerContext);

}

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

public interface TriggerContext {

    Date lastScheduledExecutionTime();

    Date lastActualExecutionTime();

    Date lastCompletionTime();

}

3.2 Trigger实现类

Spring提供了两个Trigger接口的实现。最有趣的是CronTrigger。它支持基于cron表达式调度任务。例如,以下任务计划在每小时过去15分钟后运行,但仅在工作日的9到5个“营业时间”运行。
scheduler.schedule(task, new CronTrigger("0 15 9-17 * * MON-FRI"));

* 
    *
  • "0 0 * * * *" = the top of every hour of every day.
  • *
  • "*/10 * * * * *" = every ten seconds.
  • *
  • "0 0 8-10 * * *" = 8, 9 and 10 o'clock of every day.
  • *
  • "0 0 6,19 * * *" = 6:00 AM and 7:00 PM every day.
  • *
  • "0 0/30 8-10 * * *" = 8:00, 8:30, 9:00, 9:30, 10:00 and 10:30 every day.
  • *
  • "0 0 9-17 * * MON-FRI" = on the hour nine-to-five weekdays
  • *
  • "0 0 0 25 12 ?" = every Christmas Day at midnight
  • *

https://www.cnblogs.com/javahr/p/8318728.html(cron表达式详解)

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

3.3  TaskSchedule实现类

与Spring的TaskExecutor抽象一样,TaskScheduler的主要好处是依赖于调度行为的代码不需要耦合到特定的调度程序实现。

在Application Server环境中运行时,它提供的灵活性尤为重要,因为应用程序本身不应直接创建线程。对于这种情况,Spring提供了一个TimerManagerTaskScheduler,它委托给CommonJ TimerManager实例,通常配置有JNDI查找。

只要不需要外部线程管理,就可以使用更简单的替代方案ThreadPoolTask​​Scheduler。在内部,它委托给ScheduledExecutorService实例。 ThreadPoolTask​​Scheduler实际上也实现了Spring的TaskExecutor接口,因此单个实​​例可以尽快用于异步执行以及计划和可能重复执行。

4.调度和异步执行的注释支持

   4.1Enable scheduling annotations(启动 scheduling注解)

要启用对@Scheduled和@Async注释的支持,请将@EnableScheduling和@EnableAsync添加到您的@Configuration类之一:
@Configuration
@EnableAsync
@EnableScheduling
public class AppConfig {
}
您可以自由选择适合您应用的相关注释。例如,如果您只需要支持@Scheduled,则只需省略@EnableAsync
。对于更细粒度的控制,您还可以实现SchedulingConfigurer和/或AsyncConfigurer接口。有关完整详细信息,请参阅javadocs。

如果您更喜欢XML配置,请使用该元素。

 executor="myExecutor" scheduler="myScheduler"/>
 id="myExecutor" pool-size="5"/>
 id="myScheduler" pool-size="10"/>

请注意,上述XML提供了一个执行程序引用,用于处理与带有@Async注释的方法相对应的任务,并提供了调度程序引用来管理使用@Scheduled注释的方法。

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

    4.2 @Scheduled注解

(标记要安排的方法的注释。, 必须指定* {@link #cron()},{@ link #fixedDelay()}或{@link #fixedRate()} *属性中的一个。 , * *

带注释的方法必须没有参数。, 它通常会有* {@code void}返回类型;, 如果不是,则通过调度程序调用时将忽略返回的值*。 , * *

{@code @Scheduled}注释的处理是通过*注册{@link ScheduledAnnotationBeanPostProcessor}来执行的。, 这可以通过{@code } *元素或@ {@Link EnableScheduling}注释手动完成,也可以更方便地完成。 , * *

此注释可用作元注释,以创建具有属性覆盖的自定义* 组合注释。)

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

@Scheduled(fixedDelay=5000)/*固定延迟**/
/**在最后一次调用的结束和下一次调用的开始之间以固定的周期(以毫秒为单位)执行带注释的方法。 @return延迟,以毫秒为单位**/
 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表达式的时区。

如果简单的周期性调度不够表达,则可以提供cron表达式。例如,以下内容仅在工作日执行。请注意,要调度的方法必须具有void返回值,并且不得指望任何参数。如果该方法需要与Application Context中的其他对象进行交互,则通常会通过依赖注入提供这些对象。

从Spring Framework 4.3开始,任何范围的bean都支持@Scheduled方法。 确保您没有在运行时初始化同一@Scheduled注释类的多个实例
,除非您确实要为每个此类实例安排回调。与此相关,请确保不对使用@Scheduled注释的bean类使用@Configurable,
并将其注册为带有容器的常规Spring bean:否则将获得双初始化,一次通过容器,一次通过@Configurable方面,每个@

Scheduled方法的结果被调用两次。

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

@Async
Future<String> returnSomething(int i) {
    // this will be executed asynchronously
}
@Async方法不仅可以申报一个普通java.util.concurrent.Future返回类型,也可以是Spring的org.springframework.util.concurrent.
ListenableFuture,或如spring4.2,JDK 8的java.util.concurrent.

CompletableFuture的:更丰富的互动使用异步任务并通过进一步的处理步骤立即组合。

@Async不能与生命周期回调一起使用,例如@PostConstruct。要异步初始化Spring bean,您当前必须使用单独的初始化Spring bean,然后在目标上调用@Async带注释的方法。

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

   4.4使用@Async执行者资格

默认情况下,在方法上指定@Async时,将使用的执行程序是提供给“注释驱动”元素的执行程序,如上所述。但是,当需要指示在执行给定方法时应使用非默认执行程序时,可以使用@Async批注的value属性。
@Async("otherExecutor")
void doSomething(String s) {
    // this will be executed asynchronously by "otherExecutor"
}

在这种情况下,‘’otherExecutor”可以是Spring容器中任何Executor bean的名称,也可以是与任何Executor关联的限定符的名称,例如:与元素或Spring的@Qualifier注释一起指定。

   4.5使用@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 XML元素定义自定义AsyncUncaughtExceptionHandler。

5.任务命名空间

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

   5.1 scheduler元素

以下元素将创建具有指定线程池大小的ThreadPoolTask​​Scheduler实例。
 id="scheduler" pool-size="10"/>

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

   5.2  executor元素

 id="executor" pool-size="10"/>

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


        id="executorWithPoolSizeRange"
        pool-size="5-25"
        queue-capacity="100"/>

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

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

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


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

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


        id="executorWithKeepAlive"
        pool-size="5-25"
        keep-alive="120"/>

   5.3 scheduled-tasks元素

Spring的任务命名空间最强大的功能是支持在Spring Application Context中配置要安排的任务。这遵循类似于Spring中的其他“方法调用者”的方法,例如由JMS名称空间提供的用于配置消息驱动的POJO的方法。基本上,“ref”属性可以指向任何Spring管理的对象,“method”属性提供要在该对象上调用的方法的名称。这是一个简单的例子。

 scheduler="myScheduler">
     ref="beanA" method="methodA" fixed-delay="5000"/>


 id="myScheduler" pool-size="10"/>
如您所见,调度程序由外部元素引用,每个单独的任务都包含其触发器元数据的配置。在前面的示例中,该元数据定义了具有固定延迟的周期性触发器,该延迟指示在每个任务执行完成后等待的毫秒数。另一个选项是“固定速率”,表示无论先前执行多长时间,该方法应执行的频率。另外,对于固定延迟和固定速率任务,可以指定“初始延迟”参数,该参数指示在第一次执行该方法之前等待的毫秒数。为了更多控制,可以提供“cron”属性。以下是演示这些其他选项的示例。
 scheduler="myScheduler">
     ref="beanA" method="methodA" fixed-delay="5000" initial-delay="1000"/>
     ref="beanB" method="methodB" fixed-rate="5000"/>
     ref="beanC" method="methodC" cron="*/5 * * * * MON-FRI"/>


 id="myScheduler" pool-size="10"/>
6. 使用Quartz SchedulerQuartz使用Trigger,Job和JobDetail对象来实现各种作业的调度。有关Quartz背后的基本概念,请查看http://
quartz-scheduler.org。为方便起见,Spring提供了几个类,简化了基于Spring的应用程序中Quartz的使用。

   6.1 使用JobDetailFactoryBean

Quartz JobDetail对象包含运行作业所需的所有信息。 Spring提供了一个JobDetailFactoryBean,它为XML配置提供了bean样式的属性。我们来看一个例子:
 name="exampleJob" class="org.springframework.scheduling.quartz.JobDetailFactoryBean">
     name="jobClass" value="example.ExampleJob"/>
     name="jobDataAsMap">
        
             key="timeout" value="5"/>
        
    
job详细信息配置包含运行job所需的所有信息(ExampleJob)。超时在job data映射中指定。job data映射可通过JobExecutionContext(在执行时传递给您)获得,但JobDetail也从映射到job实例属性的job data中获取其属性。因此,在这种情况下,如果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)。

    6.2 使用MethodInvokingJobDetailFactoryBean

通常,您只需要在特定对象上调用方法。使用MethodInvokingJobDetailFactoryBean可以完成以下操作:
 id="jobDetail" class="org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean">
     name="targetObject" ref="exampleBusinessObject"/>
     name="targetMethod" value="doIt"/>

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

public class ExampleBusinessObject {

    // properties and collaborators

    public void doIt() {
        // do the actual work
    }
}

 id="exampleBusinessObject" class="examples.ExampleBusinessObject"/>
使用MethodInvokingJobDetailFactoryBean,您不需要创建仅调用方法的单行作业,而只需创建实际的业务对象并连接详细信息对象。
默认情况下,Quartz Jobs是无状态的,导致作业相互干扰的可能性。 如果为同一JobDetail指定两个触发器,则可能在第一个作业完成之前,第二个作业将启动。 如果JobDetail类实现Stateful接口,则不会发生这种情况。 第二个作业在第一个作业完成之前不会开始。 要使MethodInvokingJobDetailFactoryBean产生的作业非并发,请将concurrent标志设置为false。

 id="jobDetail" class="org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean">
     name="targetObject" ref="exampleBusinessObject"/>
     name="targetMethod" value="doIt"/>
     name="concurrent" value="false"/>
默认情况下,作业将以并发方式运行。

   6.3   使用触发器和SchedulerFactoryBean连接jobs

我们已经创建了工作细节和jobs。 我们还回顾了允许您在特定对象上调用方法的便捷bean。 当然,我们仍然需要自己安排工作。 这是使用触发器和SchedulerFactoryBean完成的。 Quartz中提供了几个触发器,Spring提供了两个带有方便默认值的Quartz FactoryBean实现:CronTriggerFactoryBean和SimpleTriggerFactoryBean。
需要安排触发器。 Spring提供了一个SchedulerFactoryBean,它公开了要设置为属性的触发器。 SchedulerFactoryBean使用这些触发器调度实际jobs

 id="simpleTrigger" class="org.springframework.scheduling.quartz.SimpleTriggerFactoryBean">
    
     name="jobDetail" ref="jobDetail"/>
    
     name="startDelay" value="10000"/>
    
     name="repeatInterval" value="50000"/>


 id="cronTrigger" class="org.springframework.scheduling.quartz.CronTriggerFactoryBean">
     name="jobDetail" ref="exampleJob"/>
    
     name="cronExpression" value="0 0 6 * * ?"/>
现在我们设置了两个触发器,一个每50秒运行一次,启动延迟为10秒,每天早上6点运行一次。 要完成所有操作,我们需要设置SchedulerFactoryBean:
 class="org.springframework.scheduling.quartz.SchedulerFactoryBean">
     name="triggers">
        
             bean="cronTrigger"/>
             bean="simpleTrigger"/>
        
    
您可以为SchedulerFactoryBean设置更多属性,例如作业详细信息使用的日历,自定义Quartz的属性等。有关详细信息,请查看SchedulerFactoryBean javadocs。




你可能感兴趣的:(spring)