本篇将涉及,通过本篇可以对整个springmvc模块有一个清晰的视角,在日常开发中可以快速定位问题和源码

  1. springMVC启动初始化过程分析
  2. 处理请求过程分析
  3. 参数解析、返回值处理

springMVC应用启动初始化过程分析

初始化过程应该分为三步:

  • 第一步,创建两个spring的ApplicationContext,配置DispatcherServlet、filter、ContextLoaderListener到servlet容器中
  • 第二步,servlet容器启动后,调用ContextLoaderListener#contextInitialized初始化必要的bean(如DAO、service相关的bean)
  • 第三步,调用DispatcherServlet#init方法进一步初始化必要的bean(如HandlerMappings、HandlerMappingAdapter等)

第一步

主要完成以下工作:

  1. 通过配置初始化两个容器,分别为spring容器、springMVC容器。(关于为什么需要两个容器,个人认为是为了在两个DispatcherServlet中共享同一个spring容器)
  2. 注册DispatcherServlet到servlet容器中,对外提供服务,处理客户端请求
  3. 注册Filter到servlet容器中

第二步

主要完成以下工作:

  1. 初始化父容器
  2. 绑定到servletContext,在应用中全局共享

第三步

在这一步中,分为两个阶段:

  1. 初始化属于springmvc的ApplicationContext、并添加SourceFilteringListener作为spring容器时间监听器
  2. 在完成初始化属于springmvc的ApplicationContext的工作后,SourceFilteringListener监听到容器时间,调用onApplicationEvent方法,完成剩余的初始化工作(包括初始化HandlerMappings、HandlerMappingAdapter等属于MVC领域的bean)

SourceFilteringListener的职责

先来看一下其类关系图

SourceFilteringListener作为EventListener的实现类,在servlet的ApplicationContext初始化后,将收到事件回调完成剩余的初始化工作

protected void initStrategies(ApplicationContext context) {initMultipartResolver(context);initLocaleResolver(context);initThemeResolver(context);initHandlerMappings(context);initHandlerAdapters(context);initHandlerExceptionResolvers(context);initRequestToViewNameTranslator(context);initViewResolvers(context);initFlashMapManager(context);
}
复制代码

在这些初始化方法中,注册到spring容器中的bean由注解@EnableWebMVC导入。 导入工作委托给了DelegatingWebMvcConfiguration类

以上就是springMVC的初始化过程的整体脉络

springMVC处理请求过程分析

整体流程

整个过程相对比较复杂,整个逻辑在DispatcherServlet#doDispatcher方法中实现,该方法内部将具体工作委托给了RequestMappingHandlerMapping、HandlerExcutionChain、RequestMappingHandlerAdapter、ViewResolver、View等几个核心类来完成任务。

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.// 获取HandlerExcutionChainmappedHandler = getHandler(processedRequest);if (mappedHandler == null || mappedHandler.getHandler() == null) {noHandlerFound(processedRequest, response);return;}// 获取HandlerAdapter// 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 (logger.isDebugEnabled()) {logger.debug("Last-Modified value for [" + getRequestUri(request) + "] is: " + lastModified);}if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {return;}}// 调用拦截器前置处理if (!mappedHandler.applyPreHandle(processedRequest, response)) {return;}// 真正调用HandlerMethod(即Controller方法),内部涉及了参数解析(处理@RequestParams、@PathVariables注解原理)、参数类型转换、HandlerMethod执行结果处理(@ResponseBody注解原理)这几个步骤。后面内容我们详细剖析这个函数的内部机制// 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);}}}
}
复制代码

根据请求寻找对应的HandlerMethod

具体寻找过程的代码细节如下:

  1. 首先mappingRegistry(类MappingRegistry的实例)实例变量存放了所有的HandlerMethod(在RequestMappingHandlerMapping#afterPropertiesSet()方法中初始化完成,以RequestMappingInfo实例保存在mappingRegistry.urlLookup实例变量中),之后在请求到来时通过请求URI从mappingRegistry获取
  2. 如果在使用路径参数@PathVariables的时候,需要遍历所有Controller方法(因为初始化的时候是以/user/{name}作为key保存在urlLookup变量中的,而实际的请求路径是/user/tom。无法通过this.mappingRegistry.getMappingsByUrl(lookupPath)直接获取到)。这里会涉及到Ant风格解析。O(n)的时间复杂度。效率极低
protected HandlerMethod lookupHandlerMethod(String lookupPath, HttpServletRequest request) throws Exception {List<Match> matches = new ArrayList<Match>();List<T> directPathMatches = this.mappingRegistry.getMappingsByUrl(lookupPath);if (directPathMatches != null) {addMatchingMappings(directPathMatches, matches, request);}if (matches.isEmpty()) {// No choice but to go through all mappings...// 如果没有找到,则遍历所有Controller方法。这里会涉及到Ant风格解析。O(n)的时间复杂度。效率极低// 在使用路径参数@PathVariables的时候,会进入到这段逻辑addMatchingMappings(this.mappingRegistry.getMappings().keySet(), matches, request);}if (!matches.isEmpty()) {Comparator<Match> comparator = new MatchComparator(getMappingComparator(request));Collections.sort(matches, comparator);if (logger.isTraceEnabled()) {logger.trace("Found " + matches.size() + " matching mapping(s) for [" +lookupPath + "] : " + matches);}Match bestMatch = matches.get(0);if (matches.size() > 1) {if (CorsUtils.isPreFlightRequest(request)) {return PREFLIGHT_AMBIGUOUS_MATCH;}Match secondBestMatch = matches.get(1);if (comparator.compare(bestMatch, secondBestMatch) == 0) {Method m1 = bestMatch.handlerMethod.getMethod();Method m2 = secondBestMatch.handlerMethod.getMethod();throw new IllegalStateException("Ambiguous handler methods mapped for HTTP path '" +request.getRequestURL() + "': {" + m1 + ", " + m2 + "}");}}// 设置属性到ServletHttpRequest实例中,在解析@PathVariables时使用handleMatch(bestMatch.mapping, lookupPath, request);return bestMatch.handlerMethod;}else {return handleNoMatch(this.mappingRegistry.getMappings().keySet(), lookupPath, request);}
}
复制代码
protected void handleMatch(RequestMappingInfo info, String lookupPath, HttpServletRequest request) {super.handleMatch(info, lookupPath, request);String bestPattern;Map<String, String> uriVariables;Map<String, String> decodedUriVariables;Set<String> patterns = info.getPatternsCondition().getPatterns();if (patterns.isEmpty()) {bestPattern = lookupPath;uriVariables = Collections.emptyMap();decodedUriVariables = Collections.emptyMap();}else {bestPattern = patterns.iterator().next();// 使用Ant风格解析路径参数uriVariables = getPathMatcher().extractUriTemplateVariables(bestPattern, lookupPath);decodedUriVariables = getUrlPathHelper().decodePathVariables(request, uriVariables);}request.setAttribute(BEST_MATCHING_PATTERN_ATTRIBUTE, bestPattern);request.setAttribute(HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE, decodedUriVariables);if (isMatrixVariableContentAvailable()) {Map<String, MultiValueMap<String, String>> matrixVars = extractMatrixVariables(request, uriVariables);request.setAttribute(HandlerMapping.MATRIX_VARIABLES_ATTRIBUTE, matrixVars);}if (!info.getProducesCondition().getProducibleMediaTypes().isEmpty()) {Set<MediaType> mediaTypes = info.getProducesCondition().getProducibleMediaTypes();request.setAttribute(PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE, mediaTypes);}
}
复制代码

HandlerMethod执行细节

参数解析以及类型转换

主要是通过HandlerMethodArgumentResolverComposite这个特殊的HandlerMethodArgumentResolver聚合了多个HandlerMethodArgumentResolver来解析参数、其内部将调用RequestParamMethodArgumentResolver、PathVariableMapMethodArgumentResolver等来处理参数解析逻辑

返回结果处理

主要是通过HandlerMethodReturnValueHandlerComposite这个特殊的HandlerMethodReturnValueHandler聚合了多个HandlerMethodReturnValueHandler来处理返回结果,其内部将调用RequestResponseBodyMethodProcessor处理@ResponseBody、@RestController标记的方法和类来完成RESTful接口的结果返回

总结

以上就是整个springmvc的工作流程,包括了初始化、请求处理两个步骤。在请求处理的过程中又涉及到了参数解析、参数类型转换、返回结果处理这些流程。由于整个代码比较繁多,这里就不列举所有的流程(视图渲染、异常处理这些细节)通过本篇的主体流程图可以快速定位到具体的代码。后续有需要查看更多细节可参考本篇快速定位

转载于:https://juejin.im/post/5c2c0a39e51d4563d9209c33

springMVC源码分析相关推荐

  1. SpringMVC源码分析(4)剖析DispatcherServlet重要组件

    简单介绍了一个请求的处理过程, 简略描述了调用过程,并没有涉及过多细节,如url匹配,报文解析转换等. <SpringMVC源码分析(2)DispatcherServlet的初始化>:介绍 ...

  2. springMVC源码分析--访问请求执行ServletInvocableHandlerMethod和InvocableHandlerMethod

    在之前一篇博客中 springMVC源码分析--RequestMappingHandlerAdapter(五)我们已经简单的介绍到具体请求访问的执行某个Controller中的方法是在RequestM ...

  3. 简单直接让你也读懂springmvc源码分析(3.1)-- HandlerMethodReturnValueHandler

    该源码分析系列文章分如下章节: springmvc源码分析(1)-- DispatcherServlet springmvc源码分析(2)-- HandlerMapping springmvc源码分析 ...

  4. SpringMVC源码分析_框架原理图

                                                                                 SpringMVC源码分析_框架原理图     ...

  5. SpringMVC源码分析_1 SpringMVC容器启动和加载原理

                                                                    SpringMVC源码分析_1 SpringMVC启动和加载原理     ...

  6. Springmvc源码分析、底层原理

    1.Springmvc是如何找到Controller的? 首先在请求过来时,会先进入DispatcherServlet进行请求分发,执行DispatcherServlet类中的doDispatch() ...

  7. SpringMVC源码分析系列[转]

    说到java的mvc框架,struts2和springmvc想必大家都知道,struts2的设计基本上完全脱离了Servlet容器,而springmvc是依托着Servlet容器元素来设计的,同时sp ...

  8. SpringMVC源码分析系列

    说到java的mvc框架,struts2和springmvc想必大家都知道,struts2的设计基本上完全脱离了Servlet容器,而springmvc是依托着Servlet容器元素来设计的,同时sp ...

  9. SpringMVC源码分析(二)

    1.DispatcherServlet源码分析 1.@InitBinder(续) 1.DataBinder概述 package org.springframework.validation; 此类所在 ...

  10. ZooKeeper面试题(2020最新版,springmvc源码分析pdf百度云

    10. ACL 权限控制机制 UGO(User/Group/Others) 目前在 Linux/Unix 文件系统中使用,也是使用最广泛的权限控制方式.是一种粗粒度的文件系统权限控制模式. ACL(A ...

最新文章

  1. 【scala初学】scala symbol 符号 -3
  2. 光流 | 基于LK(Lucas-Kanade)光流算法的运动目标检测
  3. Python——科赫曲线绘制
  4. php static与self,PHP5.3新特性static与self区别
  5. linux 找不到php命令,bash scp:未找到命令的解决方法
  6. qt4 mysql_qt4连接mysql_MySQL
  7. 从 Java 替代品到打造完整生态,Kotlin 10 岁了!
  8. 【2020模拟考试T3】【PAT乙】1028 人口普查 (20分) 字符串比较
  9. SpringMVC环境搭建——HelloWorld
  10. Unity UniWebView内置浏览器插件
  11. 【高性能计算背景】《并行计算教程简介》翻译 - 中文 - 1 / 4
  12. 牛人搜集的常用的资源类网站及68个各类资源网站汇总
  13. 网贴翻译 聆听国外的声音
  14. 解决在SQLYog中执行SQL语句会提示错误的信息,但数据能查出来
  15. 财务 计算机网络,计算机网络在财务管理中的应用
  16. android魅族升级,Android8.0快来了,魅族手机终于要升级 7.0了!
  17. 三方线上美食城|基于Springboot的三方线上美食商城系统
  18. 单词翻译程序 go实现
  19. 基于YOLOv3的口罩佩戴检测
  20. 排除计算机网络故障原则,计算机网络故障排除理论与实践研究

热门文章

  1. 如何对shell脚本进行批量注释
  2. 学习开发语言 python 资料
  3. RUBY ON RAILS 插件收录: CACHE:Sweeper Generator
  4. org.hibernate.LazyInitializationException: failed to lazily initialize a collection of role:no sessi
  5. 用StringBuilder 或StringBuffer: 把字符串“ABCDE”,转变成字符串“A,B,C,D” (注意,最后一个E是需要删除的)
  6. JVM—如何利用虚拟机栈进行函数调用?
  7. 为什么不能根据返回类型来区分重载
  8. 《DSP using MATLAB》Problem 7.4
  9. Cisco配置单臂路由及静态路由
  10. 22-React JSX语法