Spring MVC源码分析(三):请求分发器DispatcherServlet的设计与实现

本文主要是介绍Spring MVC源码分析(三):请求分发器DispatcherServlet的设计与实现,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

一、概述

  • DispatcherServlet是SpringMVC的一个前端控制器,是MVC架构中的C,即controller的实现,用于拦截这个web应用的所有请求,具体为在web.xml中配置这个servlet,对应的url-pattern设置为“/”,或者使用servlet3.0之后的WebApplicationInitializer来配置,在web容器启动这个应用时,会创建和初始化这个DispatcherServlet对象实例。
  • DispatcherServlet在接收到请求之后,会根据请求的uri信息,找到对应的某个controller的某个方法来处理这个请求。通常在controller对应的类中使用@Controller和@RequestMapping来唯一确定类的一个方法处理哪些uri请求,具体包括路径,http请求方法等信息。同时还需要处理主题theme,本地化locale,multipart请求,以及响应到View视图的映射。
  • 基于以上需求背景,DispatcherServlet需要定义相关的子组件来完成这些功能。由于Spring的ApplicationContext体系结构设计当中是支持层次化的,即整个spring应用包含一个root WebApplicationContext,多个子WebApplicationContext,子WebApplicationContext共享这个root WebApplicationContext。每个DispatcherServlet可以使用一个独立的,只与这个DispatcherServlet实例绑定的WebApplicationContext来创建和管理这些子组件。

二、DispatcherServlet的类结构设计

  • DispatcherServlet既是servlet规范中的一个普通的servlet,在service方法中定义对请求的处理;又通过spring的WebApplicationContext,即使用spring的beans容器机制,来管理其相关的子组件bean,并在自身中维护对spring容器中的子组件bean的引用,从而方便调用。所以在DispatcherServlet类结构设计当中,就需要考虑这两个方面的设计,如图:
    在这里插入图片描述
  • 从上图可以看出,在DispatcherServlet的类的继承体系中,从下到上依次为:DispatcherServlet -> FrameworkServlet -> HttpServletBean。

1. HttpServletBean

  • 继承于HttpServlet,实现了EnvironmentAware(注入Environment对象)和EnvironmentCapable(访问Environment对象)接口,其中Environment主要从类路径的属性文件,运行参数,@PropertySource注解等获取应用的相关属性值,以提供给spring容器相关组件访问,或者写入属性到Environment来给其他组件访问。HttpServletBean的主要作用就是将于该servlet相关的init-param,封装成bean属性,然后保存到Environment当中,从而可以在spring容器中被其他bean访问。

2. FrameworkServlet

  • 继承于HttpServletBean,因为DispatcherServlet通常包含一个独立的WebApplication,而普通的servlet则只是通过servletContext获取spring容器的root WebApplicationContext,从而从中获取相关bean,所以在DispatcherServlet类层次结构中,增加FrameworkServlet这层设计。作用就是用于获取,创建与管理,DispatcherServlet所绑定的WebApplicationContext对象,即完成WebApplicationContext的创建相关的:从contextConfigLocation获取xml或WebApplicationInitializer配置信息,根据contextClass创建WebApplicationContext,以及获取ApplicationContextInitializer来对WebApplicationContext进行初始化,最后调用refresh完成DispatcherServlet绑定的这个WebApplicationContext的创建。

3. DispatcherServlet

  • 从FrameworkServlet中获取WebApplicationContext,然后从WebApplicationContext中获取DispatcherServlet的相关功能子组件bean,然后在自身维护一个引用。实现doService方法并使用这些功能子组件来完成请求的处理和生成响应。

4. 三者的关联

  • 在HttpServletBean的init方法中,定义initServletBean模板方法,供子类实现,其中FrameworkServlet的initServletBean方法实现为创建WebApplicationContext,即调用initWebApplicationContext方法来完成WebApplicationContext的创建,并在initWebApplicationContext方法中定义onRefresh模板方法由子类实现,其中DispatcherServlet的onRefresh方法实现为从initWebApplicationContext的WebApplicationContext获取其功能子组件的bean,保存在自身的引用中。

三、DispatcherServlet的功能子组件

  • 在DispatcherServlet的onRefresh方法中,完成从WebApplicationContext获取对应的bean,赋值到自身的引用当中,源码实现如下:

    /*** This implementation calls {@link #initStrategies}.*/
    @Override
    protected void onRefresh(ApplicationContext context) {initStrategies(context);
    }/*** Initialize the strategy objects that this servlet uses.* <p>May be overridden in subclasses in order to initialize further strategy objects.*/
    protected void initStrategies(ApplicationContext context) {initMultipartResolver(context);initLocaleResolver(context);initThemeResolver(context);initHandlerMappings(context);initHandlerAdapters(context);initHandlerExceptionResolvers(context);initRequestToViewNameTranslator(context);initViewResolvers(context);initFlashMapManager(context);
    }
    
  • DispatcherServlet中对应的bean引用如下:即核心功能组件,主要包括multipart请求处理器multipartResolver,本地化处理器localeResolver,主题(JSP的css样式)处理器themeResolver,请求URI和处理方法映射handlerMappings,请求处理器适配器handlerAdapters,请求异常处理器handlerExceptionResolvers,视图名称解析器viewNameTranslator,视图处理器viewResolvers,转发请求数据存储器flashMapManager。

    /** MultipartResolver used by this servlet. */
    @Nullable
    private MultipartResolver multipartResolver;/** LocaleResolver used by this servlet. */
    @Nullable
    private LocaleResolver localeResolver;/** ThemeResolver used by this servlet. */
    @Nullable
    private ThemeResolver themeResolver;/** List of HandlerMappings used by this servlet. */
    @Nullable
    private List<HandlerMapping> handlerMappings;/** List of HandlerAdapters used by this servlet. */
    @Nullable
    private List<HandlerAdapter> handlerAdapters;/** List of HandlerExceptionResolvers used by this servlet. */
    @Nullable
    private List<HandlerExceptionResolver> handlerExceptionResolvers;/** RequestToViewNameTranslator used by this servlet. */
    @Nullable
    private RequestToViewNameTranslator viewNameTranslator;/** FlashMapManager used by this servlet. */
    @Nullable
    private FlashMapManager flashMapManager;/** List of ViewResolvers used by this servlet. */
    @Nullable
    private List<ViewResolver> viewResolvers;
    
  • 重点需要关注initHandlerMappings的实现,主要是从WebApplicationContext获取HandlerMapping接口实现类的对象实例,然后从HandlerMapping中获取请求URI和请求方法的处理器映射,其中一个重要的实现为RequestMappingHandlerMapping(包为:org.springframework.web.servlet.mvc.method.annotation),这个是处理@Controller和@RequestMapping,然后生成HandlerMethod对象实现了映射URI的(URI的信息由RequestMappingInfo维护),具体在后续文章中详细分析。

四、DispatcherServlet的请求处理

  • DispatcherServlet作为前端控制器,接收所有的请求(即从客户端发送请求到tomcat,tomcat定位到该应用,分配给DispatcherServlet),在doService方法中定义对请求的处理逻辑。doService方法对请求request进行一些加工,即添加一些attribute,包含当前DispatcherServlet的WebApplicationContext,本地化处理器localeResolver,主题处理器themeResolver等功能组件,而选择哪个controler的哪个方法来处理的逻辑,则由子方法doDispatch来实现,并且生成response响应客户端,源码如下:

    /*** Process the actual dispatching to the handler.* <p>The handler will be obtained by applying the servlet's HandlerMappings in order.* The HandlerAdapter will be obtained by querying the servlet's installed HandlerAdapters* to find the first that supports the handler class.* <p>All HTTP methods are handled by this method. It's up to HandlerAdapters or handlers* themselves to decide which methods are acceptable.* @param request current HTTP request* @param response current HTTP response* @throws Exception in case of any kind of processing failure*/
    protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {HttpServletRequest processedRequest = request;HandlerExecutionChain mappedHandler = null;boolean multipartRequestParsed = false;WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);try {ModelAndView mv = null;Exception dispatchException = null;try {// muiltpart请求处理,如果是则进行封装加工processedRequest = checkMultipart(request);multipartRequestParsed = (processedRequest != request);// 获取处理这个请求的那个controler的那个方法// Determine handler for the current request.mappedHandler = getHandler(processedRequest);if (mappedHandler == null) {noHandlerFound(processedRequest, response);return;}// 使用请求处理器适配器HandlerAdapter来定义// 请求的DispatcherServlet的统一处理格式// 即定义一种统一的模板来调用不同的Handler实现请求处理// 其中Handler由拦截器链和请求处理方法HandlerMethod组成// Determine handler adapter for the current request.HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());// Process last-modified header, if supported by the handler.String method = request.getMethod();boolean isGet = "GET".equals(method);if (isGet || "HEAD".equals(method)) {long lastModified = ha.getLastModified(request, mappedHandler.getHandler());if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {return;}}// 调用拦截器链各个拦截器的preHandle,进行预处理if (!mappedHandler.applyPreHandle(processedRequest, response)) {return;}// 请求的实际处理,返回一个ModelAndView// 其中Model表示模型,即包含数据在视图渲染中使用// View为定义到具体的JSP视图// Actually invoke the handler.mv = ha.handle(processedRequest, response, mappedHandler.getHandler());if (asyncManager.isConcurrentHandlingStarted()) {return;}applyDefaultViewName(processedRequest, mv);mappedHandler.applyPostHandle(processedRequest, response, mv);}catch (Exception ex) {dispatchException = ex;}catch (Throwable err) {// As of 4.3, we're processing Errors thrown from handler methods as well,// making them available for @ExceptionHandler methods and other scenarios.dispatchException = new NestedServletException("Handler dispatch failed", err);}// 对客户端进行响应processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);}catch (Exception ex) {triggerAfterCompletion(processedRequest, response, mappedHandler, ex);}catch (Throwable err) {triggerAfterCompletion(processedRequest, response, mappedHandler,new NestedServletException("Handler processing failed", err));}finally {if (asyncManager.isConcurrentHandlingStarted()) {// Instead of postHandle and afterCompletionif (mappedHandler != null) {mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);}}else {// Clean up any resources used by a multipart request.if (multipartRequestParsed) {cleanupMultipart(processedRequest);}}}
    }
    

五、总结

  • 通过以上分析可知,DispatcherServlet作为springMVC框架的一个统一前端控制器,需要接收所有发送到这个应用的请求,然后在自身启动时,已经加载好的URI和请求处理器映射,获取对应的请求处理器,由请求处理器进行实际的请求处理。
  • 所以基于此,DispatcherServlet需要多种子功能组件来完成请求处理,由于应用也可以使用多个DispatcherServlet来接收请求,为了对众多子功能组件的封装和多个DispatcherServlet的子组件的隔离性,每个DispatcherServlet使用了一个自身独立spring子容器WebApplicationContext来管理自身的子功能组件。然后共享同一个root WebApplicationContext(即WEB-INF/applicationContext.xml)来获取公用组件,如数据库连接池等。
  • 在这些子功能组件中,我们需要核心关注HandlerMappings,即请求URI和请求处理器映射这个组件的设计,即DispatcherServlet的WebApplicationContext是如何产生这个映射的,映射的设计是怎么样的,请求处理器具体是什么,如我们应用代码通常是使用@Controller和@RequestMapping来定义的,这些在底层源码是怎么实用的;当一个请求到来时,DispatcherServlet是如何从里面查找的。这些问题其实就需要到spring的IOC实现了,即spring-context和spring-beans包的实现,具体在后续文章分析。

这篇关于Spring MVC源码分析(三):请求分发器DispatcherServlet的设计与实现的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/957122

相关文章

C# string转unicode字符的实现

《C#string转unicode字符的实现》本文主要介绍了C#string转unicode字符的实现,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随... 目录1. 获取字符串中每个字符的 Unicode 值示例代码:输出:2. 将 Unicode 值格式化

python安装whl包并解决依赖关系的实现

《python安装whl包并解决依赖关系的实现》本文主要介绍了python安装whl包并解决依赖关系的实现,文中通过图文示例介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面... 目录一、什么是whl文件?二、我们为什么需要使用whl文件来安装python库?三、我们应该去哪儿下

Python脚本实现图片文件批量命名

《Python脚本实现图片文件批量命名》这篇文章主要为大家详细介绍了一个用python第三方库pillow写的批量处理图片命名的脚本,文中的示例代码讲解详细,感兴趣的小伙伴可以了解下... 目录前言源码批量处理图片尺寸脚本源码GUI界面源码打包成.exe可执行文件前言本文介绍一个用python第三方库pi

Java集合中的List超详细讲解

《Java集合中的List超详细讲解》本文详细介绍了Java集合框架中的List接口,包括其在集合中的位置、继承体系、常用操作和代码示例,以及不同实现类(如ArrayList、LinkedList和V... 目录一,List的继承体系二,List的常用操作及代码示例1,创建List实例2,增加元素3,访问元

Java中将异步调用转为同步的五种实现方法

《Java中将异步调用转为同步的五种实现方法》本文介绍了将异步调用转为同步阻塞模式的五种方法:wait/notify、ReentrantLock+Condition、Future、CountDownL... 目录异步与同步的核心区别方法一:使用wait/notify + synchronized代码示例关键

Nginx实现动态封禁IP的步骤指南

《Nginx实现动态封禁IP的步骤指南》在日常的生产环境中,网站可能会遭遇恶意请求、DDoS攻击或其他有害的访问行为,为了应对这些情况,动态封禁IP是一项十分重要的安全策略,本篇博客将介绍如何通过NG... 目录1、简述2、实现方式3、使用 fail2ban 动态封禁3.1 安装 fail2ban3.2 配

Java 8 Stream filter流式过滤器详解

《Java8Streamfilter流式过滤器详解》本文介绍了Java8的StreamAPI中的filter方法,展示了如何使用lambda表达式根据条件过滤流式数据,通过实际代码示例,展示了f... 目录引言 一.Java 8 Stream 的过滤器(filter)二.Java 8 的 filter、fi

Java中实现订单超时自动取消功能(最新推荐)

《Java中实现订单超时自动取消功能(最新推荐)》本文介绍了Java中实现订单超时自动取消功能的几种方法,包括定时任务、JDK延迟队列、Redis过期监听、Redisson分布式延迟队列、Rocket... 目录1、定时任务2、JDK延迟队列 DelayQueue(1)定义实现Delayed接口的实体类 (

springboot的调度服务与异步服务使用详解

《springboot的调度服务与异步服务使用详解》本文主要介绍了Java的ScheduledExecutorService接口和SpringBoot中如何使用调度线程池,包括核心参数、创建方式、自定... 目录1.调度服务1.1.JDK之ScheduledExecutorService1.2.spring

将java程序打包成可执行文件的实现方式

《将java程序打包成可执行文件的实现方式》本文介绍了将Java程序打包成可执行文件的三种方法:手动打包(将编译后的代码及JRE运行环境一起打包),使用第三方打包工具(如Launch4j)和JDK自带... 目录1.问题提出2.如何将Java程序打包成可执行文件2.1将编译后的代码及jre运行环境一起打包2