SpringMVC作为控制层框架,具体的作用就不在此赘述了,本文主要针对其处理请求流程的原理来做一次较为细致的讲解
首先来看经典的MVC的三层架构,下面是一个模拟请求的调用和返回,我们把重点放在中间的控制层:
SpringMVC就是用于管理这一层,负责将视图层发来的请求发送给下一层,然后再将结果返回,听起来很简单,但是真的这么简单吗?接下来,我们就来渐进式的分析,来研究其工作的原理
首先我们选用的是springboot的2.1.4发布版,查看一下依赖树,发现springmvc的版本是5.1.6的版本,正好可以和现在网络上大部分针对3.x版本的源码讲解做一个比较
这里为了不做多余的功夫,一切从简,就只添加了以下的类:
具体的类信息如下
@RestController public class DemoController { private final DemoService demoService; public DemoController(DemoService demoService) { this.demoService = demoService; } @RequestMapping("hi") public String sayHi() { return demoService.getInfo(); } } 复制代码
@Service public class DemoService { public String getInfo() { return "Hello World..."; } } 复制代码
我们启动测试一下:
一切正常,接下来我们就要开始着手分析
从刚才的测试我们会发现,我们发送了一个请求(url)给springmvc,然后springmvc就执行了我们定义的sayHi()方法,接着我们的浏览器就收到了响应,这个过程究竟是怎么实现的呢?别急,我们在sayHi()方法处打上断点,进入调试模式
我们依然发起 localhost:8080/hi
这个请求,程序停在了断点位置,同时也得到了一条调用链。我们沿着调用链从启动位置向上找,我们忽略所有core包下的内容,只找web包下的类,发现请求经过HttpServlet层层封装,最终交给了一个叫DispatcherServlet的类来处理
这个DispatcherServlet内部有一个doService方法,我们就以此作为起点,来分析这个请求的过程是怎么样的
protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception { logRequest(request); // 保留属性快照,以便恢复 Map<String, Object> attributesSnapshot = null; if (WebUtils.isIncludeRequest(request)) { attributesSnapshot = new HashMap<>(); Enumeration<?> attrNames = request.getAttributeNames(); while (attrNames.hasMoreElements()) { String attrName = (String) attrNames.nextElement(); if (this.cleanupAfterInclude || attrName.startsWith(DEFAULT_STRATEGIES_PREFIX)) { attributesSnapshot.put(attrName, request.getAttribute(attrName)); } } } // 设置属性,使之可供处理程序和视图对象使用 request.setAttribute(WEB_APPLICATION_CONTEXT_ATTRIBUTE, getWebApplicationContext()); request.setAttribute(LOCALE_RESOLVER_ATTRIBUTE, this.localeResolver); request.setAttribute(THEME_RESOLVER_ATTRIBUTE, this.themeResolver); request.setAttribute(THEME_SOURCE_ATTRIBUTE, getThemeSource()); if (this.flashMapManager != null) { FlashMap inputFlashMap = this.flashMapManager.retrieveAndUpdate(request, response); if (inputFlashMap != null) { request.setAttribute(INPUT_FLASH_MAP_ATTRIBUTE, Collections.unmodifiableMap(inputFlashMap)); } request.setAttribute(OUTPUT_FLASH_MAP_ATTRIBUTE, new FlashMap()); request.setAttribute(FLASH_MAP_MANAGER_ATTRIBUTE, this.flashMapManager); } try { // 执行具体的分发操作 doDispatch(request, response); } finally { if (!WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) { // 如果是include请求,通过之间的快照来恢复属性 if (attributesSnapshot != null) { restoreAttributesAfterInclude(request, attributesSnapshot); } } } } 复制代码
整个操作具体可分为4个部分:
这里先解释一下什么是include请求,include请求指在一个Servlet请求中包含了另一个请求,清楚了这一点,我们接着看处理流程,核心方法就是一个doDispatch方法,我们直接继续进入该方法
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); // true表示开启了多部分解析 multipartRequestParsed = (processedRequest != request); // 来确定具体的处理程序,返回结果是一个执行链 mappedHandler = getHandler(processedRequest); if (mappedHandler == null) { noHandlerFound(processedRequest, response); return; } // 来确定处理器适配器 HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler()); // 检查最后修改的标头 String method = request.getMethod(); boolean isGet = "GET".equals(method); if (isGet || "HEAD".equals(method)) { long lastModified = ha.getLastModified(request, mappedHandler.getHandler()); // 根据上次修改的时间戳来判断资源是否已被修改,如果资源未被修改,则直接返回,浏览器将使用缓存 if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) { return; } } // 执行前置拦截方法 if (!mappedHandler.applyPreHandle(processedRequest, response)) { return; } // 触发具体的逻辑方法 mv = ha.handle(processedRequest, response, mappedHandler.getHandler()); // 返回true表示正在进行并发处理,应保持响应打开状态 if (asyncManager.isConcurrentHandlingStarted()) { return; } applyDefaultViewName(processedRequest, mv); // 执行后置处理 mappedHandler.applyPostHandle(processedRequest, response, mv); } catch (Exception ex) { dispatchException = ex; } catch (Throwable err) { 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()) { if (mappedHandler != null) { mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response); } } else { if (multipartRequestParsed) { cleanupMultipart(processedRequest); } } } } 复制代码
这个方法就是处理具体的分发流程,也就是说将我们的url对应到具体的controller中的方法中,具体的执行流程就在上面,对一些重要的步骤我已经标注了注释,这里抛出掉多部分请求处理和异常捕获的部分,整个执行流程可以分为以下步骤:
这就是整个springmvc的核心流程,我们这里只关注第一个部分:获取请求对应的执行链,也就是getHandler()方法
这个方法虽然简单,但是因为这是整个方法的核心部分,我认为还是有必要拿出来说的,我们来看这个方法的源码:
@Nullable protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception { if (this.handlerMappings != null) { for (HandlerMapping mapping : this.handlerMappings) { HandlerExecutionChain handler = mapping.getHandler(request); if (handler != null) { return handler; } } } return null; } 复制代码
整个流程很容易懂,就是单纯的遍历handlerMappings这个集合,然后根据请求找到对应的处理器,听起来很简单,但是这里面是有门道的,我们先来看handlerMappings这个属性,其属性定义如下:
private List<HandlerMapping> handlerMappings; 复制代码
原来是一个HandlerMapping的集合,就是下面这样:
这些HandlerMapping都是用于定义 请求到处理器的映射,这一点很重要,因为我们的每一个请求最终都是要对应到具体的处理器方法上的,而我们真正要看的就是RequestMappingHandlerMapping这个类,这里面封装了我们自己编写的controller中的方法,在这个类中,有下面这样一个属性:
private Map<String, Predicate<Class<?>>> pathPrefixes = new LinkedHashMap<>(); 复制代码
这里面保存的就是我们的请求到具体方法的映射,整个RequestMappingHandlerMapping类就是通过@RequestMapping注解来创建我们的RequestMappingInfo实例,而RequestMappingInfo这个实例就封装了我们具体需要执行的方法,在调用时就可以通过反射来创建具体的controller实例,然后调用对应的方法即可
这些映射关系都是在一开始随着ApplicationContext进行加载,当加载完成后,我们的请求映射关系也随之确定了下来,接着就可以根据请求来判断具体要调用的是哪些方法了
最后再回来看getHandler()方法,这个方法最终会返回一个HandlerExecutionChain,其中封装了处理器和拦截器,如下:
private final Object handler; @Nullable private HandlerInterceptor[] interceptors; 复制代码
接下来的调用就完全依赖这两个属性,相信后面的步骤大家也应该都了解了
最后再来看一个收尾的方法,这个方法就是用来对最终结果做处理,主要是负责将模型数据渲染到视图中,在如今前后端分离的项目中,这个方法的价值也越来越小了,不过还是有必要了解一下的,整个方法的源码我这里就不放了,其步骤主要就是两步:
没什么太难理解的地方,解析的含义就是根据视图名来找到对应的视图对象,渲染就是把模型数据填充到视图中,我这里就放一段解析视图名的resolveViewName()方法中的一段,其中会遍历查找以寻找合适的视图解析器:
if (this.viewResolvers != null) { for (ViewResolver viewResolver : this.viewResolvers) { View view = viewResolver.resolveViewName(viewName, locale); if (view != null) { return view; } } } 复制代码
整个springmvc的核心流程已经讲完了,最后放一张具体的流程图来加深大家的理解