这一步步是由请求触发的,所以入口为DispatcherServlet 的核心方法为doService(),doService()中的核心逻辑由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 {// 1.检查是否是文件上传的请求processedRequest = checkMultipart(request);multipartRequestParsed = (processedRequest != request);// Determine handler for the current request.// 2.取得处理当前请求的controller,这里也称为hanlder,处理器,//    第一个步骤的意义就在这里体现了.这里并不是直接返回controller,//  而是返回的HandlerExecutionChain请求处理器链对象,//   该对象封装了handler和interceptors.mappedHandler = getHandler(processedRequest);// 如果handler为空,则返回404if (mappedHandler == null) {noHandlerFound(processedRequest, response);return;}// Determine handler adapter for the current request.//3. 获取处理request的处理器适配器handler adapterHandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());// Process last-modified header, if supported by the handler.// 处理 last-modified 请求头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;}// Actually invoke the handler.// 4.实际的处理器处理请求,返回结果视图对象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);}}}
}

getHandler(processedRequest)方法实际上就是从HandlerMapping 中找到url 和Controller 的对应关系。也就是Map<url,Controller>。我们知道,最终处理Request的是Controller 中的方法,我们现在只是知道了Controller,我们如何确认Controller中处理Request 的方法呢?继续往下看。

从Map<urls,beanName>中取得Controller 后,经过拦截器的预处理方法,再通过反射获取该方法上的注解和参数,解析方法和参数上的注解,然后反射调用方法获取ModelAndView 结果视图。最后,调用的就是RequestMappingHandlerAdapter 的handle()中的核心逻辑由handleInternal(request, response, handler)实现。

@Override
protected ModelAndView handleInternal(HttpServletRequest request,HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {ModelAndView mav;checkRequest(request);// Execute invokeHandlerMethod in synchronized block if required.if (this.synchronizeOnSession) {HttpSession session = request.getSession(false);if (session != null) {Object mutex = WebUtils.getSessionMutex(session);synchronized (mutex) {mav = invokeHandlerMethod(request, response, handlerMethod);}}else {// No HttpSession available -> no mutex necessarymav = invokeHandlerMethod(request, response, handlerMethod);}}else {// No synchronization on session demanded at all...mav = invokeHandlerMethod(request, response, handlerMethod);}if (!response.containsHeader(HEADER_CACHE_CONTROL)) {if (getSessionAttributesHandler(handlerMethod).hasSessionAttributes()) {applyCacheSeconds(response, this.cacheSecondsForSessionAttributeHandlers);}else {prepareResponse(response);}}return mav;
}

整个处理过程中最核心的逻辑其实就是拼接Controller 的url 和方法的url,与Request的url 进行匹配,找到匹配的方法。

protected HandlerMethod getHandlerInternal(HttpServletRequest request) throws Exception {String lookupPath = getUrlPathHelper().getLookupPathForRequest(request);if (logger.isDebugEnabled()) {logger.debug("Looking up handler method for path " + lookupPath);}this.mappingRegistry.acquireReadLock();try {HandlerMethod handlerMethod = lookupHandlerMethod(lookupPath, request);if (logger.isDebugEnabled()) {if (handlerMethod != null) {logger.debug("Returning handler method [" + handlerMethod + "]");}else {logger.debug("Did not find handler method for [" + lookupPath + "]");}}return (handlerMethod != null ? handlerMethod.createWithResolvedBean() : null);}finally {this.mappingRegistry.releaseReadLock();}
}

通过上面的代码分析,已经可以找到处理Request 的Controller 中的方法了,现在看如何解析该方法上的参数,并反射调用该方法。

/** 获取处理请求的方法,执行并返回结果视图 **/
@Nullable
protected ModelAndView invokeHandlerMethod(HttpServletRequest request,HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {ServletWebRequest webRequest = new ServletWebRequest(request, response);try {WebDataBinderFactory binderFactory = getDataBinderFactory(handlerMethod);ModelFactory modelFactory = getModelFactory(handlerMethod, binderFactory);ServletInvocableHandlerMethod invocableMethod = createInvocableHandlerMethod(handlerMethod);if (this.argumentResolvers != null) {invocableMethod.setHandlerMethodArgumentResolvers(this.argumentResolvers);}if (this.returnValueHandlers != null) {invocableMethod.setHandlerMethodReturnValueHandlers(this.returnValueHandlers);}invocableMethod.setDataBinderFactory(binderFactory);invocableMethod.setParameterNameDiscoverer(this.parameterNameDiscoverer);ModelAndViewContainer mavContainer = new ModelAndViewContainer();mavContainer.addAllAttributes(RequestContextUtils.getInputFlashMap(request));modelFactory.initModel(webRequest, mavContainer, invocableMethod);mavContainer.setIgnoreDefaultModelOnRedirect(this.ignoreDefaultModelOnRedirect);AsyncWebRequest asyncWebRequest = WebAsyncUtils.createAsyncWebRequest(request, response);asyncWebRequest.setTimeout(this.asyncRequestTimeout);WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);asyncManager.setTaskExecutor(this.taskExecutor);asyncManager.setAsyncWebRequest(asyncWebRequest);asyncManager.registerCallableInterceptors(this.callableInterceptors);asyncManager.registerDeferredResultInterceptors(this.deferredResultInterceptors);if (asyncManager.hasConcurrentResult()) {Object result = asyncManager.getConcurrentResult();mavContainer = (ModelAndViewContainer) asyncManager.getConcurrentResultContext()[0];asyncManager.clearConcurrentResult();if (logger.isDebugEnabled()) {logger.debug("Found concurrent result value [" + result + "]");}invocableMethod = invocableMethod.wrapConcurrentResult(result);}invocableMethod.invokeAndHandle(webRequest, mavContainer);if (asyncManager.isConcurrentHandlingStarted()) {return null;}return getModelAndView(mavContainer, modelFactory, webRequest);}finally {webRequest.requestCompleted();}
}

invocableMethod.invokeAndHandle()最终要实现的目的就是:完成Request 中的参数和方法参数上数据的绑定。Spring MVC 中提供两种Request 参数到方法中参数的绑定方式:

1、通过注解进行绑定,@RequestParam。

2、通过参数名称进行绑定。

使用注解进行绑定,我们只要在方法参数前面声明@RequestParam("name"),就可以将request 中参数name 的值绑定到方法的该参数上。使用参数名称进行绑定的前提是必须要获取方法中参数的名称,Java 反射只提供了获取方法的参数的类型,并没有提供获取参数名称的方法。SpringMVC 解决这个问题的方法是用asm 框架读取字节码文件,来获取方法的参数名称。asm 框架是一个字节码操作框架,关于asm 更多介绍可以参考其官网。个人建议,使用注解来完成参数绑定,这样就可以省去asm 框架的读取字节码的操作。

public Object invokeForRequest(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer,Object... providedArgs) throws Exception {Object[] args = getMethodArgumentValues(request, mavContainer, providedArgs);if (logger.isTraceEnabled()) {logger.trace("Invoking '" + ClassUtils.getQualifiedMethodName(getMethod(), getBeanType()) +"' with arguments " + Arrays.toString(args));}Object returnValue = doInvoke(args);if (logger.isTraceEnabled()) {logger.trace("Method [" + ClassUtils.getQualifiedMethodName(getMethod(), getBeanType()) +"] returned [" + returnValue + "]");}return returnValue;
}
private Object[] getMethodArgumentValues(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer,Object... providedArgs) throws Exception {MethodParameter[] parameters = getMethodParameters();Object[] args = new Object[parameters.length];for (int i = 0; i < parameters.length; i++) {MethodParameter parameter = parameters[i];parameter.initParameterNameDiscovery(this.parameterNameDiscoverer);args[i] = resolveProvidedArgument(parameter, providedArgs);if (args[i] != null) {continue;}if (this.argumentResolvers.supportsParameter(parameter)) {try {args[i] = this.argumentResolvers.resolveArgument(parameter, mavContainer, request, this.dataBinderFactory);continue;}catch (Exception ex) {if (logger.isDebugEnabled()) {logger.debug(getArgumentResolutionErrorMessage("Failed to resolve", i), ex);}throw ex;}}if (args[i] == null) {throw new IllegalStateException("Could not resolve method parameter at index " +parameter.getParameterIndex() + " in " + parameter.getExecutable().toGenericString() +": " + getArgumentResolutionErrorMessage("No suitable resolver for", i));}}return args;
}

关于asm 框架获取方法参数的部分,这里就不再进行分析了。感兴趣的小伙伴可以继续深入了解这个处理过程。

到这里,方法的参数值列表也获取到了,就可以直接进行方法的调用了。整个请求过程中最复杂的一步就是在这里了。到这里整个请求处理过程的关键步骤都已了解。理解了Spring MVC 中的请求处理流程,整个代码还是比较清晰的。最后我们再来梳理一下Spring MVC 核心组件的关联关系(如下图):

最后来一张时序图:

Spring MVC 源码-运行调用阶段相关推荐

  1. Spring MVC源码——Servlet WebApplicationContext

    上一篇笔记(Spring MVC源码--Root WebApplicationContext)中记录了下 Root WebApplicationContext 的初始化代码.这一篇来看 Servlet ...

  2. Spring MVC源码——Root WebApplicationContext

    Spring MVC源码--Root WebApplicationContext 打算开始读一些框架的源码,先拿 Spring MVC 练练手,欢迎点击这里访问我的源码注释, SpringMVC官方文 ...

  3. Spring MVC源码解析——HandlerMapping(处理器映射器)

    Sping MVC 源码解析--HandlerMapping处理器映射器 1. 什么是HandlerMapping 2. HandlerMapping 2.1 HandlerMapping初始化 2. ...

  4. Spring MVC 源码-初始化阶段

    我们首先找到DispatcherServlet 这个类,必然是寻找init()方法.然后,我们发现其init方法其实在父类HttpServletBean 中,其源码如下: @Override publ ...

  5. 精尽Spring MVC源码分析 - 一个请求的旅行过程

    我们先来了解一个请求是如何被 Spring MVC 处理的,由于整个流程涉及到的代码非常多,所以本文的重点在于解析整体的流程,主要讲解 DispatcherServlet 这个核心类,弄懂了这个流程后 ...

  6. Spring MVC源码解析

    Spring Mvc结构解析 上图是Dispatcher Servlet的结构图,从图中可以清楚的看到Dispatcher Servlet的继承链,下面我们将基于Spring4.1.6揭开Spring ...

  7. Spring MVC源码分析(一) 说明

    为什么会有这一个系列的文章 现在正值大学的第一个暑假,这个暑假我准备开始进入框架的学习,首先我选择的是Spring MVC框架,这是自己学的第一个框架,我在学习的过程中不断告诉自己,这一次不是单纯的学 ...

  8. Spring MVC 源码分析

    根据上面分析的Spring MVC 工作机制,从三个部分来分析Spring MVC 的源代码. 其一,ApplicationContext 初始化时用Map 保存所有url 和Controller 类 ...

  9. Spring MVC源码 ----- @RequestBody和@ResponseBody原理解析

    来源:https://www.cnblogs.com/java-chen-hao/p/11187914.html 1. 概述 在SpringMVC的使用时,往往会用到@RequestBody和@Res ...

最新文章

  1. 元旦特惠!无人机/ROS2/三维重建点云/SLAM/多传感器/相机标定/深度估计等重磅干货教程...
  2. R函数:交集intersect、并集union、找不同setdiff、判断相同setequal
  3. TED+肢体语言塑造你自己+power+fake it till you make it
  4. Java--获取request中所有参数的方法
  5. 去除 计算机里面的百度云管家,WIN7如何彻底清除“百度云管家”图标或残留文件?...
  6. CodeForces - 475B Strongly Connected City(最短路+判断强联通图/思维)
  7. leetcode125验证回文串
  8. 【转】Android AlertDialog自定义布局
  9. mysql数据库创建带-的数据库名
  10. 解决方案:Lua环境搭建
  11. 微信小程序开发笔记2——如何发布小程序体验版
  12. 读书笔记_量化交易如何建立自己的算法交易02
  13. Android引入第三方jar包报错java.lang.NoClassDefFoundErro...
  14. 面向对象的数据库db4o: 初识db4o
  15. BIG5, GB(GB2312, GBK, ...), Unicode编码, UTF8, WideChar, MultiByte, Char说明与区别
  16. 私域运营是不是就是社群运营?
  17. 美化牙齿的几大方式,护牙剂省钱省力
  18. 用前端代码编写一个动态的罗盘时钟
  19. go每日新闻(2021-12-01)——Go 1.18新特性前瞻:原生支持Fuzzing测试
  20. Kali及Windows安装和使用OpenVPN

热门文章

  1. Java程序员总结出必看的初级~高级技术面试题
  2. ActiveRecord学习(六):总结
  3. 2019.6.18 校内测试 分析+题解
  4. Linux环境编程之同步(四):Posix信号量
  5. 由于权限引起的Tomcat中项目某些页面访问不了
  6. 实现GridView的插入功能
  7. 重新开始我的园子生活了
  8. sharepoint Lists Web service 用法
  9. ALI的Tensorflow炼成与GAN科普
  10. ubuntu 安装截图工具 Shutter,并设置快捷键 Ctrl+Alt+A