探索SpringMVC-DispatcherServlet

阿里云国内75折 回扣 微信号:monov8
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6

前言

《探索SpringMVC-web上下文》中我们介绍了DispatcherServlet的上下文的初始化。然后为了让大家对DispatcherServlet的各个组件有所了解我们花了很多的时间来介绍各大组件。现在我们来看看DispatcherServlet是如何使用这些组件完成功能的。

DispatcherSerlvet的结构

DispatcherServlet
图释黄色部分是javax.servlet包的绿色部分是org.springframework的。而红色是DispatcherServlet的九大组件。

结构分析

javax的设计

  • GenericServlet通用的Servlet
    这是个抽象类意在提供一个通用的、与协议无关的Servlet基类。他实现了两个接口Servlet和ServletConfig。如果你非要说设计在一个接口里面不行吗可以。但是我们总是希望最求一种高内聚低耦合的设计希望减少软件系统的维护成本。从设计原则上讲这里是接口隔离原则。只向客户端提供其需要的行为。
  • HttpServletHttp协议Servlet
    这是为了Http协议而拓展的Servlet。从类结构上看他实现了Servlet接口的service方法并且将范围权限缩小到protected。该方法会将ServletRequest、ServletResponse转成HttpServletRequest、HttpServletResponse。然后会调用重载方法service该方法就是专门为http扩展的POST、GET、PUT等进行支持提供对应的doPost、doGet、doPut等方法。

Spring的扩展

  • HttpServletBean
    为this当前HttpServlet对象提供属性绑定基于ServletConfig获取属性值。其核心能力来自BeanWrapImpl这个在之前讲RequestMappingHandlerAdapter参数解析时也提到过他是Spring重要的底层支撑组件。在init()方法中执行该操作。并扩展出来initServletBean()让子类执行自己的初始化。

  • FrameworkServlet
    Spring的抽象框架Servlet类为架设SpringMVC处理框架做准备。他会继承Spring上下文从而为子类提供从上下文获取各种对象的能力。
    他干了两个重要的工作

    1. initServletBean()初始化了上下文、并且调用模板方法onRefresh(ApplicationContext context)
    2. 将所有请求统一调度到processRequest方法并调用抽象doService处理请求。因为只有统一了入口才有可能提供统一的处理能力。而processRequest方法会维护localeContext、RequestAttributes同时还会发布ServletRequestHandledEvent时间。
  • DispatcherServlet
    SpringMVC的核心意为将请求分发到处理的处理器进行处理。在onRefresh方法中从上下文获取到九大组件从而真正使得DispatcherServlet具备请求处理条件。
    实现doService方法为了便于Handler和View使用框架组件将ApplicationContext、ThemeResolver、LocaleResolver设置为request的Attribuite。然后调用到关键的doDispatch方法处理请求。后面会重点讲该方法。

DispacherServlet的初始化

这里复习一下之前的内容

DispatcherServlet是基于Servlet的声明周期方法init来进行初始化的。初始时会刷新上下文并且会通过ApplicationContext初始化DispatcherServlet所依赖的九大组件。

初始化九大组件

《探索SpringMVC-九大组件》中我们知道onRefresh方法会调用initStrategies初始化策略。而该方法就会初始化DispatcherServlet的各个组件。

从设计模式看因为每个组件都有各种各样的实现因此使用的策略模式。那么初始化组件就是初始化策略。这应该也是其方法命名的缘由。

其初始化也比较简单就是从ApplicationContext中直接获取对应的组件。如果ApplicationContext中没有则使用默认的。这个默认的就配置在DispatcherServlet.properties中。

组件的声明

最常用的配置就是@EnableWebMvc。他会引入DelegatingWebMvcConfiguration配置就是这个类声明了各个组件。SpringMVC还提供了WebMvcConfigurer接口便于大家进行定制。

这些配置都是在上下文刷新时被加载到容器中的。

DispatcherServlet的请求处理

前面讲结构的时候我们提到doDispatch就是处理请求的关键方法。现在我们来看看他是怎么处理请求的。

先说明我们不会分析异步请求。

这里我将该方法的核心逻辑代码提炼出来

try {
	// 检查请求是否为multipart request。是则通过MultipartResolver进行包装
	processedRequest = checkMultipart(request);
	// 1. 确定处理当前请求的Handler
	mappedHandler = getHandler(processedRequest);
	// 2. 确定handler的适配器
	HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
	// 3.1 调用拦截器的前置方法
	if (!mappedHandler.applyPreHandle(processedRequest, response)) {
		return;
	}

	// 3.2 调用handler处理请求
	mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
	// 3.3 调用拦截器的后置方法
	mappedHandler.applyPostHandle(processedRequest, response, mv);
	// 如果mv为空则调用RequestToViewNameTranslator获取默认的viewName
	applyDefaultViewName(processedRequest, mv);
	// 4. 处理分发后的结果异常、响应视图。
	processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
}
catch (Exception ex) {
	// 确保拦截器的afterCompletion方法被调用
	triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
}
catch (Throwable err) {
	// 确保拦截器的afterCompletion方法被调用
	triggerAfterCompletion(processedRequest, response, mappedHandler,
			new NestedServletException("Handler processing failed", err));
}

下面我们来具体分析这每个步骤的细节

    1. 确定处理当前请求的Handler
      这里会遍历this.handlerMappings属性只要返回不为空则使用该HandlerMapping
    1. 确定handler的适配器
      通过HandlerMapping拿到Handler再遍历this.handlerAdapters只要找到support则返回该HandlerAdapter
  • 3.1 调用拦截器的前置方法
    遍历this.interceptorList调用HandlerInterceptor#preHandle方法。如果该方法返回false还需要调用HandlerInterceptor#afterCompletion。因为该方法返回false会阻止请求处理。而HandlerInterceptor#afterCompletion不管请求正常出完成还是异常退出都需要被调用。
  • 3.2 调用handler处理请求
    会调用HandlerAdapter#handle方法处理请求
  • 3.3 调用拦截器的后置方法
    遍历this.interceptorList调用HandlerInterceptor#postHandle方法。
    1. 处理分发后的结果异常、响应视图。
      由于异常处理器也可能返回ModelAndView因此先处理异常。异常不为空调用processHandlerException处理异常。该方法会遍历异常处理器来处理异常。
      视图不为null则render方法会遍历视图解析器解析视图不为空则返回view并调用View#render方法响应页面。

有上面的步骤我们可以看到一个常规的请求是如何被处理的。以及HandlerMapping、HandlerAdapter、HandlerExceptionResolver、ViewResolver这几个关键组件是如何被调用串联的。他们就是流水线式的干活。这里没有提到另外的几个组件原因是为了重点给大家分析DispatcherServlet的核心处理逻辑。这里给大家稍微提一下

  • LocaleResolver负责解析请求的本地语言。在DispatcherServlet中会调用request.setAttribute方法放到request中。便于后续Handler等等组件处理国际化时使用。
  • ThemeResolver只是负责解析主题名称。真正干活的是ThemeSource。说的简单点就是你在视图技术例如JSP、FreeMark页面设置的样式通过占位符引用model中对应的样式名称取值。想了解更多的同学这边请Spring MVC更多家族成员–主题(Theme)与ThemeResolver
  • MultipartResolver会对Request进行封装。例如StandardServletMultipartResolver会将request封装成StandardMultipartHttpServletRequest。
  • RequestToViewNameTranslator在HandlerAdapter没有返回ModelAndView时会通过他获取默认的viewName。
  • FlashMapManager则与重定向有关在处理请求之前会通过他获取/保存FlashMap重定向参数。

总结

  1. DispatcherServlet的结构分为两个层次。一个是javax的另一个则是spring的。
  2. DispatcherServlet的初始化基于Servlet的生命周期函数init方法初始化的。在该函数中完成WebApplicationContext的初始化并在上下文refresh之后初始化DispatcherServlet的相关组件。
  3. DispatcherServlet的处理过程大致分为三大步骤
    • 初始化请求包括封装请求、处理重定向参数
    • 处理请求从HandlerMapping找到Handler再通过Handler找到HandlerAdapter调用HandlerAdapter执行Handler处理逻辑。
    • 渲染视图响应调用视图解析器获得视图对象。调用View.render方法响应视图。
    • 分支逻辑异常处理、拦截器调用。

专栏总结

  1. DispatcherServlet的重要武器是WebApplicationContext。各种组件包括处理器都是在初始化时从WebApplicationContext获取到响应的对象的。例如RequestMappingHandlerMapping在初始化的过程中就从容器中遍历所有bean寻找@Controller/@RequestMapping。因此在DispatcherServlet正常对外提供服务时核心处理逻辑都不需要再通过上下文获取bean了。
  2. SpringMVC的工作流程就借用百度百科的图了
    在这里插入图片描述

后记

终于把专栏完成了这是第一个完整完成的专栏如果有不对的地方欢迎大家多提意见一起探讨。《探索SpringMVC》


祝大家新年快乐。

第一篇
探索SpringMVC-web上下文
上一篇
探索SpringMVC-组件之ViewResolver

阿里云国内75折 回扣 微信号:monov8
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6
标签: Spring