前言

  开心一刻


    晚上陪老丈人吃饭,突然手机响了,我手贱按了免提……哥们:快出来喝酒!哥几个都在呢!我:今天不行,我现在陪老丈人吃饭呢。哥们:那你抓紧喝,我三杯白酒,把我岳父放倒了才出来的,你也快点。看着我老丈人的脸,我不知道该怎么回了……



猪一样的队友

遗留问题

  在关于利用maven搭建ssm的博客,我们一起来探讨下问的最多的问题
<https://www.cnblogs.com/youzhibing/p/10658540.html>中,我遗留了一个问题:Spring
mvc是何时、何地、如何将Model中的属性绑定到哪个作用域,这里的作用域指的是Servlet的四大作用域;不了解问题背景的可以回过头去看看我的上篇博文。

  明确的解答我会放到最后,在解答问题之前,我先和大家一起来捋一捋Spring mvc的工作原理。废话不多说,开始我们神秘的探险之旅!

应用示例

  在讲工作原理之前,我们先看一个简单的spring mvc(ssm)示例,以及实现的效果

  工程代码地址:ssm-web <https://gitee.com/youzhibing/spring/tree/master/ssm-web> 

  工程结构与效果如上所示,我们不做过多的探究,我们打起精神往下看本篇的重点

工作原理

  准备 - 资源的加载与初始化

    1、DispatcherServlet 静态初始化

      DispatcherServlet中有如下静态块
static { // Load default strategy implementations from properties file. //
This is currently strictly internal and not meant to be customized// by
application developers. try { ClassPathResource resource = new
ClassPathResource(DEFAULT_STRATEGIES_PATH, DispatcherServlet.class);
defaultStrategies= PropertiesLoaderUtils.loadProperties(resource); } catch
(IOException ex) {throw new IllegalStateException("Could not load
'DispatcherServlet.properties': " + ex.getMessage()); } }
      这里会将DispatcherServlet.properties中的内容读取到DispatcherServlet的属性:private
static final Properties defaultStrategies中,DispatcherServlet.properties内容如下
# Default implementation classes for DispatcherServlet's strategy interfaces.
# Used as fallback when no matching beans are found in the DispatcherServlet
context. # Not meant to be customized by application developers.
org.springframework.web.servlet.LocaleResolver=
org.springframework.web.servlet.i18n.AcceptHeaderLocaleResolver
org.springframework.web.servlet.ThemeResolver=
org.springframework.web.servlet.theme.FixedThemeResolver
org.springframework.web.servlet.HandlerMapping=
org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping,\
org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping
org.springframework.web.servlet.HandlerAdapter=
org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter,\
org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter,\
org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter
org.springframework.web.servlet.HandlerExceptionResolver=
org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerExceptionResolver,\
org.springframework.web.servlet.mvc.annotation.ResponseStatusExceptionResolver,\
org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver
org.springframework.web.servlet.RequestToViewNameTranslator=
org.springframework.web.servlet.view.DefaultRequestToViewNameTranslator
org.springframework.web.servlet.ViewResolver=
org.springframework.web.servlet.view.InternalResourceViewResolver
org.springframework.web.servlet.FlashMapManager
=org.springframework.web.servlet.support.SessionFlashMapManager View Code
      指定了DispatcherServlet策略接口的默认实现,后续DispatcherServlet初始化策略的时候会用到

    2、interceptor定义的加载


      spring启动过程中会调用InterceptorsBeanDefinitionParser的parse方法来解析出我们自定义的interceptor定义,封装成MappedInterceptor类型的bean定义,并放到spring容器中;我们可以简单的认为spring容器中已经存在了我们自定义的interceptor的bean定义

    3、DispatcherServlet初始化策略:initStrategies

      DispatcherServlet的继承图如下


      DispatcherServlet是一个Servlet,tomcat启动过程中会调用其init方法,一串的调用后,会调用DispatcherServlet的initStrategies方法
protected void initStrategies(ApplicationContext context) {
initMultipartResolver(context); initLocaleResolver(context);
initThemeResolver(context); initHandlerMappings(context);
initHandlerAdapters(context); initHandlerExceptionResolvers(context);
initRequestToViewNameTranslator(context); initViewResolvers(context);
initFlashMapManager(context); }
      实例化步骤1中的默认实现,并填充到DispatcherServlet各个属性值中

    4、DefaultAnnotationHandlerMapping的拦截器初始化


      DispatcherServlet.properties种指定了两个默认的HandlerMapping:BeanNameUrlHandlerMapping、DefaultAnnotationHandlerMapping,这两者的类继承图如下(我们暂时只关注DefaultAnnotationHandlerMapping)




      DefaultAnnotationHandlerMapping间接实现了ApplicationContextAware,那么在DefaultAnnotationHandlerMapping实例初始化过程中,会调用setApplicationContext(ApplicationContext
applicationContext)方法,一串调用后,会来到AbstractUrlHandlerMapping的initApplicationContext()
@Override protected void initApplicationContext() throws BeansException {
extendInterceptors(this.interceptors); detectMappedInterceptors(this
.mappedInterceptors); initInterceptors(); }
      初始化了DefaultAnnotationHandlerMapping的拦截器:interceptor

    我们来看下具体的初始化过程,看看上面的顺序是否只是我个人的臆想?


    可以看到,初始化顺序就是我们上面说的,不是我个人的意淫;此时的DefaultAnnotationHandlerMapping中有我们自定义的MyInterceptor。初始化过程我们需要关注的就是上述这些,下面我们一起看看具体请求的过程

  请求的处理

    请求从servlet的service开始,一路到DispatcherServlet的doDispatch,如下图



    doDispatch
/** * Process the actual dispatching to the handler.
将请求分发到具体的handler,也就是我们的controller * <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 {
processedRequest= checkMultipart(request); multipartRequestParsed =
processedRequest != request; // Determine handler for the current request.
决定哪个handler来处理当前的请求//
mappedHandler是由handler和interceptor集合组成的一个执行链,有点类似FilterChain mappedHandler =
getHandler(processedRequest);if (mappedHandler == null ||
mappedHandler.getHandler() ==null) { noHandlerFound(processedRequest, response);
return; } // Determine handler adapter for the current request.
决定哪个adapter来处理当前的请求// handlerMapping是找出适配的handler,而真正回调handler的是adapter
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 (logger.isDebugEnabled()) { String requestUri =
urlPathHelper.getRequestUri(request); logger.debug("Last-Modified value for ["
+ requestUri + "] is: " + lastModified); } if (new ServletWebRequest(request,
response).checkNotModified(lastModified) && isGet) { return; } } //
handler的前置处理,也就是调用适配当前url的interceptor的preHandler方法 if (!
mappedHandler.applyPreHandle(processedRequest, response)) {return; } try { //
Actually invoke the handler. 真正调用handler mv = ha.handle(processedRequest,
response, mappedHandler.getHandler()); }finally { if
(asyncManager.isConcurrentHandlingStarted()) {return; } }
applyDefaultViewName(request, mv);//
handler的后置处理,也就是调用适配当前url的interceptor的postHandler方法
mappedHandler.applyPostHandle(processedRequest, response, mv); }catch
(Exception ex) { dispatchException= ex; } //
处理handler返回的结果,会调用适配当前url的interceptor的afterCompletion方法// 这里会将响应结果返回给请求者
processDispatchResult(processedRequest, response, mappedHandler, mv,
dispatchException); }catch (Exception ex) {
triggerAfterCompletion(processedRequest, response, mappedHandler, ex); }catch
(Error err) { triggerAfterCompletionWithError(processedRequest, response,
mappedHandler, err); }finally { if (asyncManager.isConcurrentHandlingStarted())
{// Instead of postHandle and afterCompletion
mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
return; } // Clean up any resources used by a multipart request. if
(multipartRequestParsed) { cleanupMultipart(processedRequest); } } } View Code

      handlerMapping具体如何找到匹配当前url的handler(一般而言就是我们的controller)、handlerAdapter具体如何回调真正的handler,有兴趣的可以自行去跟下,我就不跟了。我们具体看下processDispatchResult(processedRequest,
response, mappedHandler, mv, dispatchException); 这个与我们最初的疑问有关

    processDispatchResult


      可以看到model中的persons会被设置到request的attributes中,然后转发请求到show_person.jsp,转发过程中request作用域的变量仍然有效,所以show_person.jsp中的jstl标签和el表达式能够取到persons变量,最后将show_person.jsp中的内容填充好之后的静态内容返回给请求者;至此就完成了一次请求的响应

问题解答

  回到我们开篇的疑问:Spring mvc是何时、何地、如何将Model中的属性绑定到哪个作用域?想必大家已经知道答案了

  Controller中的model、ModelMap的注入由spring
mvc完成,这个不是请求传入的参数,用于绑定变量到Servlet作用域;默认情况下,在DispatcherServlet调用了真正的handler之后,将结果返回给请求者的过程中,将model、modelMap中的变量设置到了request的attributes中,转发的过程中,request中的变量仍然有效,所以show_person.jsp中能取到persons这个变量,自此疑问得到解答

总结

  1、Spring MVC工作原理图

    图是用的别人的,具体是谁的我也不记得了(捂脸)




  2、DefaultAnnotationHandlerMapping在spring3.2中被废弃,替换成了RequestMappingHandlerMapping

友情链接
KaDraw流程图
API参考文档
OK工具箱
云服务器优惠
阿里云优惠券
腾讯云优惠券
华为云优惠券
站点信息
问题反馈
邮箱:[email protected]
QQ群:637538335
关注微信