springMVC源码分析
本篇将涉及,通过本篇可以对整个springmvc模块有一个清晰的视角,在日常开发中可以快速定位问题和源码
- springMVC启动初始化过程分析
- 处理请求过程分析
- 参数解析、返回值处理
springMVC应用启动初始化过程分析
初始化过程应该分为三步:
- 第一步,创建两个spring的ApplicationContext,配置DispatcherServlet、filter、ContextLoaderListener到servlet容器中
- 第二步,servlet容器启动后,调用ContextLoaderListener#contextInitialized初始化必要的bean(如DAO、service相关的bean)
- 第三步,调用DispatcherServlet#init方法进一步初始化必要的bean(如HandlerMappings、HandlerMappingAdapter等)
第一步
主要完成以下工作:
- 通过配置初始化两个容器,分别为spring容器、springMVC容器。(关于为什么需要两个容器,个人认为是为了在两个DispatcherServlet中共享同一个spring容器)
- 注册DispatcherServlet到servlet容器中,对外提供服务,处理客户端请求
- 注册Filter到servlet容器中
第二步
主要完成以下工作:
- 初始化父容器
- 绑定到servletContext,在应用中全局共享
第三步
在这一步中,分为两个阶段:
- 初始化属于springmvc的ApplicationContext、并添加SourceFilteringListener作为spring容器时间监听器
- 在完成初始化属于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
具体寻找过程的代码细节如下:
- 首先mappingRegistry(类MappingRegistry的实例)实例变量存放了所有的HandlerMethod(在RequestMappingHandlerMapping#afterPropertiesSet()方法中初始化完成,以RequestMappingInfo实例保存在mappingRegistry.urlLookup实例变量中),之后在请求到来时通过请求URI从mappingRegistry获取
- 如果在使用路径参数@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源码分析相关推荐
- SpringMVC源码分析(4)剖析DispatcherServlet重要组件
简单介绍了一个请求的处理过程, 简略描述了调用过程,并没有涉及过多细节,如url匹配,报文解析转换等. <SpringMVC源码分析(2)DispatcherServlet的初始化>:介绍 ...
- springMVC源码分析--访问请求执行ServletInvocableHandlerMethod和InvocableHandlerMethod
在之前一篇博客中 springMVC源码分析--RequestMappingHandlerAdapter(五)我们已经简单的介绍到具体请求访问的执行某个Controller中的方法是在RequestM ...
- 简单直接让你也读懂springmvc源码分析(3.1)-- HandlerMethodReturnValueHandler
该源码分析系列文章分如下章节: springmvc源码分析(1)-- DispatcherServlet springmvc源码分析(2)-- HandlerMapping springmvc源码分析 ...
- SpringMVC源码分析_框架原理图
SpringMVC源码分析_框架原理图 ...
- SpringMVC源码分析_1 SpringMVC容器启动和加载原理
SpringMVC源码分析_1 SpringMVC启动和加载原理 ...
- Springmvc源码分析、底层原理
1.Springmvc是如何找到Controller的? 首先在请求过来时,会先进入DispatcherServlet进行请求分发,执行DispatcherServlet类中的doDispatch() ...
- SpringMVC源码分析系列[转]
说到java的mvc框架,struts2和springmvc想必大家都知道,struts2的设计基本上完全脱离了Servlet容器,而springmvc是依托着Servlet容器元素来设计的,同时sp ...
- SpringMVC源码分析系列
说到java的mvc框架,struts2和springmvc想必大家都知道,struts2的设计基本上完全脱离了Servlet容器,而springmvc是依托着Servlet容器元素来设计的,同时sp ...
- SpringMVC源码分析(二)
1.DispatcherServlet源码分析 1.@InitBinder(续) 1.DataBinder概述 package org.springframework.validation; 此类所在 ...
- ZooKeeper面试题(2020最新版,springmvc源码分析pdf百度云
10. ACL 权限控制机制 UGO(User/Group/Others) 目前在 Linux/Unix 文件系统中使用,也是使用最广泛的权限控制方式.是一种粗粒度的文件系统权限控制模式. ACL(A ...
最新文章
- 【scala初学】scala symbol 符号 -3
- 光流 | 基于LK(Lucas-Kanade)光流算法的运动目标检测
- Python——科赫曲线绘制
- php static与self,PHP5.3新特性static与self区别
- linux 找不到php命令,bash scp:未找到命令的解决方法
- qt4 mysql_qt4连接mysql_MySQL
- 从 Java 替代品到打造完整生态,Kotlin 10 岁了!
- 【2020模拟考试T3】【PAT乙】1028 人口普查 (20分) 字符串比较
- SpringMVC环境搭建——HelloWorld
- Unity UniWebView内置浏览器插件
- 【高性能计算背景】《并行计算教程简介》翻译 - 中文 - 1 / 4
- 牛人搜集的常用的资源类网站及68个各类资源网站汇总
- 网贴翻译 聆听国外的声音
- 解决在SQLYog中执行SQL语句会提示错误的信息,但数据能查出来
- 财务 计算机网络,计算机网络在财务管理中的应用
- android魅族升级,Android8.0快来了,魅族手机终于要升级 7.0了!
- 三方线上美食城|基于Springboot的三方线上美食商城系统
- 单词翻译程序 go实现
- 基于YOLOv3的口罩佩戴检测
- 排除计算机网络故障原则,计算机网络故障排除理论与实践研究
热门文章
- 如何对shell脚本进行批量注释
- 学习开发语言 python 资料
- RUBY ON RAILS 插件收录: CACHE:Sweeper Generator
- org.hibernate.LazyInitializationException: failed to lazily initialize a collection of role:no sessi
- 用StringBuilder 或StringBuffer: 把字符串“ABCDE”,转变成字符串“A,B,C,D” (注意,最后一个E是需要删除的)
- JVM—如何利用虚拟机栈进行函数调用?
- 为什么不能根据返回类型来区分重载
- 《DSP using MATLAB》Problem 7.4
- Cisco配置单臂路由及静态路由
- 22-React JSX语法