SpringMVC 的控制器是单例的,这意味着对同一个类中的处理方法发送的所有请求,都会共享同一个实例,因此控制器方法不适合处理耗时的方法。
当控制器确实需要处理耗时方法时,建议采用异步方法来处理。即让控制器方法返回一个Callable对象,SpirngMVC 会启动新的线程来执行该Callable对象,而Callable对象的call方法的返回值才会被当成真正的处理方法返回值返回给DispatcherServlet,进而进行视图解析。
栗子:
用户请求url:
/query?name=lyx&price=15.2"
控制器:
@Controller
public class UserController
{
@Resource(name="userService")
private UserService userService;
@RequestMapping("/query")
public Callable<String> queryMethod(Model model)
{
Callable<String> result = () ->{
System.out.println("---------------------"+new Date()+"---------------------");
//userService.getBooks()是个耗时方法
model.addAttribute("books",userService.getBooks());
return "result";
};
System.out.println("---------------------"+new Date()+"---------------------");
return result;
}
}
耗时方法类:
public class UserServiceImpl implements UserService
{
@Override
public List<String> getBooks() {
try {
Thread.sleep(6000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return List.of("疯狂java讲义","轻量级javaweb应用实战","http协议");
}
}
单单这样还是不过,还要开启异步处理功能,所以要进行一些配置:
开启异步处理支持只需要在web.xml的servlet元素中添加如下配置即可。
<async-supported>true</async-supported>
当然,如果没次执行该控制器方法就会创建一条全新的线程是不好的,所以最好要配置线程池来管理线程,其中SpirngMVC 内置了一个线程池ThreadPoolTaskExecutor
<mvc:annotation-driven>
<!--开启异步支持,并且指定线程池以及超市时间-->
<mvc:async-support task-executor="taskExecutor"
default-timeout="5000">
<!--配置超时拦截器-->
<mvc:callable-interceptors>
<bean class="com.lyx.app.controller.MyCallableInterceptor"/>
</mvc:callable-interceptors>,
</mvc:async-support>
</mvc:annotation-driven>
<!--配置线程池-->
<bean id="taskExecutor"
class="org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor"
p:maxPoolSize="100"
p:corePoolSize="10"
p:queueCapacity="1020"
p:keepAliveSeconds="30"/>
我们看到,上面的async-support不仅指定了线程池还制定了超时拦截器,该拦截器会在连接超时自动触发,写法与普通的控制器一样,不过该拦截器得实现CallableProcessingInterceptor接口,重写handleTimeout方法。
public class MyCallableInterceptor implements CallableProcessingInterceptor
{
@Override
public <T> Object handleTimeout(NativeWebRequest request, Callable<T> task) throws Exception {
//timeout为逻辑是图名,其解析结果为一个超时提醒的jsp页面
return "timeout";
}
}
除了用Callable之外,SpringMVC 自己提供了异步处理策略,使用DeferredResult支持的异步处理
直接看改写的控制器吧:
@Controller
public class UserController
{
@Resource(name="userService")
private UserService userService;
@RequestMapping("/query")
public DeferredResult<String> queryMethod(Model model)
{
//创建DeferredResult,50000(ms)为超时长,timeout为超时默认返回值
var deferredResult = new DeferredResult<String>(5000L,"timeout");
new Thread(()->{
System.out.println("---------------------"+new Date()+"---------------------");
model.addAttribute("books",userService.getBooks());
//下面的result才是真正的返回的逻辑视图名
deferredResult.setResult("result");
}).start();
System.out.println("---------------------"+new Date()+"---------------------");
return deferredResult;
}
}
其实使用DeferredResult并不一定需要开启新线程,只要在超时之前执行了deferredResult.setResult(“xxx”);即可正常返回正常的逻辑视图名。