一、前言
Spring Framework分别使用TaskExecutor和TaskScheduler接口提供异步执行和任务调度的抽象。本节我们着重讲解@Async如何实现异步处理。
二、 @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}
如果简单的周期性调度不够表达,则可以提供cron表达式。例如,以下内容仅在工作日执行:
@Scheduled(cron="*/5 * * * * MON-FRI")public void doSomething() { // something that should execute on weekdays only}
请注意,要调度的方法必须具有void返回,并且不得指望任何参数。如果该方法需要与Application Context中的其他对象进行交互,则通常会通过依赖注入提供这些对象。
需要注意的是该注解默认是不会解析的,需要加上@EnableScheduling 来启动。
三、 @Async
可以在方法上添加@Async注释,以便异步调用该方法。换句话说,调用者将在调用时立即返回,并且该方法的实际执行将发生在Spring TaskExecutor中。
@Async
public void dosomthingAsync() {
System.out.println("--dosomthingAsync begin---");
// 模拟异步处理
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("--dosomthingAsync end---");
}
如上代码在方法dosomthingAsync上添加了@Async的注解,所以当我们调用dosomthingAsync方法时候,该方法会马上返回。
与使用@Scheduled注解的方法不同,@Async可以有返回值,因为它们将在运行时由调用者以“正常”方式调用,而不是由容器管理的调度任务调用。例如,以下是@Async注解的合法应用程序:
@Component
public class AsyncTask {
...
@Async
public CompletableFuture<String> dosomthingAsyncFuture() {
System.out.println("--dosomthingAsync begin---");
CompletableFuture<String> future = new CompletableFuture<String>();
// 模拟异步处理
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
future.complete("ok");
System.out.println("--dosomthingAsync end---");
return future;
}
}
如上代码调用该方法后,该方法会马上返回一个CompletableFuture对象,如果你一直持有这个CompletableFuture对象,那么等dosomthingAsyncFuture内业务处理异步处理完毕后,就可以从dosomthingAsyncFuture的get()方法获取到执行结果。那么Spring框架是如何做到我们dosomthingAsyncFuture时候会马上返回一个CompletableFuture那?其实其对该类进行了代理,经过代理后的上面的方法类似于:
public class AsynTaskProxy {
public AsyncTask getAsyncTask() {
return asyncTask;
}
public void setAsyncTask(AsyncTask asyncTask) {
this.asyncTask = asyncTask;
}
private AsyncTask asyncTask;
private TaskExecutor executor = new SimpleAsyncTaskExecutor();
public CompletableFuture<String> dosomthingAsyncFuture() {
return CompletableFuture.supplyAsync(new Supplier<String>() {
@Override
public String get() {
try {
return asyncTask.dosomthingAsyncFuture().get();
} catch (Throwable e) {
throw new CompletionException(e);
}
}
});
}
}
Spring会对AsyncTask类使用类似的AsynTaskProxy进行代理,并且会把AsynTask的实例注入到AsynTaskProxy内部,当我们调用AsynTask的dosomthingAsyncFuture方法时候,实际调用的是AsynTaskProxy的dosomthingAsyncFuture方法,后者则使用 CompletableFuture.supplyAsync开启了一个异步任务(其马上返回一个 CompletableFuture对象),并且使用默认的SimpleAsyncTaskExecutor线程池做为异步处理线程,然后异步任务内在具体调用了 AsyncTask实例的dosomthingAsyncFuture方法,并且在返回的future上获取执行结果。
默认情况下,Spring将搜索关联的线程池定义:Spring上下文容器中的唯一的org.springframework.core.task.TaskExecutor类型的bean,如果不存在,则查找名为“taskExecutor”的java.util.concurrent.Executo的 bean。如果两者都不存在,则将使用org.springframework.core.task.SimpleAsyncTaskExecutor的一个实例来处理异步方法调用。
SimpleAsyncTaskExecutor中对每个异步任务对应开启一个线程来进行处理,会造成线程频繁创建与销毁,没有进行线程复用,所以我们可以创建自己的线程池,比如下面:
@Bean
public TaskExecutor bizExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setMaxPoolSize(8);
executor.setCorePoolSize(8);
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
executor.setQueueCapacity(5);
return executor;
}
则当bizExecutor通过@Bean注入到Spring上下文中后,异步处理就使用其中线程池进行处理。
需要注意的是该注解默认是不会被解析的,需要加上@EnableAsync来启动。