前言
在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请求的大体流程。
comments powered by Disqus