SpringBoot异步回调
实现后台异步处理请求,并将处理结果返回前端
Callable
使用
Callable
进行回调,直接返回Callable<目标类>
即可。需要进行
WebMvcConfigurer
的AsyncSupportConfigurer
,即MVC的异步支持配置
配置类
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.web.servlet.config.annotation.AsyncSupportConfigurer;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class webConfig implements WebMvcConfigurer {
@Override
public void configureAsyncSupport(AsyncSupportConfigurer configurer) {
// 设定异步请求线程池callable等, spring默认线程不可重用
configurer.setTaskExecutor(asyncExecutor());
}
public ThreadPoolTaskExecutor asyncExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
// 核心线程数
executor.setCorePoolSize(10);
// 最大线程数
executor.setMaxPoolSize(100);
// 队列容量,如果此数字大于0,使用队列LinkedBlockingQueue
executor.setQueueCapacity(100);
// 线程名称前缀
executor.setThreadNamePrefix("WebmvcThread-");
//初始化
executor.initialize();
return executor;
}
}
Controller
import com.example.bean.Demo;
import com.example.service.TtlTool;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.concurrent.Callable;
@RestController
public class IndexController {
@Autowired
private TtlTool ttlTool;
@GetMapping("test1")
public Callable<Demo> test1() {
System.out.println("Controller方法被 "+Thread.currentThread().getName()+" 开始执行于 "+ new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()));
Callable<Demo> callable = new Callable<Demo>() {
@Override
public Demo call() throws Exception {
System.out.println("Call方法被 "+Thread.currentThread().getName()+" 开始执行于 "+ new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()));
Demo d = ttlTool.ttl3();
System.out.println("Call方法被 "+Thread.currentThread().getName()+" 结束执行于 "+ new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()));
System.out.println();
return d;
}
};
System.out.println("Controller方法被 "+Thread.currentThread().getName()+" 结束执行于 "+ new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()));
return callable;
}
}
Service
import org.springframework.stereotype.Component;
import com.example.bean.Demo;
import java.text.SimpleDateFormat;
import java.util.Date;
@Component
public class TtlTool {
public Demo ttl3() throws InterruptedException {
System.out.println("异步方法被 "+Thread.currentThread().getName()+" 开始执行于 "+ new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()));
Thread.sleep(5000);
Demo d = new Demo();
d.setOne("1");
d.setTwo("2");
d.setThree("3");
d.setFour("4");
System.out.println("异步方法被 "+Thread.currentThread().getName()+" 结束执行于"+ new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()));
return d;
}
}
结果
同时发送两个
http://localhost:8081/test1
请求
Controller方法被 http-nio-8081-exec-1 开始执行于 2023-10-20 15:08:23
Controller方法被 http-nio-8081-exec-1 结束执行于 2023-10-20 15:08:23
Call方法被 WebmvcThread-1 开始执行于 2023-10-20 15:08:23
异步方法被 WebmvcThread-1 开始执行于 2023-10-20 15:08:23
Controller方法被 http-nio-8081-exec-2 开始执行于 2023-10-20 15:08:23
Controller方法被 http-nio-8081-exec-2 结束执行于 2023-10-20 15:08:23
Call方法被 WebmvcThread-2 开始执行于 2023-10-20 15:08:23
异步方法被 WebmvcThread-2 开始执行于 2023-10-20 15:08:23
异步方法被 WebmvcThread-1 结束执行于2023-10-20 15:08:28
Call方法被 WebmvcThread-1 结束执行于 2023-10-20 15:08:28
异步方法被 WebmvcThread-2 结束执行于2023-10-20 15:08:28
Call方法被 WebmvcThread-2 结束执行于 2023-10-20 15:08:28
并且浏览器有显示返回值
DeferredResult
一是可以封装异步请求结果返回到前端
二是允许在处理请求的方法中延迟发送响应,直到**
满足某个条件
,或者达到某个时间限制
**。
以下代码实现效果:
- 如果
Service
的ttl1
方法的执行时间**长于5秒
**,就不等它,转而将ttl2
方法的超时处理结果进行返回给前端显示; - 如果
Service
的ttl1
方法的执行时间**短于5秒
**,就将ttl1
方法的正常业务结果返回给前端显示; - 如果
Service
的ttl1
方法的执行时间**等于5秒
**,返回ttl1
方法的正常业务结果
配置类
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
@Configuration
@EnableAsync
public class AsyncConfig {
@Bean(name = "asyncExecutor")
public ThreadPoolTaskExecutor asyncExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
// 核心线程数
executor.setCorePoolSize(10);
// 最大线程数
executor.setMaxPoolSize(100);
// 队列容量,如果此数字大于0,使用队列LinkedBlockingQueue
executor.setQueueCapacity(100);
// 线程名称前缀
executor.setThreadNamePrefix("WebmvcThread-");
//初始化
executor.initialize();
return executor;
}
}
Controller
import com.example.bean.Demo;
import com.example.service.TtlTool;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RestController;
import java.text.SimpleDateFormat;
import java.util.Date;
import org.springframework.web.context.request.async.DeferredResult;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.function.BiConsumer;
@RestController
public class IndexController {
@Autowired
private TtlTool ttlTool;
/**
* 如果ttl1方法的执行时间短于5秒,Controller就会返回ttl1的结果
* 如果ttl1方法的执行时间大于5秒,Controller就会返回ttl2的结果
* */
@RequestMapping("/user/{name}")
public DeferredResult<Demo> demo(@PathVariable String name) throws InterruptedException, ExecutionException {
System.out.println("Controller方法被 "+Thread.currentThread().getName()+" 开始执行于 "+ new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()));
//5秒之后,返回ttl2()方法结果给response
DeferredResult<Demo> deferredResult = new DeferredResult<>(5000l,ttlTool.ttl2());
CompletableFuture<Demo> d = ttlTool.ttl1();
//当d中的任务完成后,whenComplete中的逻辑才会执行
d.whenComplete(new BiConsumer<Demo, Throwable>() {
@Override
public void accept(Demo result, Throwable throwable) {
if (throwable != null) {
deferredResult.setErrorResult(throwable.getMessage());
} else {
//此方法一执行,上面的deferredResult的5秒限制就失效了
deferredResult.setResult(result);
}
}
});
System.out.println("Controller方法被 "+Thread.currentThread().getName()+" 结束执行于 "+ new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()));
System.out.println();
return deferredResult;
}
}
Service
package com.example.service;
import com.example.bean.Demo;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.concurrent.CompletableFuture;
@Component
public class TtlTool {
@Async
public CompletableFuture<Demo> ttl1() throws InterruptedException {
System.out.println("异步方法被 "+Thread.currentThread().getName()+" 开始执行于 "+ new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()));
//模拟耗时业务逻辑执行,把这里的数值改成大于5000和小于5000分别看结果
Thread.sleep(3000);
Demo d = new Demo();
d.setOne("1");
d.setTwo("2");
d.setThree("3");
d.setFour("4");
System.out.println("异步方法被 "+Thread.currentThread().getName()+" 结束执行于"+ new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()));
CompletableFuture<Demo> result = CompletableFuture.completedFuture(d);
return result;
}
public Demo ttl2(){
System.out.println("超时处理方法被 "+Thread.currentThread().getName()+" 开始执行于 "+ new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()));
Demo d = new Demo();
d.setOne("11");
d.setTwo("12");
d.setThree("13");
d.setFour("14");
System.out.println("超时处理方法被 "+Thread.currentThread().getName()+" 结束执行于"+ new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()));
return d;
}
}
结果
业务执行时间小于
DeferredResult
的超时时间:
ttl1
方法执行时间小于5秒
Controller方法被 http-nio-8081-exec-1 开始执行于 2023-10-20 15:52:56
超时处理方法被 http-nio-8081-exec-1 开始执行于 2023-10-20 15:52:56
超时处理方法被 http-nio-8081-exec-1 结束执行于2023-10-20 15:52:56
异步方法被 WebmvcThread-1 开始执行于 2023-10-20 15:52:56
Controller方法被 http-nio-8081-exec-1 结束执行于 2023-10-20 15:52:56
异步方法被 WebmvcThread-1 结束执行于2023-10-20 15:52:59
返回的是ttl1
方法的结果,即正常的业务结果
业务执行时间大于
DeferredResult
的超时时间:
ttl1
方法执行时间大于5秒
,将上面Service
代码的Thread.sleep()
设置大于5秒,看结果:
Controller方法被 http-nio-8081-exec-1 开始执行于 2023-10-20 16:09:30
超时处理方法被 http-nio-8081-exec-1 开始执行于 2023-10-20 16:09:30
超时处理方法被 http-nio-8081-exec-1 结束执行于2023-10-20 16:09:30
异步方法被 WebmvcThread-1 开始执行于 2023-10-20 16:09:30
Controller方法被 http-nio-8081-exec-1 结束执行于 2023-10-20 16:09:30
异步方法被 WebmvcThread-1 结束执行于2023-10-20 16:09:36
返回的是ttl2
方法的结果,即DeferredResult
的超时结果
业务执行时间等于
DeferredResult
的超时时间:
ttl1
方法执行时间等于5秒
,将上面Service
代码的Thread.sleep()
设置为5秒,看结果:
Controller方法被 http-nio-8081-exec-1 开始执行于 2023-10-20 16:11:16
超时处理方法被 http-nio-8081-exec-1 开始执行于 2023-10-20 16:11:16
超时处理方法被 http-nio-8081-exec-1 结束执行于2023-10-20 16:11:16
异步方法被 WebmvcThread-1 开始执行于 2023-10-20 16:11:16
Controller方法被 http-nio-8081-exec-1 结束执行于 2023-10-20 16:11:16
异步方法被 WebmvcThread-1 结束执行于2023-10-20 16:11:21
返回的是ttl1
方法的结果,即正常的业务结果
Future及其派生
Future
以及它的子类CompletableFuture
都可以进行回调返回
CompletableFuture
更加灵活,优于Future
配置类
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
@Configuration
@EnableAsync
public class AsyncConfig {
@Bean(name = "asyncExecutor")
public ThreadPoolTaskExecutor asyncExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
// 核心线程数
executor.setCorePoolSize(10);
// 最大线程数
executor.setMaxPoolSize(100);
// 队列容量,如果此数字大于0,使用队列LinkedBlockingQueue
executor.setQueueCapacity(100);
// 线程名称前缀
executor.setThreadNamePrefix("WebmvcThread-");
//初始化
executor.initialize();
return executor;
}
}
Controller
import com.example.bean.Demo;
import com.example.service.TtlTool;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RestController;
import java.text.SimpleDateFormat;
import java.util.Date;
import org.springframework.web.context.request.async.DeferredResult;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.function.BiConsumer;
@RestController
public class IndexController {
@Autowired
private TtlTool ttlTool;
@RequestMapping("/user/{name}")
public Future<Demo> demo(@PathVariable String name) throws InterruptedException, ExecutionException {
System.out.println("Controller方法被 "+Thread.currentThread().getName()+" 开始执行于 "+ new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()));
//返回CompletableFuture也可以,把方法上面的Future<Demo>也改成CompletableFuture
//CompletableFuture<Demo> d = ttlTool.ttl1();
Future<Demo> d = ttlTool.ttl1();
System.out.println("Controller方法被 "+Thread.currentThread().getName()+" 结束执行于 "+ new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()));
return d;
}
}
Service
import com.example.bean.Demo;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.concurrent.CompletableFuture;
@Component
public class TtlTool {
@Async
public CompletableFuture<Demo> ttl1() throws InterruptedException {
System.out.println("异步方法被 "+Thread.currentThread().getName()+" 开始执行于 "+ new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()));
Thread.sleep(3000);
Demo d = new Demo();
d.setOne("1");
d.setTwo("2");
d.setThree("3");
d.setFour("4");
System.out.println("异步方法被 "+Thread.currentThread().getName()+" 结束执行于"+ new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()));
CompletableFuture<Demo> result = CompletableFuture.completedFuture(d);
return result;
}
}
结果
Controller方法被 http-nio-8081-exec-1 开始执行于 2023-10-20 16:30:25
Controller方法被 http-nio-8081-exec-1 结束执行于 2023-10-20 16:30:25
异步方法被 WebmvcThread-1 开始执行于 2023-10-20 16:30:25
异步方法被 WebmvcThread-1 结束执行于2023-10-20 16:30:28
前端有返回
三者区别
Callable
比较简单,直接在call方法中写任务即可,结合WebMvcConfigurer配置实现请求异步处理
DeferredResult
真正的作用是延迟返回。
在异步任务处理完成后,可以使用DeferredResult
的setResult
方法封装异步结果,这样Controller
就会在setResult
方法执行后才进行结果返回。除此之外,还可以通过DeferredResult
的构造方法设置超时时间,实现异步处理的超时兜底。
值得注意的是,在使用DeferredResult
进行结果返回时,会开启一个新的tomcat
线程进行返回,前面执行Controller
请求的线程会被释放。
一般使用自己配置的异步线程池执行任务,并使用setResult
方法设置结果,这样用于返回结果的tomcat线程就不会涉及到耗时处理。从而节省资源。
Future及其派生
其实主要是它的子类CompletableFuture
CompletableFuture
可以对多种有参无参以及有无返回值的情况处理,还可以组合多个异步任务执行,非常灵活。而Callable
无法做到这一点CompletableFuture
可以创建异步任务,Future
只能接收异步结果
Callable
和Future
区别
在Java中,Callable
和Future
是用于支持异步计算和获取结果的关键接口。它们之间的区别如下:
- 功能不同:
Callable
接口代表一个可以返回结果的异步计算任务,它的call()
方法可以在计算完成后返回一个结果。Callable
通常被用于ExecutorService
中提交任务。Future
接口代表一个异步计算的结果,它提供了一些方法来检查任务是否完成、获取任务的计算结果或取消任务的执行。
- 返回值类型不同:
Callable
的call()
方法定义了一个泛型返回值类型,可以通过Future<T>
来获取计算结果,其中T
表示计算结果的类型。Future
的get()
方法返回一个泛型类型的结果,可以是Callable
的计算结果或者抛出的异常。
- 异步处理方式不同:
Callable
是一个主动执行的接口,任务执行完成后会返回结果。Future
是一个被动获取结果的接口,可以通过调用get()
方法来等待并获取异步任务的结果。
- 取消任务的能力不同:
Callable
接口没有提供取消任务的功能。Future
接口提供了cancel()
方法来取消异步任务的执行。
综上所述,Callable
和Future
是Java中用于支持异步计算的接口,Callable
用于定义异步计算任务,而Future
用于获取异步计算的结果,并提供了一些其他的方法来管理和取消任务的执行。