探索SpringMVC-DispatcherServlet

前言

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

DispatcherSerlvet的结构

探索SpringMVC-DispatcherServlet_第1张图片
图释:黄色部分是javax.servlet包的,绿色部分是org.springframework的。而红色是DispatcherServlet的九大组件。

结构分析

javax的设计

  • GenericServlet:通用的Servlet
    这是个抽象类,意在提供一个通用的、与协议无关的Servlet基类。他实现了两个接口:Servlet和ServletConfig。如果你非要说设计在一个接口里面不行吗?可以。但是我们总是希望最求一种高内聚低耦合的设计,希望减少软件系统的维护成本。从设计原则上讲,这里是接口隔离原则。只向客户端提供其需要的行为。
  • HttpServlet:Http协议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-DispatcherServlet_第2张图片

后记

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


祝大家新年快乐。

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

你可能感兴趣的:(探索SpringMVC,servlet,spring,java)