【小家Spring】高性能关键技术之---体验Spring MVC的异步模式(Callable、WebAsyncTask、DeferredResult) 基础使用篇(下)

简介: 【小家Spring】高性能关键技术之---体验Spring MVC的异步模式(Callable、WebAsyncTask、DeferredResult) 基础使用篇(下)

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的高级使用场景,见下一篇博文:高级应用和源码分析篇

相关文章
|
4月前
|
设计模式 存储 前端开发
MVC(Model-View-Controller)是一种软件设计模式,用于将应用程序的输入逻辑、业务逻辑和用户界面逻辑分离
【6月更文挑战第17天】**MVC模式**是软件设计模式,用于分离输入逻辑、业务逻辑和用户界面。模型处理数据和业务,视图展示数据,控制器协调两者响应用户请求。优点包括:关注点分离、提高开发效率、简化测试、支持多视图及便于大型项目管理。
43 3
|
4天前
|
设计模式 开发框架 前端开发
MVC 模式在 C# 中的应用
MVC(Model-View-Controller)模式是广泛应用于Web应用程序开发的设计模式,将应用分为模型(存储数据及逻辑)、视图(展示数据给用户)和控制器(处理用户输入并控制模型与视图交互)三部分,有助于管理复杂应用并提高代码可读性和维护性。在C#中,ASP.NET MVC框架常用于构建基于MVC模式的Web应用,通过定义模型、控制器和视图,实现结构清晰且易维护的应用程序。
12 2
|
1月前
|
前端开发 安全 Java
技术进阶:使用Spring MVC构建适应未来的响应式Web应用
【9月更文挑战第2天】随着移动设备的普及,响应式设计至关重要。Spring MVC作为强大的Java Web框架,助力开发者创建适应多屏的应用。本文推荐使用Thymeleaf整合视图,通过简洁的HTML代码提高前端灵活性;采用`@ResponseBody`与`Callable`实现异步处理,优化应用响应速度;运用`@ControllerAdvice`统一异常管理,保持代码整洁;借助Jackson简化JSON处理;利用Spring Security增强安全性;并强调测试的重要性。遵循这些实践,将大幅提升开发效率和应用质量。
54 7
|
1月前
|
前端开发 测试技术 开发者
MVC模式在现代Web开发中有哪些优势和局限性?
MVC模式在现代Web开发中有哪些优势和局限性?
|
1月前
|
设计模式 前端开发 PHP
PHP中实现简易的MVC模式
【8月更文挑战第31天】 本文将引导你了解如何在PHP中应用MVC(Model-View-Controller)架构模式,通过一个简单的例子展示其实现过程。我们将从基础的概念出发,逐步深入到代码实践,最终让你能够自己动手构建一个简易的MVC框架。文章不仅提供理论知识,还包含具体的代码示例,帮助你更好地理解并运用MVC模式。
|
2月前
|
缓存 Java Spring
Java本地高性能缓存实践问题之的Spring Boot中启用缓存支持问题如何解决
Java本地高性能缓存实践问题之的Spring Boot中启用缓存支持问题如何解决
序-Servlet和SpringMVC的联系和区别-配置路径先想好使用的使用的方法,然后匹配的需要的技术
序-Servlet和SpringMVC的联系和区别-配置路径先想好使用的使用的方法,然后匹配的需要的技术
|
4月前
|
JSON JavaScript 前端开发
技术经验分享:ExtJS4MVC架构讲解
技术经验分享:ExtJS4MVC架构讲解
24 0
|
4月前
|
JSON Java 数据库
技术笔记:SpringMVC常用注解
技术笔记:SpringMVC常用注解
|
4月前
|
设计模式 前端开发 Java
mvc模式详解
mvc模式详解
下一篇
无影云桌面