详解SpringMVC处理流程

前言

在Spring MVC中,处理一个Web请求的流程被设计得非常清晰且高效,从客户端的请求发送到最终的响应返回,整个过程涵盖了多个核心组件的协作。理解这些组件的工作机制及其之间的交互,是掌握Spring MVC的重要基础。

用户发出HTTP请求时,Spring MVC会通过一系列步骤来处理该请求,并生成对应的响应。这个处理流程主要包括以下关键阶段:

前端控制器(DispatcherServlet):Spring MVC的核心,它负责接收所有进入的请求,并协调其他组件进行处理。

处理器映射(Handler Mapping):用于确定具体的处理器(Controller)来处理当前请求。

处理器(Controller):在接收到请求后执行相应的业务逻辑,并返回模型数据和视图信息。

视图解析器(View Resolver):根据控制器返回的视图名,解析并生成最终的视图。

视图(View)渲染:将模型数据与视图结合,生成HTML或其他格式的响应内容,并返回给客户端。

本文将深入分析Spring MVC处理流程中的每个阶段,帮助你理解如何从请求到响应的完整工作机制。

整体流程

大家先看看这张图对大致流程进行一个了解(后边文章在看的时候可以结合这张图)

我们知道SpringMVC是运行在Web容器(Tomcat、Jetty等)中的,而SpringMVC的核心处理器就是DispatcherServlet ,那么DispatcherServlet 是如何接收到要处理的Http请求的呢?

实际上,它继承了FrameworkServlet ,而FrameworkServlet 又继承了HttpServlet ,当Web容器收到Http请求后,会将它们发送给Servlet,并调用Servlet的service()方法来处理它们。

然而FrameworkServlet中定义了service方法,所以DispatcherServlet实际上调用的就是它父类的service方法,service方法中又调用了processRequest方法,而这些都是在父类FrameworkServlet中完成的,接下来processRequest方法调用了doService方法,就交给了子类DispatcherServlet来处理,而SpringMVC的核心处理流程就从doService方法开始了。

先给大家看下DispatcherServlet的调用时序图(比较丑陋,大家将就看看),时序图忽略了doService之前的流程,所以给大家在上面梳理了一下,接下来就只关注doService方法开始。

看图我们可以知道,实际上doService方法并没有控制各个组件进行处理,而是调用了doDispatch方法,doService只是对Request对象进行了一些处理,比如设置一些属性值。

而在doDispatch方法中,可谓是贯穿了SpringMVC所以的核心处理流程。这是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 {

try {

ModelAndView mv = null;

Exception dispatchException = null;

try {

processedRequest = this.checkMultipart(request);

multipartRequestParsed = processedRequest != request;

mappedHandler = this.getHandler(processedRequest);

if (mappedHandler == null || mappedHandler.getHandler() == null) {

this.noHandlerFound(processedRequest, response);

return;

}

HandlerAdapter ha = this.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 (this.logger.isDebugEnabled()) {

this.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;

}

mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

if (asyncManager.isConcurrentHandlingStarted()) {

return;

}

this.applyDefaultViewName(processedRequest, mv);

mappedHandler.applyPostHandle(processedRequest, response, mv);

} catch (Exception var20) {

Exception ex = var20;

dispatchException = ex;

} catch (Throwable var21) {

Throwable err = var21;

dispatchException = new NestedServletException("Handler dispatch failed", err);

}

this.processDispatchResult(processedRequest, response, mappedHandler, mv, (Exception)dispatchException);

} catch (Exception var22) {

Exception ex = var22;

this.triggerAfterCompletion(processedRequest, response, mappedHandler, ex);

} catch (Throwable var23) {

Throwable err = var23;

this.triggerAfterCompletion(processedRequest, response, mappedHandler, new NestedServletException("Handler processing failed", err));

}

} finally {

if (asyncManager.isConcurrentHandlingStarted()) {

if (mappedHandler != null) {

mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);

}

} else if (multipartRequestParsed) {

this.cleanupMultipart(processedRequest);

}

}

}

我相信大家在看过这个代码之后多多少少会对一张图有点亲切感。

这里再详细描述一下doDispatch方法的流程,实际上第一张图对应的就是doDispatch方法。

首先,会通过HandlerMapping去拿到与请求相对应的HandlerExecutionChain,对应的方法就是

protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {

Iterator var2 = this.handlerMappings.iterator();

HandlerExecutionChain handler;

do {

if (!var2.hasNext()) {

return null;

}

HandlerMapping hm = (HandlerMapping)var2.next();

if (this.logger.isTraceEnabled()) {

this.logger.trace("Testing handler map [" + hm + "] in DispatcherServlet with name '" + this.getServletName() + "'");

}

handler = hm.getHandler(request);

} while(handler == null);

return handler;

}

这个HandlerExecutionChain包含了要执行的Handler以及我们定义的拦截器HandlerInterceptor[]。拦截器中有三个方法,分别为

preHandler:在执行handler之前调用

postHandler:在执行handler之后调用

afterCompletion:在preHandler被拦截成功后执行,或者在处理过程中抛出异常时执行

接下来,会获取一个HandlerAdapor,这个我们从名字就可以看出,处理器适配器,也就是用来执行不同的处理器的,也就是我们的Controller方法。

接着,会通过HandlerExcutionChain执行前置处理,也就是前置拦截器,具体方法如下

boolean applyPreHandle(HttpServletRequest request, HttpServletResponse response) throws Exception {

HandlerInterceptor[] interceptors = this.getInterceptors();

if (!ObjectUtils.isEmpty(interceptors)) {

for(int i = 0; i < interceptors.length; this.interceptorIndex = i++) {

HandlerInterceptor interceptor = interceptors[i];

if (!interceptor.preHandle(request, response, this.handler)) {

this.triggerAfterCompletion(request, response, (Exception)null);

return false;

}

}

}

return true;

}

这个方法会执行拦截器操作,如果请求被拦截,直接触发triggerAfterCompletion操作。如果请求通过,则进行下一步。

接着,就是真正执行handler的时候了,也就是执行我们的controller中的方法,因为我们从HandlerMapping中拿到的Handler是一个Object,不能直接执行,所以才有了HandlerApator适配器模式,让它执行handler。执行结束后会将我们controller中方法返回的数据包装成一个ModelAndView返回。

然后,继续执行拦截器方法,此次执行的是postHandler。

接下就到了ViewReslover出场了,这个大家可以在时序图中看到,调用了好几个方法才向ViewResolver发起请求的,实际上也就是把ModelAndView的信息传给ViewResolver将它转换成View。

最后就是执行applyAfterConcurrentHandlingStarted方法,也是一个拦截器操作,这个是在处理完整个请求流程时触发。也是由我们自定义的拦截器。

这样,整个SpringMVC的处理流程我们就梳理完了,有想更深入了解的可以按照时序图中的调用顺序去看源码,这里的方法名都和源码对应。

现如今大家一般都时前后端分离开发,所以ViewResolver基本上也就用不上了。

我们进行前后段分离开发的时候往往会在Controller上加@RequestMapping和@RestController注解,这时我们使用的HandlerAdaptor实际上时RequestMappingHandlerAdaptor,处理方法如下

protected ModelAndView handleInternal(HttpServletRequest request, HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {

this.checkRequest(request);

ModelAndView mav;

if (this.synchronizeOnSession) {

HttpSession session = request.getSession(false);

if (session != null) {

Object mutex = WebUtils.getSessionMutex(session);

synchronized(mutex) {

mav = this.invokeHandlerMethod(request, response, handlerMethod);

}

} else {

mav = this.invokeHandlerMethod(request, response, handlerMethod);

}

} else {

mav = this.invokeHandlerMethod(request, response, handlerMethod);

}

if (!response.containsHeader("Cache-Control")) {

if (this.getSessionAttributesHandler(handlerMethod).hasSessionAttributes()) {

this.applyCacheSeconds(response, this.cacheSecondsForSessionAttributeHandlers);

} else {

this.prepareResponse(response);

}

}

return mav;

}

前后段分离时handle方法返回的ModelAndView为null,后端返回的数据都在响应体中携带。

至此,我们梳理了SpringMVC处理Http请求的大体流程。

Previous Post

Next

Post →

comments powered by Disqus