2018-10-26 07:15:29

Spring MVC 5.X Debug

web.xml初始化


        web.xml需要配置Spring的listener(ContextLoaderListener),ContextLoaderListener是Spring的初始化加载类,如果未在web.xml中配置“context-param”,默认会优先加载“/WEB-INF/”下加载applicationContext.xml。

    SpringMVC实现方式是Servlet,通过在web.xml配置DispatcherServlet,所有的请求都会先经过Spring处理。

    Spring MVC是一个架构上非常复杂的框架,经过抽象后的DispatcherServlet就会显得比较复杂了。但是从根本上来说DispatcherServlet就是一个普通的Servlet,所以它处理Http请求的方式就必然是重写Servlet的doXXX或service方法。

     从类图可以看到,FrameworkServlet类是DispatcherServlet第一个父类,这个类也是重写了所有的Http请求方法(七种Http请求方式),以及Servlet提供的service方法:


    在Servlet中,所有的请求方法都会先经过service方法,然后才会进入具体的doXXXX方法。

不过Spring MVC中并没有直接在service方法中处理请求的,而是在具体的请求方法,比如doPost方法:


从上图可以看到,所有的doXXX方法都会进入processRequest方法。而processRequest方法会直接回调DispatcherServlet类的doService方法。
doService经过初始化后会调用doDispatch方法来完成MVC的请求分发。Spring 5支持请求异步处理,这里暂且跳过相关分析。

doDispatch方法是整个MVC处理的核心,大致逻辑如下:

  1. checkMultipart 检测是否是Multipart请求(文件上传请求),如果是Multipart请求需要解析表单域内容并封装成一个包含了文件信息和表单内容的特殊的request对象(StandardMultipartHttpServletRequest)。StandardMultipartHttpServletRequest类实现了MultipartHttpServletRequest接口,并继承了HttpServletRequestWrapper类(这个类是用于包装HttpRequest请求对象的,如果想修改request的方法类需要继承此类并重写对应的方法),StandardMultipartHttpServletRequest类内部还实现了对文件上传的请求解析(parseRequest方法)、对request对象进行包装(主要重写了getParameterMap、getParameterNames方法不然无法从request中获取表单域中的参数值)。
  2. 查找请求处理器mappedHandler,getHandler(processedRequest);这个方法是获取当前request的处理器的,往深了跟会发现handler是有很多类型的(RequestMappingHandlerMapping、BeanNameUrlHandlerMapping、DelegatingHandlerMapping、EmptyHandlerMapping),保存在handlerMappings对象中的。跟进到AbstractHandlerMapping类的getHandler方法能看到获取handler的内部细节,其中调用类getHandlerInternal方法,这个方法内部调用了AbstractHandlerMethodMapping类的lookupHandlerMethod方法来查找请求URL地址对应的handler。通过addMatchingMappings处理后matches对象就会记录url地址和对应的具体的handler,用于后面的handler调用。其实Spring MVC的所有mapping的配置信息都是保存在了mappingRegistry对象中。如果一个url地址找到了多个handler,那么Spring MVC会从中挑选一个最佳匹配(bestMatch)的handler来处理请求。
  3. getHandlerAdapter方法会从上面找到的handler中找到一个合适的适配器类型,handlerAdapters分为RepositoryRestHandlerAdapter、RequestMappingHandlerAdapter、HttpRequestHandlerAdapter、SimpleControllerHandlerAdapter。
  4. 处理Http请求中的last-modified(只支持GET、Head请求)。
  5. 调用mappedHandler,处理Spring的拦截器(applyPreHandle方法),这里会加载用户自定义的拦截器(优先)以及内置的拦截器并调用其preHandle方法,根据拦截器的处理结果(true,false)来决定mvc请求是否继续执行,这也是为什么在拦截器preHandle方法中一定要返回正确的值,因为如果返回false程序是不会进任何的Controller方法执行就会结束掉http请求的。
  6. 根据getHandlerAdapter找到的适配器结果处理mvc请求,比如找到的适配器类型是RequestMappingHandlerAdapter,那么就会调用RequestMapping的handler处理逻辑去处理handler。具体的处理逻辑在RequestMappingHandlerAdapter类的handleInternal方法调用的invokeHandlerMethod、ServletInvocableHandlerMethod类的invokeAndHandle方法,处理完成后返回ModelAndView。Spring Controller的方法参数值是在AbstractMessageConverterMethodArgumentResolver类的readWithMessageConverters方法处理的,Spring默认有8种参数转换器(在messageConverters变量中定义的:ByteArrayHttpMessageConverter、StringHttpMessageConverter、ResourceHttpMessageConverter、ResourceRegionHttpMessageConverter、SourceHttpMessageConverter、AllEncompassingFormHttpMessageConverter、Jaxb2RootElementHttpMessageConverter、MappingJackson2HttpMessageConverter)用于参数值自动映射。顺便解答下为什么json只能映射一个参数?因为Spring根本就不考虑多参数的json映射这类情况,AbstractJackson2HttpMessageConverter类的readJavaType方法中直接调用了Jackson的json序列化,而参数就是request的InputStream,所以这个in被Jackson读取了就无法再次渲染了。
  7. 如果handler处理结果返回的ModelAndView中未包含view,设置applyDefaultViewName。
  8. 依次调用拦截器的postHandle方法。
  9. 处理handler执行结果,processDispatchResult,如果有ModelAndView就渲染view、调用拦截器的afterCompletion方法。
  10. 结束doDispatch逻辑,请求处理完成。

发表回复