对于有经验的SpringMVC用户来说,应该对它的HTTP请求处理流程有一个大概的了解。SpringMVC是原生Servlet的扩展,它映射了请求的方法和处理请求的方法,也是我们常见的@RequestMapping中指定的路径和@RequestMapping注解的方法的映射。这个映射是在SpringMVC容器启动时预先解析并初始化到Spring容器中的。它们存储在地图中。Key可以简单理解为请求路径,Value可以简单理解为请求处理器,也就是对应的控制器方法。请求过来后,根据请求uri获取对应的处理器方法,然后反射调用。那么SpringMVC的详细处理流程是怎样的呢?我们将从源码中逐步了解细节:首先是初始化过程,在Spring容器的启动过程中,如何初始化控制器Controller和请求映射器RequestMapping。初始化过程完成后,结果就是将请求映射到RequestMapping和processor方法的对应关系,注册到请求映射器注册中心的MappingRegistry中。那么当http请求到达SpringMVC框架时,它是如何接收请求的呢?下面就来分析一下吧!首先,在分析之前,大家需要有个基础的知识储备,就是Servlet的执行过程。我们都知道SpringMVC框架遵循Servlet规范,是通过Servlet进行扩展的,所以它的执行过程也必然离不开原来的Servlet。在Servlet容器中“通常指的是Tomcat”,每当Http请求到达Servlet容器时,就会执行Servlet服务方法,那么Servlet服务方法的执行与SpringMVC的执行过程有什么关系呢?我们先来看一下SpringMVC的核心处理器DispatcherServlet的继承关系图,如下:可以看到最上层的父类是Servlet的子类来实现HttpServlet,那我们看看service方法是在哪里调用的?按照之前介绍的技巧,可以从最底层的子类开始一一往上查找init方法是在哪个类中调用的,查找后发现是在FrameworkServlet中调用的,如下:/***Overridethe父类的方法,增加PATCH请求的处理*/@Overrideprotectedvoidservice(HttpServletRequestreq,HttpServletResponseresp){//获取请求方法类型。将请求方法类型转换为HttpMethod枚举类型HttpMethodhttpMethod=HttpMethod.resolve(req.getMethod());//如果是PATCH类型,直接进入processRequest方法。//PATCH请求:PUT请求方法用于替换资源,PATCH方法用于更新部分资源。如果(httpMethod==HttpMethod.PATCH||httpMethod==null){processRequest(req,resp);}else{//调用父类HttpServlet的服务方法。父类方法会判断请求类型是get还是post,从而调用子类的doGet或doPost方法super.service(req,resp);}}第一步首先获取http请求的请求类型,可以稍微了解一下这个方法,根据方法名从一个Map集合中获取请求类型,可以看到有以下8种枚举类型在这个枚举类中,也就是HTTP请求的方法一共有8种。publicenumHttpMethod{GET,HEAD,POST,PUT,PATCH,DELETE,OPTIONS,TRACE;}如果是PATCH类型的请求,直接进入processRequest方法,那么这个PATCH请求是什么?我们都知道在RestFULL设计规范中,PUT请求是用来替换资源的,而这个PATCH请求是用来更新一些资源的。再看super.service方法。如果不是PATCH请求,且Method存在,则执行该方法。这个方法点击后,会直接调用HttpServlet的服务方法。这个方法会根据请求类型POST或者GET来决定。调用doGet、doPost或某种其他类型的方法。可以看到,这里使用了dispatch设计模式。在这个方法中,假设调用了一个GET请求的接口,就会调用doGet方法,而这个doGet方法被子类FrameworkServlet覆盖,所以会调用FrameworkServlet的doGet方法。我们来看看FrameworkServlet的doGet方法如下:/***处理get请求,异常省略*/@OverrideprotectedfinalvoiddoGet(HttpServletRequestreq,HttpServletResponseresp){//处理get请求processRequest(req,resp);}直接调用了processRequest方法,和上面刚刚看到的处理PATCH请求调用的方法是一样的,接下来继续分析processRequest方法,该方法核心代码如下:/***处理具体的http请求,异常省略*/protectedfinalvoidprocessRequest(HttpServletRequestreq,HttpServletResponseresp){//中间处理国际化和异步调用的代码暂时省略try{//调用doService方法处理请求doService(请求,响应);}catch(Throwableex){failureCause=ex;thrownewNestedServletException("请求处理失败",ex);}finally{//ResetContextHolder...}}可以看到,核心就是调用一个doService方法。我们在查看doService方法的实现,发现是一个没有实现的抽象方法。你看过一次模板设计模式的应用吗?我们在其子类中查找该方法的实现,发现是在DispatcherServlet中实现的。核心代码如下:@OverrideprotectedvoiddoService(HttpServletRequestreq,HttpServletResponseresp){//打印请求日志logRequest(req);//设置一堆解析器,也就是Spring的九大组件//可以看到,在request中设置,后面会用到,直接从request中获取req.setAttribute(WEB_APPLICATION_CONTEXT_ATTRIBUTE,getWebApplicationContext());req.setAttribute(LOCALE_RESOLVER_ATTRIBUTE,这个.localeResolver);req.setAttribute(THEME_RESOLVER_ATTRIBUTE,this.themeResolver);req.setAttribute(THEME_SOURCE_ATTRIBUTE,getThemeSource());if(this.flashMapManager!=null){req.setAttribute(FLASH_MAP_MANAGER_ATTRIBUTE,this.flash){//调用doDispatch方法处理请求,SpringMVC处理请求的核心方法。doDispatch(请求,响应);}finally{//省略}}核心代码还是一行,就是调用doDispatch方法,这个方法走到了真正的SpringMVC核心处理方法中,所有的处理逻辑都在这个方法中完成,包括文件上传,拦截、异常处理程序等我们先看一下核心处理请求的上下文,其他的具体功能可以慢慢理解。核心逻辑代码如下。在下面的代码中,为了清晰起见,删除了一些与校验和和变量定义相关的代码://处理器执行链HandlerExecutionChainmappedHandler=null;布尔multipartRequestParsed=false;try{try{//检查请求中是否有二进制流,判断请求是否为文件上传请求。//如果容器中没有配置MultipartResolver组件,则直接忽略文件上传请求processedRequest=checkMultipart(req);//文件上传请求需要处理multipartRequestParsed=(processedRequest!=req);//根据请求找到请求处理器//声明一个控制器通常有三个方法://(1)@Controller//(2)实现HttpRequestHandler接口,并重写handleRequest方法,并将控制器类标记为Bean通过@Component//(3)通过xml配置。mappedHandler=getHandler(processedRequest);//如果没有找到对应的handler,直接返回404if(mappedHandler==null){noHandlerFound(processedRequest,resp);返回;}//查找当前requesthandler对应的adapter,一般使用RequestMappingHandlerAdaptor,其他两个Adapter一般不常用HandlerAdapterha=getHandlerAdapter(mappedHandler.getHandler());//在处理请求之前,执行拦截器中配置的方法,获取所有配置的拦截器,循环遍历,依次执行//如果拦截器执行过程中返回false,则直接返回,不执行目标方法if(!mappedHandler.applyPreHandle(processedRequest,resp)){return;}//执行SpringMVC中真正对应的业务方法//HandlerAdapter的实现子类有:AbstractHandlerMethodAdapter,HttpRequestHandlerAdaptermv=ha.handle(processedRequest,resp,mappedHandler.getHandler());//找到视图路径。前缀+uri+后缀applyDefaultViewName(processedRequest,mv);//处理完请求后执行拦截器方法。映射处理程序。applyPostHandle(processedRequest,resp,mv);}catch(Exceptionex){dispatchException=ex;}//处理完成后渲染页面视图。例如:跳转到Jsp页面。processDispatchResult(processedRequest,resp,mappedHandler,mv,dispatchException);}catch(Exceptionex){//页面渲染过程中,如果出现异常,afterCompletion方法triggerAfterCompletion(processedRequest,resp,mappedHandler,ex);}finally{//一些善后工作,比如清理文件上传遗留的Temporaryfiles等}}上面的方法执行完后,处理了一个SpringMVC请求。由于篇幅原因,具体的文件上传判断、视图渲染返回、异常处理、拦截器执行逻辑将在后续文章中介绍。带注释的源码已经上传到github,地址:https://github.com/wb02125055...大家可以自己fork一下。有问题请在下方留言!
