Spring MVC 源码-运行调用阶段
这一步步是由请求触发的,所以入口为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 源码-运行调用阶段相关推荐
- Spring MVC源码——Servlet WebApplicationContext
上一篇笔记(Spring MVC源码--Root WebApplicationContext)中记录了下 Root WebApplicationContext 的初始化代码.这一篇来看 Servlet ...
- Spring MVC源码——Root WebApplicationContext
Spring MVC源码--Root WebApplicationContext 打算开始读一些框架的源码,先拿 Spring MVC 练练手,欢迎点击这里访问我的源码注释, SpringMVC官方文 ...
- Spring MVC源码解析——HandlerMapping(处理器映射器)
Sping MVC 源码解析--HandlerMapping处理器映射器 1. 什么是HandlerMapping 2. HandlerMapping 2.1 HandlerMapping初始化 2. ...
- Spring MVC 源码-初始化阶段
我们首先找到DispatcherServlet 这个类,必然是寻找init()方法.然后,我们发现其init方法其实在父类HttpServletBean 中,其源码如下: @Override publ ...
- 精尽Spring MVC源码分析 - 一个请求的旅行过程
我们先来了解一个请求是如何被 Spring MVC 处理的,由于整个流程涉及到的代码非常多,所以本文的重点在于解析整体的流程,主要讲解 DispatcherServlet 这个核心类,弄懂了这个流程后 ...
- Spring MVC源码解析
Spring Mvc结构解析 上图是Dispatcher Servlet的结构图,从图中可以清楚的看到Dispatcher Servlet的继承链,下面我们将基于Spring4.1.6揭开Spring ...
- Spring MVC源码分析(一) 说明
为什么会有这一个系列的文章 现在正值大学的第一个暑假,这个暑假我准备开始进入框架的学习,首先我选择的是Spring MVC框架,这是自己学的第一个框架,我在学习的过程中不断告诉自己,这一次不是单纯的学 ...
- Spring MVC 源码分析
根据上面分析的Spring MVC 工作机制,从三个部分来分析Spring MVC 的源代码. 其一,ApplicationContext 初始化时用Map 保存所有url 和Controller 类 ...
- Spring MVC源码 ----- @RequestBody和@ResponseBody原理解析
来源:https://www.cnblogs.com/java-chen-hao/p/11187914.html 1. 概述 在SpringMVC的使用时,往往会用到@RequestBody和@Res ...
最新文章
- 元旦特惠!无人机/ROS2/三维重建点云/SLAM/多传感器/相机标定/深度估计等重磅干货教程...
- R函数:交集intersect、并集union、找不同setdiff、判断相同setequal
- TED+肢体语言塑造你自己+power+fake it till you make it
- Java--获取request中所有参数的方法
- 去除 计算机里面的百度云管家,WIN7如何彻底清除“百度云管家”图标或残留文件?...
- CodeForces - 475B Strongly Connected City(最短路+判断强联通图/思维)
- leetcode125验证回文串
- 【转】Android AlertDialog自定义布局
- mysql数据库创建带-的数据库名
- 解决方案:Lua环境搭建
- 微信小程序开发笔记2——如何发布小程序体验版
- 读书笔记_量化交易如何建立自己的算法交易02
- Android引入第三方jar包报错java.lang.NoClassDefFoundErro...
- 面向对象的数据库db4o: 初识db4o
- BIG5, GB(GB2312, GBK, ...), Unicode编码, UTF8, WideChar, MultiByte, Char说明与区别
- 私域运营是不是就是社群运营?
- 美化牙齿的几大方式,护牙剂省钱省力
- 用前端代码编写一个动态的罗盘时钟
- go每日新闻(2021-12-01)——Go 1.18新特性前瞻:原生支持Fuzzing测试
- Kali及Windows安装和使用OpenVPN