Spring MVC学习之DispatcherServlet请求处理详析
前言
要深入理解spring mvc的工作流程,就需要先了解spring mvc的架构:
从上图可以看到 前端控制器dispatcherservlet在其中起着主导作用,理解了dispatcherservlet 就完全可以说弄清楚了spring mvc。
dispatcherservlet作为spring用于处理web请求注册的唯一一个servlet,所有的请求都是经由dispatcherservlet进行分发处理的。本文主要讲解dispatcherservlet是如何对请求进行分发,处理,并且生成相应的视图的。
1. 整体结构
在httpservlet中,其对不同方式的请求进行了分发,比如对于get请求,其提供了doget()方法,对于post请求,其提供了dopost()方法等等。通过这种方式,子类可以针对于当前请求的方式实现不同的方法即可。但是在dispatcherservlet中,由于需要使用同一的方式对不同的请求进行处理,因而其对各个请求方式进行了整合,如下就是dispatcherservlet针对get和post请求所编写的同一处理逻辑:
@override protected final void doget(httpservletrequest request, httpservletresponse response) throws servletexception, ioexception { processrequest(request, response); } @override protected final void dopost(httpservletrequest request, httpservletresponse response) throws servletexception, ioexception { processrequest(request, response); }
可以看到,无论是get请求还是post请求,dispatcherservlet都是委托给了processrequest()方法处理,对于其他的请求方式,其处理方式也是类似的。通过这种方式,dispatcherservlet将各个请求整合在了一起,虽然整合在了一起,但是request中也还是保存有当前请求的请求方式的,因而保存了后续对请求进行分发的能力。这里我们直接看processrequest()方法是如何处理各个请求的:
protected final void processrequest(httpservletrequest request, httpservletresponse response) throws servletexception, ioexception { long starttime = system.currenttimemillis(); throwable failurecause = null; // 获取先前请求的localecontext localecontext previouslocalecontext = localecontextholder.getlocalecontext(); // 获取当前请求的localecontext,其中保存了当前请求的locale信息 localecontext localecontext = buildlocalecontext(request); // 获取先前请求的attributes信息 requestattributes previousattributes = requestcontextholder.getrequestattributes(); // 获取当前请求的attributes信息,其中保存了当前请求的各个属性数据 servletrequestattributes requestattributes = buildrequestattributes(request, response, previousattributes); // 获取当前请求的webasyncmanager,这只有在当前请求是请求的异步任务时才会真正用到 webasyncmanager asyncmanager = webasyncutils.getasyncmanager(request); // 注册异步任务的拦截器,如果请求的是异步任务,这个拦截器可以拦截异步任务的前置,后置和异常等情况 asyncmanager.registercallableinterceptor(frameworkservlet.class.getname(), new requestbindinginterceptor()); // 将当前请求的locale,attributes信息初始化到对应的threadlocal对象中,用于后续使用 initcontextholders(request, localecontext, requestattributes); try { // 对当前请求进行分发 doservice(request, response); } catch (servletexception | ioexception ex) { failurecause = ex; throw ex; } catch (throwable ex) { failurecause = ex; throw new nestedservletexception("request processing failed", ex); } finally { // 在请求完成之后,判断当前请求的locale和attributes信息是否需要继承,如果需要继承, // 则会将locale信息设置到inheritablelocalecontextholder中,而将attributes // 信息设置到inheritablerequestattributesholder中;否则就会移除对应的信息, // 而只为当前请求的contextholder设置相应的属性 resetcontextholders(request, previouslocalecontext, previousattributes); if (requestattributes != null) { // 调用已注册的在当前请求被销毁时的回调函数,并且更新session中当前请求所更新的属性 requestattributes.requestcompleted(); } if (logger.isdebugenabled()) { if (failurecause != null) { this.logger.debug("could not complete request", failurecause); } else { if (asyncmanager.isconcurrenthandlingstarted()) { logger.debug("leaving response open for concurrent processing"); } else { this.logger.debug("successfully completed request"); } } } // 发布请求已经完成的事件,以便对该事件进行监听的程序进行相应的处理 publishrequesthandledevent(request, response, starttime, failurecause); } }
可以看到,processrequest()方法主要是对locale和attributes信息进行了处理,然后就通过doservice()方法对请求再次进行了分发。我们这里继续阅读doservice()方法的源码:
@override protected void doservice(httpservletrequest request, httpservletresponse response) throws exception { if (logger.isdebugenabled()) { string resumed = webasyncutils.getasyncmanager(request).hasconcurrentresult() ? " resumed" : ""; logger.debug("dispatcherservlet with name '" + getservletname() + "'" + resumed + " processing " + request.getmethod() + " request for [" + getrequesturi(request) + "]"); } // 这里主要是判断当前请求是否为include请求,如果是include请求,那么就会将当前请求中的 // 数据都放入一个快照中,在当前请求完成之后,会从该块中中取出数据,然后将其重新加载到 // 当前request中,以便request进行后续的处理。这里默认情况下是会对所有的属性进行处理的, // 因为cleanupafterinclude默认值为true,如果将其设置为false,那么就只会对spring框架 // 相关的属性进行处理 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)); } } } // 这里分别将applicationcontext,loacleresolver,themeresolver和themesource等 // bean添加到当前request中 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()); // 这里flashmapmanager主要的作用在于当请求如果是重定向的请求,那么可以将一些属性保存在flashmap // 中,然后通过flashmapmanager进行管理,从而在重定向之后能够获取到重定向之前所保存的请求 if (this.flashmapmanager != null) { // 在当前请求中获取flashmap数据,如果不是重定向之后的请求,那么这里获取到的就是空值 flashmap inputflashmap = this.flashmapmanager.retrieveandupdate(request, response); if (inputflashmap != null) { // 将获取到的flashmap数据保存在request中 request.setattribute(input_flash_map_attribute, collections.unmodifiablemap(inputflashmap)); } // 设置默认的flashmap和flashmapmanager request.setattribute(output_flash_map_attribute, new flashmap()); request.setattribute(flash_map_manager_attribute, this.flashmapmanager); } try { // 这里才是真正的对请求进行分发处理的位置 dodispatch(request, response); } finally { // 判断当前请求不是一个异步任务的请求,但是是一个include请求,那么就会重新加载 // 请求之前保存的快照数据 if (!webasyncutils.getasyncmanager(request).isconcurrenthandlingstarted()) { if (attributessnapshot != null) { restoreattributesafterinclude(request, attributessnapshot); } } } }
这里的doservice()方法也还没有对请求进行真正的处理,其首先判断了当前请求是不是一个include请求,如果是include请求,那么就将请求的属性都保存在一个快照中,以便请求完成之后能够重新进行加载;然后会判断当前是否是一个重定向之后的请求,如果是重定向之后的请求,那么其flashmapmanager就不是空的,此时会将重定向之前保存的属性重新加载到当前请求中;最后doservice()方法才会调用dodispatch()方法进行请求的分发和处理。如下是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 { // 这里判断当前请求是否为一个文件请求,这里的判断方式就是要求当前请求满足两点:①请求 // 方式是post;②判断contenttype是否以multipart/开头。如果满足这两点,那么就认为当前 // 请求是一个文件请求,此时会将当前请求的request对象封装为一个 // multiparthttpservletrequest对象,这也是我们在定义文件请求的controller时 // 能够将request参数写为multiparthttpservletrequest的原因。这里如果不是文件请求, // 那么会将request直接返回。 processedrequest = checkmultipart(request); // 这里判断原始request与转换后的request是否为同一个request,如果不是同一个,则说明 // 其是一个文件请求 multipartrequestparsed = (processedrequest != request); // 这里gethandler()方法就是通过遍历当前spring容器中所有定义的handlermapping对象, // 通过调用它们的gethandler()方法,看当前的handlermapping能否将当前request映射 // 到某个handler,也就是某个controller方法上,如果能够映射到,则说明该handler能够 // 处理当前请求 mappedhandler = gethandler(processedrequest); if (mappedhandler == null) { // 如果每个handlermapping都无法找到与当前request匹配的handler,那么就认为 // 无法处理当前请求,此时一般会返回给页面404状态码 nohandlerfound(processedrequest, response); return; } // 通过找到的handler,然后在当前spring容器中找到能够支持将当前request请求适配到 // 找到的handler上的handleradapter。这里需要找到这样的适配器的原因是,我们的handler // 一般都是controller的某个方法,其是一个java方法,而当前request则是一种符合http // 协议的请求,这里是无法直接将request直接应用到handler上的,因而需要使用一个适配器, // 也就是这里的handleradapter。由于前面获取handler的时候,不同的handlermapping // 所产生的handler是不一样的,比如reqeustmappinghandlermappin*生的handler是一个 // handlermethod对象,因而这里在判断某个handleradapter是否能够用于适配当前handler的 // 时候是通过其supports()方法进行的,比如requestmappinghandleradapter就是判断 // 当前的handler是否为handlermethod类型,从而判断其是否能够用于适配当前handler。 handleradapter ha = gethandleradapter(mappedhandler.gethandler()); string method = request.getmethod(); boolean isget = "get".equals(method); // 这里判断请求方式是否为get或head请求,如果是这两种请求的一种,那么就会判断 // 当前请求的资源是否超过了其lastmodified时间,如果没超过,则直接返回, // 并且告知浏览器可以直接使用缓存来处理当前请求 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; } } // 这里在真正处理请求之前会获取容器中所有的拦截器,也就是handlerinterceptor对象, // 然后依次调用其prehandle()方法,如果某个prehandle()方法返回了false,那么就说明 // 当前请求无法通过拦截器的过滤,因而就会直接出发其aftercompletion()方法,只有在 // 所有的prehandle()方法都返回true时才会认为当前请求是能够使用目标handler进行处理的 if (!mappedhandler.applyprehandle(processedrequest, response)) { return; } // 在当前请求通过了所有拦截器的预处理之后,这里就直接调用handleradapter.handle() // 方法来处理当前请求,并且将处理结果封装为一个modelandview对象。该对象中主要有两个 // 属性:view和model,这里的view存储了后续需要展示的逻辑视图名或视图对象,而model // 中则保存了用于渲染视图所需要的属性 mv = ha.handle(processedrequest, response, mappedhandler.gethandler()); // 如果当前是一个异步任务,那么就会释放当前线程,等待异步任务处理完成之后才将 // 任务的处理结果返回到页面 if (asyncmanager.isconcurrenthandlingstarted()) { return; } // 如果返回的modelandview对象中没有指定视图名或视图对象,那么就会根据当前请求的url // 来生成一个视图名 applydefaultviewname(processedrequest, mv); // 在请求处理完成之后,依次调用拦截器的posthandle()方法,对请求进行后置处理 mappedhandler.applyposthandle(processedrequest, response, mv); } catch (exception ex) { dispatchexception = ex; } catch (throwable err) { // 将处理请求过程中产生的异常封装到dispatchexception中 dispatchexception = new nestedservletexception("handler dispatch failed", err); } // 这里主要是请求处理之后生成的视图进行渲染,也包括出现异常之后对异常的处理。 // 渲染完之后会依次调用拦截器的aftercompletion()方法来对请求进行最终处理 processdispatchresult(processedrequest, response, mappedhandler, mv, dispatchexception); } catch (exception ex) { // 如果在上述过程中任意位置抛出异常,包括渲染视图时抛出异常,那么都会触发拦截器的 // aftercompletion()方法的调用 triggeraftercompletion(processedrequest, response, mappedhandler, ex); } catch (throwable err) { triggeraftercompletion(processedrequest, response, mappedhandler, new nestedservletexception("handler processing failed", err)); } finally { // 如果当前异步任务已经开始,则触发异步任务拦截器的afterconcurrenthandlingstarted()方法 if (asyncmanager.isconcurrenthandlingstarted()) { if (mappedhandler != null) { mappedhandler.applyafterconcurrenthandlingstarted(processedrequest, response); } } else { // 如果当前是一个文件请求,则清理当前request中的文件数据 if (multipartrequestparsed) { cleanupmultipart(processedrequest); } } } }
这里dodispatch()方法是进行请求分发和处理的主*分,其主要分为如下几个步骤:
- 判断当前是否为文件请求,如果是,则将request对象类型转换为multiparthttpservletrequest;
- 在handlermapping中查找能够处理当前request的handlermapping,并且获取能够处理当前请求的handler;
- 根据获取到的handler,查找当前容器中支持将当前request适配到该handler的handleradapter;
- 应用容器中所有拦截器的prehandle()方法,只有在所有的prehandle()方法都通过之后才会将当前请求交由具体的handler进行处理;
- 调用handleradapter.handle()方法将request适配给获取到的handler进行处理;
- 应用容器中所有拦截器的posthandle()方法,以对当前请求进行后置处理;
- 根据处理后得到的modelandview对象对视图进行渲染;
- 应用容器中所有拦截器的aftercompletion()方法,以对当前请求进行完成处理。
2. handler获取
从前面的步骤可以看出,请求的具体处理过程主要是通过handlermapping根据当前request获取到对应的handler,然后交由handleradapter将request适配给该handler进行处理,并将处理结果封装为一个modelandview对象,最后将该modelandview对象渲染出来。这里我们首先看handlermapping根据request查找具体的handler的过程:
@nullable protected handlerexecutionchain gethandler(httpservletrequest request) throws exception { if (this.handlermappings != null) { // 遍历当前容器中所有的handlermapping对象,调用其gethandler()方法,如果其能够根据 // 当前request获取一个handler,那么就直接返回。 for (handlermapping hm : this.handlermappings) { if (logger.istraceenabled()) { logger.trace( "testing handler map [" + hm + "] in dispatcherservlet with name '" + getservletname() + "'"); } handlerexecutionchain handler = hm.gethandler(request); if (handler != null) { return handler; } } } return null; }
这里的逻辑比较简单,就是遍历当前容器中所有的handlermapping对象,然后依次判断其是否能够根据当前request获取一个handler,如果能够获取就直接使用该handler。这里关于handlermapping将request映射为handler的过程可以阅读本人之前的文章:spring mvc之requestmappinghandlermapping匹配。
3. handleradapter获取与请求处理
在获取到具体的handler之后,dispatcher就会根据获取到的handler查找能够将当前request适配到该handler的adapter,这里获取handleradapter的代码如下:
protected handleradapter gethandleradapter(object handler) throws servletexception { if (this.handleradapters != null) { // 遍历当前容器中所有的handleradapter,通过调用其supports()方法,判断当前handleradapter // 能否用于适配当前的handler,如果可以,则直接使用该handleradapter for (handleradapter ha : this.handleradapters) { if (logger.istraceenabled()) { logger.trace("testing handler adapter [" + ha + "]"); } if (ha.supports(handler)) { return ha; } } } // 如果找不到任何一个handleradapter用于适配当前请求,则抛出异常 throw new servletexception("no adapter for handler [" + handler + "]: the dispatcherservlet configuration needs to include a handleradapter" + " that supports this handler"); }
这里获取handleradapter的过程与handlermapping非常的相似,也是遍历当前容器中所有的handleradapter对象,然后调用其supports()方法,判断该适配器能否应用于当前handler的适配,如果可以则直接使用该handleradapter。关于handleradapter进行request与handler适配的过程,读者可阅读本人之前的文章:spring mvc之requestmappinghandleradapter详解。
4. 视图渲染
在handleradapter进行了请求的适配,并且调用了目标handler之后,其会返回一个modelandview对象,该对象中保存有用于渲染视图的模型数据和需要渲染的视图名。具体的视图渲染工作是在processdispatchresult()方法中进行的,这里我们直接阅读器源码:
private void processdispatchresult(httpservletrequest request, httpservletresponse response, @nullable handlerexecutionchain mappedhandler, @nullable modelandview mv, @nullable exception exception) throws exception { // 用于标记当前生成view是否是异常处理之后生成的view boolean errorview = false; if (exception != null) { // 如果当前的异常是modelandviewdefiningexception类型,则说明是modelandview的定义 // 异常,那么就会调用其getmodelandview()方法生成一个新的view if (exception instanceof modelandviewdefiningexception) { logger.debug("modelandviewdefiningexception encountered", exception); mv = ((modelandviewdefiningexception) exception).getmodelandview(); } else { // 如果生成的异常是其他类型的异常,就会在当前容器中查找能够处理当前异常的“拦截器”, // 找到之后调用这些拦截器,然后生成一个新的modelandview object handler = (mappedhandler != null ? mappedhandler.gethandler() : null); mv = processhandlerexception(request, response, handler, exception); errorview = (mv != null); } } // 如果得到的modelandview对象(无论是否为异常处理之后生成的modelandview)不为空,并且没有被清理, // 那么就会对其进行渲染,渲染的主要逻辑在render()方法中 if (mv != null && !mv.wascleared()) { render(mv, request, response); if (errorview) { // 如果当前是异常处理之后生成的视图,那么就请求当前request中与异常相关的属性 webutils.clearerrorrequestattributes(request); } } else { if (logger.isdebugenabled()) { logger.debug("null modelandview returned to dispatcherservlet with name '" + getservletname() + "': assuming handleradapter completed request " + "handling"); } } // 如果当前正在进行异步请求任务的调用,则直接释放当前线程,等异步任务处理完之后再进行处理 if (webasyncutils.getasyncmanager(request).isconcurrenthandlingstarted()) { return; } // 在视图渲染完成之后,依次调用当前容器中所有拦截器的aftercompletion()方法 if (mappedhandler != null) { mappedhandler.triggeraftercompletion(request, response, null); } }
从上面的逻辑可以看出,在进行视图渲染时,首先会判断请求处理过程中是否抛出了异常,如果抛出了异常,则会调用相应的异常处理器,获取异常处理之后的modelandview对象,然后通过modelandview对象渲染具体的视图,最后会依次触发当前容器中所有拦截器的aftercompletion()方法。这里对视图的具体渲染工作在render()方法中,我们继续阅读其源码:
protected void render(modelandview mv, httpservletrequest request, httpservletresponse response) throws exception { // 获取当前请求的locale信息,该信息在进行视图的国际化展示时将会非常有用 locale locale = (this.localeresolver != null ? this.localeresolver.resolvelocale(request) : request.getlocale()); response.setlocale(locale); view view; string viewname = mv.getviewname(); if (viewname != null) { // 如果视图名不为空,那么就会使用当前容器中配置的viewresolver根据视图名获取一个view对象 view = resolveviewname(viewname, mv.getmodelinternal(), locale, request); if (view == null) { throw new servletexception("could not resolve view with name '" + mv.getviewname() + "' in servlet with name '" + getservletname() + "'"); } } else { // 如果modelandview中没有视图名,而提供的view对象,则直接使用该view对象 view = mv.getview(); if (view == null) { throw new servletexception("modelandview [" + mv + "] neither contains a " + "view name nor a view object in servlet with name '" + getservletname() + "'"); } } if (logger.isdebugenabled()) { logger.debug("rendering view [" + view + "] in dispatcherservlet with name '" + getservletname() + "'"); } try { // 设置响应的status属性 if (mv.getstatus() != null) { response.setstatus(mv.getstatus().value()); } // 调用view对象的render()方法来渲染具体的视图 view.render(mv.getmodelinternal(), request, response); } catch (exception ex) { if (logger.isdebugenabled()) { logger.debug("error rendering view [" + view + "] in dispatcherservlet" + " with name '" + getservletname() + "'", ex); } throw ex; } }
这里的render()方法才是进行视图渲染的真正方法,首先该方法首先通过modelandview对象获取所要渲染的视图名,通过viewresolver生成一个用于视图渲染的view对象;如果modelandview中不是保存的视图名,而是保存的view对象,则直接使用该对象。在生成view对象之后,通过调用该对象的render()方法渲染得到具体的视图。这里关于viewresolver如何获取到view对象,并且如何进行视图渲染的过程,读者可以阅读本人的文章:spring mvc之视图解析。
5. 小结
本文首先从整体上讲解了dispatcherservlet是如何对请求进行聚合并且处理的,然后分别从handler获取,handleradapter进行请求适配,以及视图的渲染三个方面对请求处理的整体流程进行了讲解。这里主要是对dispatcherservlet处理请求的整体流程进行讲解,其各个部分的细节读者可以阅读本人前面的文章以进行详细的了解。
总结
以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,如果有疑问大家可以留言交流,谢谢大家对的支持。
推荐阅读
-
Spring MVC学习之DispatcherServlet请求处理详析
-
Spring Boot学习入门之AOP处理请求详解
-
Spring Boot学习入门之AOP处理请求详解
-
Spring MVC Controller与jquery ajax请求处理json 博客分类: 学习笔记 springmvcjqueryjson
-
Java注解处理器学习之编译时处理的注解详析
-
Java注解处理器学习之编译时处理的注解详析
-
Spring MVC源码(二) ----- DispatcherServlet 请求处理流程 面试必问
-
荐 从零写一个具有IOC-AOP-MVC功能的框架---学习笔记---11. MVC功能之http请求处理器的编写---简易框架最后一公里!
-
Spring MVC源码(二) ----- DispatcherServlet 请求处理流程 面试必问
-
荐 从零写一个具有IOC-AOP-MVC功能的框架---学习笔记---11. MVC功能之http请求处理器的编写---简易框架最后一公里!