编辑推荐: |
本文来自于csdn,本文主要介绍Spring框架在异步执行和任务调度方面的相关内容,希望对大家能有所帮助。 |
|
1 概述
Spring框架分别使用TaskExecutor和TaskScheduler接口提供异步执行和任务调度的抽象。Spring还提供了这些接口的实现,这些接口支持线程池或将其委托给应用服务器环境中的CommonJ。
2 TaskExecutor
Spring 2.0 开始引入的新的抽像。Executors 是线程池的Java 5名称。之所以称作是“执行器”是因为不能保证底层实现实际上是一个池;执行程序可以是单线程的,甚至是同步的。Spring的TaskExecutor接口与java.util.concurrent是等价的。
2.1 TaskExecutor类型
SimpleAsyncTaskExecutor
线程不会重用,每次调用开启一个新的线程。支持并发,超过最大并发调用数时,会阻塞,直到释放一个槽为止。
SyncTaskExecutor
不会异步执行调用。每次调用都发生在调用线程中。它主要用于不需要多线程的情况。
ConcurrentTaskExecutor
Java 5 Java .util.concurrent. executor的包装。替代方案是ThreadPoolTaskExecutor,它将Executor配置参数作为bean属性公开。很少使用。
SimpleThreadPoolTaskExecutor
Quartz的SimpleThreadPool的一个子类,它监听Spring的生命周期回调。Quartz组件和非Quartz组件共享需要共享一个线程池时,通常会使用这种方法。
ThreadPoolTaskExecutor
只能在java5中使用。公开了用于配置java.util.concurrent的bean属性。如果需要高级的东西,比如ScheduledThreadPoolExecutor,建议使用ConcurrentTaskExecutor替代。
TimerTaskExecutor
通过TimerTask支撑实现。 不同于SyncTaskExecutor,因为方法调用在一个单独的线程中执行,尽管它们在那个线程中是同步的。
WorkManagerTaskExecutor
使用CommonJ WorkManager作为它的支持实现,并且是在Spring上下文中设置CommonJ
WorkManager引用的中心便利类。与SimpleThreadPoolTaskExecutor类似,这个类实现了WorkManager接口,因此也可以直接作为WorkManager使用。
2.2 使用 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 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="cn.pconline.activity.task.TaskExecutorExample"
init-method="printMessages">
<constructor-arg ref="taskExecutor"
/>
</bean> |
3 TaskScheduler
除了任务执行者抽象之外。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”的方法,它只接受可运行的日期。这将导致任务在指定时间后运行一次。
所有其他方法都能够安排任务重复运行。固定速率和固定延迟方法用于简单的、周期性的执行,但是使用 Trigger
的方法要灵活得多。
3.1 Trigger
触发器的基本思想是,执行时间可以根据过去的执行结果甚至任意条件来确定。如果这些决定确实考虑了前面执行的结果,那么该信息在TriggerContext中是可用的。
public interface
Trigger { Date nextExecutionTime(TriggerContext
triggerContext); } |
public interface
TriggerContext {
Date lastScheduledExecutionTime();
Date lastActualExecutionTime();
Date lastCompletionTime();
} |
TriggerContext是最重要的部分。它封装了所有相关的数据,如果有必要,将来还可以进行扩展。TriggerContext是一个接口(默认情况下使用SimpleTriggerContext实现)。
3.2 Trigger 实现
Spring提供了触发器接口的两个实现。最有趣的是CronTrigger。它支持基于cron表达式的任务调度。
scheduler.schedule(task,
new CronTrigger("* 15 9-17 * * MON-FRI")); |
另一个开箱即用的实现是一个周期性Trigger ,它接受一个固定的周期、一个可选的初始延迟值,以及一个布尔值,用来指示周期应该解释为固定速率还是固定延迟。由于TaskScheduler接口已经定义了以固定速率或固定延迟调度任务的方法,因此应该尽可能直接使用这些方法。PeriodicTrigger实现的价值在于它可以在依赖于触发器抽象的组件中使用。例如,允许周期性Trigger
、基于cro的Trigger ,甚至自定义Trigger 实现可以互换使用,这可能很方便。这样的组件可以利用依赖注入,这样就可以在外部配置这样的Trigger
。
3.3 TaskScheduler实现
在应用服务器环境中,TaskScheduler提供的灵活性尤其重要。因为在这种环境中,线程不应该由应用程序本身直接创建。对于这种情况,Spring提供了一个TimerManagerTaskScheduler,它将委托给CommonJ
TimerManager实例,通常配置为JNDI-lookup。
4 调度和异步执行的注解支持
4.1 开启scheduling 注解功能
为了支持@Scheduled和@Async注释,请将@EnableScheduling和@EnableAsync添加到@Configuration类中
@Configuration
@EnableAsync
@EnableSCheduling
public class AppConfig {
} |
你可以自由选择应用程序的相关注释。例如,如果只需要支持@Scheduled,那么只需省略**@EnableAsync即可。对于更细粒度的控制,可以另外实现调度器和/或AsyncConfigurer**接口。
如果你更喜欢xml配置,这样配置。
<task:annotation-driven
executor="myExecutor" scheduler="myScheduler"/>
<task:executor id="myExecutor" pool-size="5"/>
<task:scheduler id="myScheduler"
pool-size="10"/>} |
4.2 @Scheduled
@Scheduled添加到方法上
//上一次调用完之后,五秒再调用一次,依此循环下去
@Scheduled(fixedDelay=5000)
public void doSomething() {
// something that should execute periodically
}
//连续的每次调用开始时间之间,间隔5s
@Scheduled(fixedRate=5000)
public void doSomething() {
// something that should execute periodically
}
//
@Scheduled(cron="*/5 * * * * MON-FRI")
public void doSomething() {
// something that should execute on weekdays only
} |
值得注意的是调度的方法返回值必须是void,并且不能期望有任何参数。如果方法需要与来自应用程序上下文的其他对象交互,那么这些对象通常是通过依赖注入提供的。
4.3 @Async注解
添加了**@Async**注解的方法会异步执行。换句话说,方法调用后会立即返回,方法的实际执行将发生在提交给Spring
TaskExecutor的任务中。
@Async
void doSomething() {
// this will be executed asynchronously
} |
/@Scheduled注释的方法不同,这些方法可以预期参数.
//因为调用方将在运行时以“正常”方式调用它们,而不是从容器管理的调度任务中调用。
@Async
void doSomething(String s) {
// this will be executed asynchronously
} |
// 具有Future回调返回值
//执行其它任务的优先级 依然是高于执行回调的优先级。
@Async
Future<String> returnSomething(int i) {
// this will be executed asynchronously
} |
4.4 指定@Async注解的执行器
默认情况下,在方法上指定@Async时,将使用的执行器是提供“annotation-driven”元素的执行器,如上所述。然而,当需要指示在执行给定方法时应该使用非默认的执行器时,可以使用**@Async**注释的值属性。
@Async("otherExecutor")
void doSomething(String s) {
// this will be executed asynchronously by "otherExecutor"
} |
5 Task 命名空间
从Spring 3.0开始,有一个用于配置TaskExecutor和TaskScheduler实例的XML名称空间。并提供了一种方便的方法,可以将任务配置为使用触发器进行调度。
5.1 scheduler 元素
<!-- 默认创建一个指定大小的ThreadPoolTaskScheduler
-->
<task:scheduler id="scheduler" pool-size="10"/> |
id属性用作线程池中线程的前缀名。如果不指定pool-size,默认的线程池中只有一个线程。
5.2 executor元素
<!-- 创建ThreadPoolTaskExecutor实例
-->
<task:executor id="executor" pool-size="10"/> |
与上面的调度器一样,为’id’属性提供的值将用作池中线程名称的前缀。就池大小而言,'executor’元素比’scheduler’元素支持更多的配置选项。首先,ThreadPoolTaskExecutor的线程池本身是可配置的。执行程序的线程池可能对核心和最大大小有不同的值,而不仅仅是单个大小。如果提供了单个值,那么执行器将拥有一个固定大小的线程池(核心和最大大小相同)。然而,“executor”元素的“池大小”属性也接受“min-max”形式的范围。
<task:executor
id="executorWithPoolSizeRange"
pool-size="5-25"
queue-capacity="100"/><!-- 指定队列的大小--> |
<task:executor
id="executorWithCallerRunsPolicy"
pool-size="5-25"
queue-capacity="100"
rejection-policy="CALLER_RUNS"/><!--
任务拒绝策略(强制任务提交线程执行任务,从而限制传入负载变得过大) 默认的是直接抛出异常--> |
5.3 scheduled-tasks元素
Spring task namespace 最强大的特性是支持在Spring应用程序上下文中配置要调度的任务。这与Spring中的其他“方法调用者”类似,例如JMS名称空间提供的配置消息驱动pojo的方法。
<task:scheduled-tasks
scheduler="myScheduler"> <task:scheduled
ref="beanA" method="methodA"
fixed-delay="5000"/><!--ref
指向容器管理的对象,method是要执行的方法名-->
</task:scheduled-tasks>
<task:scheduler id="myScheduler"
pool-size="10"/> |
<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"/> |
6 Quartz Scheduler
Quartz使用触发器、作业和作业细节对象来实现各种作业的调度。有关Quartz背后的基本概念,请参阅http://quartz-scheduling
er.org。为了方便起见,Spring提供了两个类,它们简化了基于Spring的应用程序中Quartz的使用。
6.1 使用JobDetailBean
JobDetail对象包含运行作业所需的所有信息。Spring框架提供了JobDetailBean,它使JobDetail更接近于具有合理默认值的实际JavaBean。
<bean name="exampleJob"
class="org.springframework.scheduling .quartz.JobDetailBean">
<property name="jobClass" value="example.ExampleJob"
/> <property name="jobDataAsMap">
<map> <entry key="timeout"
value="5" /> </map>
</property>
</bean> |
job detail bean 具有运行作业(ExampleJob)所需的所有信息。timeout在job
data map指定。job data map可以通过JobExecutionContext(在执行时传递给您)获得,但是JobDetailBean还将
job data map 中的属性映射到实际job的属性。因此,在本例中,如果ExampleJob包含一个名为timeout的属性,JobDetailBean将自动应用它。
package example;
public class ExampleJob extends QuartzJobBean
{
private int timeout;
/**
* Setter called after the ExampleJob is instantiated
* with the value from the JobDetailBean (5)
*/
public void setTimeout(int timeout) {
this.timeout = timeout;
}
protected void executeInternal(JobExecutionContext
ctx) throws JobExecutionException {
// do the actual work
}
} |
6.2 使用MethodInvokingJobDetailFactoryBean
使用MethodInvokingJobDetailFactoryBean你可以调用特定对象上的方法。
<bean id="jobDetail"
class="org.springframework.scheduling.quartz .MethodInvokingJobDetailFactoryBean">
<property name="targetObject" ref="exampleBusinessObject"
/> <property name="targetMethod"
value="doIt" />
</bean> |
<bean id="exampleBusinessObject"
class="examples.ExampleBusinessObject"/> |
使用上面的配置将会导致ExampleBusinessObject
.doIt()方法被调用。
public class
ExampleBusinessObject {
// properties and collaborators
public void doIt() {
// do the actual work
}
} |
使用MethodInvokingJobDetailFactoryBean,不再需要创建只调用一个方法的一行作业,只需要创建实际的业务对象并连接到它。
默认情况下,Quartz作业是无状态的,导致作业相互干扰的可能性。如果为相同的JobDetail指定两个触发器,那么可能在第一个作业完成之前,第二个作业就会开始。如果JobDetail类实现有状态接口,则不会发生这种情况。在第一项工作完成之前,第二项工作不会开始。要使方法调用jobdetailfactorybean产生的作业非并发,请将并发标志设置为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"
/> <!--并发执行false-->
</bean> |
6.3 作业调度
尽管我们能使用MethodInvokingJobDetailFactoryBean调用特定对象上的方法,但是我们还是需要调度作业
。这需要使用触发器和scheduler erfactorybean完成。Quartz 提供了多种触发器
Spring提供了两种Quartz 工厂对象:
CronTriggerFactoryBean
SimpleTriggerFactoryBean
两种触发器的示例
<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> |
关于SchedulerFactoryBean的等多属性设置 ,参考SchedulerFactoryBean
javadoc |