转载请注明https://zhangzhaoyu.github.io/2016/09/30/spring-task-and-schedule-deep-research/

摘要

在很多业务场景中,系统都需要用到任务调度系统。例如定期地清理Redis 缓存,周期性地检索某一条件并更新系统的资源等。在现代的应用系统中,快速地响应用户的请求,是用户体验最主要的因素之一。因此在Web 系统中异步地执行任务,也会在很多场景中经常涉及到。本文对任务调度和异步执行的Java 实现进行了总结,主要讲述一下内容:

  • Java 对异步执行和任务调度的支持
  • Spring 4.X 的异步执行和任务调度实现

Java 对异步执行和任务调度的支持

异步执行和任务调度底层的语言支撑都是Java 的多线程技术。线程是系统进行独立运行和调度的基本单位。拥有了多线程,系统就拥有了同时处理多项任务的能力。

Java 实现异步调用

在Java 中要实现多线程有实现Runnable 接口和扩展Thread 类两种方式。只要将需要异步执行的任务放在run() 方法中,在主线程中启动要执行任务的子线程就可以实现任务的异步执行。如果需要实现基于时间点触发的任务调度,就需要在子线程中循环的检查系统当前的时间跟触发条件是否一致,然后触发任务的执行。该内容属于Java 多线程的基础知识,此处略过不讲。

Java Timer 和 TimeTask 实现任务调度

为了便于开发者快速地实现任务调度,JavaJDK 对任务调度的功能进行了封装,实现了Timer 和TimerTask 两个工具类。

由上图,我们可以看出TimeTask 抽象类在实现Runnable 接口的基础上增加了任务cancel() 和任务scheduledExecuttionTime() 两个方法。

上图为调度类Timer 的实现。从Timer类的源码,可以看到其采用TaskQueue 来实现对多个TimeTask 的管理。TimerThread 集成自Thread 类,其mainLoop() 用来对任务进行调度。而Timer 类提供了四种重载的schedule() 方法和重载了两种sheduleAtFixedRate() 方法来实现几种基本的任务调度类型。下面的代码是采用Timer 实现的定时系统时间打印程序。

public class PrintTimeTask extends TimerTask {
@Override
public void run() {
System.out.println(new Date().toString());
} public static void main(String[] args) {
Timer timer = new Timer("hello");
timer.schedule(new PrintTimeTask(), 1000L, 2000L);
}
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

Spring 4.x 中的异步执行和任务调度

Spring 4.x 中的异步执行

spring 作为一站式框架,为开发者提供了异步执行和任务调度的抽象接口TaskExecutor 和TaskScheduler。Spring 对这些接口的实现类支持线程池(Thread Pool) 和代理。 
Spring 提供了对JDK 中Timer和开源的流行任务调度框架Quartz的支持。Spring 通过将关联的Schedule 转化为FactoryBean 来实现。通过Spring 调度框架,开发者可以快速地通过MethodInvokingFactoryBean 来实现将POJO 类的方法转化为任务。

Spring TaskExecutor

TaskExecutor 接口扩展自java.util.concurrent.Executor 接口。TaskExecutor 被创建来为其他组件提供线程池调用的抽象。

ThreadPoolTaskExecutor 是TaskExecutor 的最主要实现类之一。该类的核心继承关系如下图所示。 

ThreadPoolTaskExecutor 接口扩展了重多的接口,让其具备了更多的能力。要实现异步需要标注@Async 注解:

  • AsyncTaskExecutor 增加了返回结果为Future 的submit() 方法,该方法的参数为Callable 接口。相比Runnable 接口,多了将执行结果返回的功能。
  • AsyncListenableTaskExecutor 接口允许返回拥有回调功能的ListenableFuture 接口,这样在结果执行完毕是,能够直接回调处理。
public class ListenableTask {
@Async
public ListenableFuture<Integer> compute(int n) {
int sum = 0;
for (int i = 0; i < n; i++) {
sum += i;
}
return new AsyncResult<>(sum);
} static class CallBackImpl implements
ListenableFutureCallback<Integer> {
@Override
public void onFailure(Throwable ex) {
System.out.println(ex.getMessage());
} @Override
public void onSuccess(Integer result) {
System.out.println(result);
}
} public static void main(String[] args) {
ListenableTask listenableTask = new ListenableTask();
ListenableFuture<Integer> listenableFuture =
listenableTask.compute(10);
listenableFuture.addCallback(new CallBackImpl());
}
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • ThreadFactory 定义了创建线程的工厂方法,可以扩展该方法实现对Thread 的改造。

基于Java Config

  • 基于注解 当采用基于Java Config 注解配置时,只需要在主配置添加@EnableAsync 注解,Spring 会自动的创建基于ThreadPoolTaskExecutor 实例注入到上下文中。
@Configuration
@EnableAsync
public class AppConfig {
}
  • 1
  • 2
  • 3
  • 4
  • 1
  • 2
  • 3
  • 4
  • 基于AsyncConfigurer接口自定义 开发者可以自定义Executor 的类型,并且注册异常处理器。
@Configuration
public class TaskConfig implements AsyncConfigurer {
@Override
public Executor getAsyncExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setMaxPoolSize(100);
executor.setCorePoolSize(10);
return executor;
} @Override
public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
return new AsyncUncaughtExceptionHandler() {
@Override
public void handleUncaughtException(Throwable ex,
Method method, Object... params) {
System.out.println(ex.getMessage());
}
};
}
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21

基于XML Config

  • 基于传统XML的配置 基于XML 的形式,采用传统的Java Bean的形式配置ThreadPoolTaskExecutor。然后采用自动注入(autowire, resource,name)的可以直接在Spring Component 中注入Executor。以编程的形式实现异步任务。
<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>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 基于task 命名空间的配置 Spring 为任务的执行提供了便利的task 命名空间。当采用基于XML 配置时Spring 会自动地为开发者创建Executor。同时可以在annotation-driven 标签上注册实现了AsyncUncaughtExceptionHandler 接口的异常处理器。
<!-- config exception handler  -->
<bean id="taskAsyncExceptionHandler" class="org.zzy.spring4.application.schedulie.TaskAsyncExceptionHandler"/>
<task:annotation-driven exception-handler="taskAsyncExceptionHandler" scheduler="scheduler" executor="executor"/>
  • 1
  • 2
  • 3
  • 1
  • 2
  • 3

异步执行的异常处理

除了上文提到的两种异常处理方式,Spring 还提供了基于SimpleApplicationEventMulticaster 类的异常处理方式。

@Bean
public SimpleApplicationEventMulticaster eventMulticaster(TaskExecutor taskExecutor) {
SimpleApplicationEventMulticaster eventMulticaster = new SimpleApplicationEventMulticaster();
eventMulticaster.setTaskExecutor(taskExecutor);
eventMulticaster.setErrorHandler(new ErrorHandler() {
@Override
public void handleError(Throwable t) {
System.out.println(t.getMessage());
}
});
return eventMulticaster;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

Spring 4.x 中任务调度实现

Spring 的任务调度主要基于TaskScheduler 接口。ThreadPoolTaskScheduler 是Spring 任务调度的核心实现类。该类提供了大量的重载方法进行任务调度。Trigger 定义了任务被执行的触发条件。Spring 提供了基于Corn表达式的CornTrigger实现。TaskScheduler 如下图所示。 

实现TaskScheduler 接口的ThreadPoolTaskExecutor 继承关系。 

基于Java Config

  • 基于注解的配置 当采用基于Java Config 注解配置时,只需要在主配置添加@EnableScheduling 注解,Spring 会自动的创建基于ThreadPoolTaskExecutor 实例注入到上下文中。
@Configuration
@EnableScheduling
public class AppConfig {
}
  • 1
  • 2
  • 3
  • 4
  • 1
  • 2
  • 3
  • 4
  • 基于SchedulingConfigurer接口自定义
@Configuration
public class ScheduleConfig implements SchedulingConfigurer {
@Override
public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
taskRegistrar.setTaskScheduler(new ThreadPoolTaskScheduler());
taskRegistrar.getScheduler().schedule(new Runnable() {
@Override
public void run() {
System.out.println("hello");
}
}, new CronTrigger("0 15 9-17 * * MON-FRI"));
}
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

基于XML Config

<task:annotation-driven scheduler="myScheduler"/>
<task:scheduler id="myScheduler" pool-size="10"/>
  • 1
  • 2
  • 1
  • 2

@Scheduled 注解的使用

当某个Bean 由Spring 管理生命周期时,就可以方便的使用@Shcheduled 注解将该Bean 的方法准换为基于任务调度的策略。

@Scheduled(initialDelay=1000, 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
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

task 命名空间中的task:scheduled-tasks

该元素能够实现快速地将一个普通Bean 的方法转换为Scheduled 任务的途径。具体如下:

<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"/>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

总结

本文着重介绍了JDK 为任务调度提供的基础类Timer。并在此基础上详细介绍了Spring 4.x 的异步执行和任务调度的底层接口设计。并针对常用的模式进行了讲解,并附带了源代码。第三方开源的Quartz 实现了更为强大的任务调度系统,Spring 也对集成Quartz 提供了转换。之后会择机再详细的介绍Quartz 的应用和设计原理。同时,Servlet 3.x 为Web 的异步调用提供了AsyncContext,对基于Web 的异步调用提供了原生的支持,后续的文章也会对此有相应的介绍。

参考引用

  1. Spring Doc Task and Schedule
  2. Quartz
  3. Corn Wiki
  4. Servlet AsyncContext

Spring 4.x Task 和 Schedule 概述(代java配置)的更多相关文章

  1. 从java的开始,java概述,java配置环境变量

    一.java开发入门 java 概述   Java划分为三个技术平台:JavaSE(标准版,含Java基础类库),JavaEE(企业版,技术平台),JavaME(小型版,小型产品.嵌入式设备) Jav ...

  2. Spring任务调度器之Task的使用

    Spring Task提供两种方式进行配置,正如大家所想吧,还是一种是annotation(标注),而另外一种就是XML配置了.但其实这里我觉得比较尴尬,因为任务调度这样的需求,通常改动都是比较多的, ...

  3. Spring表达式语言 之 5.1 概述 5.2 SpEL基础(拾叁)

    5.1  概述 5.1.1  概述 Spring表达式语言全称为"Spring Expression Language",缩写为"SpEL",类似于Struts ...

  4. 开涛spring3(5.1&5.2) - Spring表达式语言 之 5.1 概述 5.2 SpEL基础

    5.1  概述 5.1.1  概述 Spring表达式语言全称为“Spring Expression Language”,缩写为“SpEL”,类似于Struts2x中使用的OGNL表达式语言,能在运行 ...

  5. Spring任务调度器之Task的使用(转)

    文章转自 http://blog.csdn.net/l454822901/article/details/51829307 最近发现真的凹凸了,spring升级到3后原来已经自带任务调度器了,之前还一 ...

  6. 跟我学Spring3(9.1):Spring的事务之数据库事务概述

    原文出处: 张开涛 9.1 数据库事务概述 事务首先是一系列操作组成的工作单元,该工作单元内的操作是不可分割的,即要么所有操作都做,要么所有操作都不做,这就是事务. 事务必需满足ACID(原子性.一致 ...

  7. 使用Spring Security3的四种方法概述

    使用Spring Security3的四种方法概述 那么在Spring Security3的使用中,有4种方法: 一种是全部利用配置文件,将用户.权限.资源(url)硬编码在xml文件中,已经实现过, ...

  8. Spring注解开发_Spring容器创建概述

    浅尝Spring注解开发_Spring容器创建概述 浅尝Spring注解开发,基于Spring 4.3.12 概述Spring容器创建的过程,包括12个方法的执行 浅尝Spring注解开发_自定义注册 ...

  9. 使用 Java 配置进行 Spring bean 管理--转

    概述 众所周知,Spring 框架是控制反转 (IOC) 或依赖性注入 (DI) 模式的推动因素,而这种推动是通过基于容器的配置实现的.过去,Spring 允许开发人员使用基于 XML 的配置,通过利 ...

随机推荐

  1. 动态加载JS脚本

    建立dynamic.js文件,表示动态加载的js文件,里面的内容为: function dynamicJS() { alert("加载完毕"); } 如下方法中的html页面和dy ...

  2. js页面跳转的方式

    js方式的页面跳转1.window.location.href方式    <script language="javascript" type="text/java ...

  3. C#文件流读写文件的简单winform实现

    using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; usin ...

  4. seleniu+python+chrome的调试

    1.下载chrome,下载chrome driver,下载地址:http://code.google.com/p/chromedriver/downloads/list 2.安装chrome,默认路径 ...

  5. JavaScript中style, currentStyle和 getComputedStyle的异同

    今天在做项目的时候,习惯性的用到了element.style.width,然而浏览器却报错,错误提示是style is undefined,这是我才意识到,内联样式表和外联样式表在js应用中也有很大的 ...

  6. 2.4G/5G频段WLAN的模式、带宽、协商速率

    2.4G频段 5G频段

  7. jQueryNotes仿QQ空间添加标记

    jquery-notes有以下特点: 支持添加备注图像 丰富的API 支持标记伸缩 支持更改主题 支持图片标记添加链接 不需要数据库 HTML 首先在页面上放置一张添加标志的图片 <div cl ...

  8. TypeScript Handbook 2——接口1(翻译)

    接口(Interfaces) One of TypeScript's core principles is that type-checking focuses on the 'shape' that ...

  9. 数据存储之CoreData

    #import "ViewController.h" #import <CoreData/CoreData.h> #import "Person.h" ...

  10. 云计算 云服务 hadoop

    云:是一种虚拟化的技术,重在资源管理. 云服务是云计算的一种商业模式,有三个层次: Iaas:场外服务器,存储和网络硬件:节省了维护成本和办公场地,公司可以在任何时候利用这些硬件来运行其应用 Paas ...