Spring MVC异步模式中使用Filter和HandlerInterceptor
看到上面的异步访问,不免我们会新生怀疑,若是普通的拦截器HandlerInterceptor,还生效吗?若生效,效果是怎么样的,现在我们直接看一下吧:(备注:我以上面Callable的Demo为示例)
Filter
// 注意,这里必须开启异步支持asyncSupported = true,否则报错:Async support must be enabled on a servlet and for all filters involved in async request processing @WebFilter(urlPatterns = "/*", asyncSupported = true) public class HelloFilter extends OncePerRequestFilter { @Override protected void initFilterBean() throws ServletException { System.out.println("Filter初始化..."); } @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { System.out.println(Thread.currentThread().getName() + "--->" + request.getRequestURI()); filterChain.doFilter(request, response); } }
输出:
http-apr-8080-exec-3--->/demowar_war/async/controller/hello http-apr-8080-exec-3 主线程start http-apr-8080-exec-3 主线程end MvcAsync1 子子子线程start MvcAsync1 子子子线程end
由此可以看出,异步上下文,Filter还是只会被执行一次拦截的,符合我们的预期,所以没什么毛病。
HandlerInterceptor
public class HelloInterceptor implements HandlerInterceptor { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { System.out.println(Thread.currentThread().getName() + "---preHandle-->" + request.getRequestURI()); return true; } @Override public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception { System.out.println(Thread.currentThread().getName() + "---postHandle-->" + request.getRequestURI()); } @Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { System.out.println(Thread.currentThread().getName() + "---postHandle-->" + request.getRequestURI()); } } // 注册拦截器 @Configuration @EnableWebMvc public class AppConfig implements WebMvcConfigurer { @Override public void addInterceptors(InterceptorRegistry registry) { // /**拦截所有请求 registry.addInterceptor(new HelloInterceptor()).addPathPatterns("/**"); } }
输出:
http-apr-8080-exec-3--->/demowar_war/async/controller/hello http-apr-8080-exec-3---preHandle-->/demowar_war/async/controller/hello http-apr-8080-exec-3 主线程start http-apr-8080-exec-3 主线程end MvcAsync1 子子子线程start MvcAsync1 子子子线程end // 注意 子子子线程处理结束后,再一次触发了preHandle===== // 此处还要一个细节:这里面的线程既不是子线程,也不是上面的线程 而是新开了一个线程~~~ http-apr-8080-exec-5---preHandle-->/demowar_war/async/controller/hello http-apr-8080-exec-5---postHandle-->/demowar_war/async/controller/hello http-apr-8080-exec-5---afterCompletion-->/demowar_war/async/controller/hello
从上面可以看出,如果我们就是普通的Spring MVC的拦截器,preHandler会执行两次,这也符合我们上面分析的处理步骤。所以我们在书写preHandler的时候,一定要特别的注意,要让preHandler即使执行多次,也不要受到影响(幂等)
异步拦截器 AsyncHandlerInterceptor、CallableProcessingInterceptor、DeferredResultProcessingInterceptor
Spring MVC给提供了异步拦截器,能让我们更深入的参与进去异步request的生命周期里面去。其中最为常用的为:AsyncHandlerInterceptor:
public class AsyncHelloInterceptor implements AsyncHandlerInterceptor { // 这是Spring3.2提供的方法,专门拦截异步请求的方式 @Override public void afterConcurrentHandlingStarted(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { System.out.println(Thread.currentThread().getName() + "---afterConcurrentHandlingStarted-->" + request.getRequestURI()); } @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { System.out.println(Thread.currentThread().getName() + "---preHandle-->" + request.getRequestURI()); return true; } @Override public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception { System.out.println(Thread.currentThread().getName() + "---postHandle-->" + request.getRequestURI()); } @Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { System.out.println(Thread.currentThread().getName() + "---afterCompletion-->" + request.getRequestURI()); } }
输出:
http-apr-8080-exec-3---preHandle-->/demowar_war/async/controller/hello http-apr-8080-exec-3 主线程start http-apr-8080-exec-3 主线程end // 这里发现,它在主线程结束后,子线程开始之前执行的(线程号还是同一个哦~) http-apr-8080-exec-3---afterConcurrentHandlingStarted-->/demowar_war/async/controller/hello MvcAsync1 子子子线程start MvcAsync1 子子子线程end http-apr-8080-exec-6---preHandle-->/demowar_war/async/controller/hello http-apr-8080-exec-6---postHandle-->/demowar_war/async/controller/hello http-apr-8080-exec-6---afterCompletion-->/demowar_war/async/controller/hello
AsyncHandlerInterceptor提供了一个afterConcurrentHandlingStarted()方法, 这个方法会在Controller方法异步执行时开始执行, 而Interceptor的postHandle方法则是需要等到Controller的异步执行完才能执行
(比如我们用DeferredResult的话,afterConcurrentHandlingStarted是在return的之后执行,而postHandle()是执行.setResult()之后执行)
需要说明的是:如果我们不是异步请求,afterConcurrentHandlingStarted是不会执行的。所以我们可以把它当做加强版的HandlerInterceptor来用。平时我们若要使用拦截器,建议使用它。(Spring5,JDK8以后,很多的xxxAdapter都没啥用了,直接implements接口就成~)
同样可以注册CallableProcessingInterceptor或者一个DeferredResultProcessingInterceptor用于更深度的集成异步request的生命周期
@Override public void configureAsyncSupport(AsyncSupportConfigurer configurer) { // 注册异步的拦截器、默认的超时时间、任务处理器TaskExecutor等等 //configurer.registerCallableInterceptors(); //configurer.registerDeferredResultInterceptors(); //configurer.setDefaultTimeout(); //configurer.setTaskExecutor(); }
只是一般来说,我们并不需要注册这种精细的拦截器,绝大多数情况下,使用AsyncHandlerInterceptor是够了的。
(Spring MVC的很多默认设置,请参考WebMvcConfigurationSupport)
区别使用
我觉得最主要的区别是:DeferredResult需要自己用线程来处理结果setResult,而Callable的话不需要我们来维护一个结果处理线程。
总体来说,Callable的话更为简单,同样的也是因为简单,灵活性不够;
相对地,DeferredResult更为复杂一些,但是又极大的灵活性,所以能实现非常多个性化的、复杂的功能,可以设计高级应用。
有些较常见的场景, Callable也并不能解决,比如说:我们访问A接口,A接口调用三方的服务,服务回调(注意此处指的回调,不是返回值)B接口,这种情况就没办法使用Callable了,这个时候可以使用DeferredResult
使用原则:基本上在可以用Callable的时候,直接用Callable;而遇到Callable没法解决的场景的时候,可以尝试使用DeferredResult。
这里所指的Callable包括WebAsyncTask
总结
在Reactive编程模型越来越流行的今天,多一点对异步编程模型(Spring MVC异步模式)的了解,可以更容易去接触Spring5带来的新特性—响应式编程。
同时,异步编程是我们高效利用系统资源,提高系统吞吐量,编写高性能应用的必备技能。希望此篇文章能帮助到大家,运用到工作中~
然后,关于DeferredResult的高级使用场景,见下一篇博文:高级应用和源码分析篇