springmvc从请求到响应的流程分析
一、创建springmvc项目
通过网盘分享的文件:hello-springmvc.zip
链接: https://pan.baidu.com/s/1VmUHurgph661ND9LWqKhaw 提取码: b36a
二、从请求到响应流程
我们先画一下流程图,如下图所示。
三、源码解析
3.1 HttpServlet接收请求
用户发送http请求后,首先进入到的是HttpServlet当中,执行的是service方法,如下所示,这个方法中首先获取到method,然后根据具体是get或者post或者其他类型请求来进行不同的处理。我们的代码中用的是get类型的请求。因此调doGet(req, resp);
protected void service(HttpServletRequest req, HttpServletResponse resp)throws ServletException, IOException{String method = req.getMethod();if (method.equals(METHOD_GET)) {long lastModified = getLastModified(req);if (lastModified == -1) {// servlet doesn't support if-modified-since, no reason// to go through further expensive logicdoGet(req, resp);} else {long ifModifiedSince = req.getDateHeader(HEADER_IFMODSINCE);if (ifModifiedSince < lastModified) {// If the servlet mod time is later, call doGet()// Round down to the nearest second for a proper compare// A ifModifiedSince of -1 will always be lessmaybeSetLastModified(resp, lastModified);doGet(req, resp);} else {resp.setStatus(HttpServletResponse.SC_NOT_MODIFIED);}}} else if (method.equals(METHOD_HEAD)) {long lastModified = getLastModified(req);maybeSetLastModified(resp, lastModified);doHead(req, resp);} else if (method.equals(METHOD_POST)) {doPost(req, resp);} else if (method.equals(METHOD_PUT)) {doPut(req, resp);} else if (method.equals(METHOD_DELETE)) {doDelete(req, resp);} else if (method.equals(METHOD_OPTIONS)) {doOptions(req,resp);} else if (method.equals(METHOD_TRACE)) {doTrace(req,resp);} else {//// Note that this means NO servlet supports whatever// method was requested, anywhere on this server.//String errMsg = lStrings.getString("http.method_not_implemented");Object[] errArgs = new Object[1];errArgs[0] = method;errMsg = MessageFormat.format(errMsg, errArgs);resp.sendError(HttpServletResponse.SC_NOT_IMPLEMENTED, errMsg);}}
3.2 FrameworkServlet处理get方法
FrameworkServlet类继承了HttpServletBean类,而HttpServletBean又继承了HttpServlet,因此它是HttpServlet的子类,它重写了doGet方法,具体实现doGet方法如下,内部调用了processRequest(request, response);
/*** Delegate GET requests to processRequest/doService.* <p>Will also be invoked by HttpServlet's default implementation of {@code doHead},* with a {@code NoBodyResponse} that just captures the content length.* @see #doService* @see #doHead*/
@Override
protected final void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {processRequest(request, response);
}
processRequest方法源码如下,我们跳过非主要代码,直接看try方法中doService(request, response);
/*** Process this request, publishing an event regardless of the outcome.* <p>The actual event handling is performed by the abstract* {@link #doService} template method.*/
protected final void processRequest(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {long startTime = System.currentTimeMillis();Throwable failureCause = null;LocaleContext previousLocaleContext = LocaleContextHolder.getLocaleContext();LocaleContext localeContext = buildLocaleContext(request);RequestAttributes previousAttributes = RequestContextHolder.getRequestAttributes();ServletRequestAttributes requestAttributes = buildRequestAttributes(request, response, previousAttributes);WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);asyncManager.registerCallableInterceptor(FrameworkServlet.class.getName(), new RequestBindingInterceptor());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 {resetContextHolders(request, previousLocaleContext, previousAttributes);if (requestAttributes != null) {requestAttributes.requestCompleted();}logResult(request, response, failureCause, asyncManager);publishRequestHandledEvent(request, response, startTime, failureCause);}
}
3.3 DispatcherServlet处理doService方法
DispatcherServlet继承了FrameworkServlet类,它实现了doService方法,如下所示,刨除掉不重要的代码,直接看doDispatch(request, response);
@Override
protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception {logRequest(request);// Keep a snapshot of the request attributes in case of an include,// to be able to restore the original attributes after the include.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));}}}// Make framework objects available to handlers and view objects.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());if (this.flashMapManager != null) {FlashMap inputFlashMap = this.flashMapManager.retrieveAndUpdate(request, response);if (inputFlashMap != null) {request.setAttribute(INPUT_FLASH_MAP_ATTRIBUTE, Collections.unmodifiableMap(inputFlashMap));}request.setAttribute(OUTPUT_FLASH_MAP_ATTRIBUTE, new FlashMap());request.setAttribute(FLASH_MAP_MANAGER_ATTRIBUTE, this.flashMapManager);}try {doDispatch(request, response);} finally {if (!WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) {// Restore the original attribute snapshot, in case of an include.if (attributesSnapshot != null) {restoreAttributesAfterInclude(request, attributesSnapshot);}}}
}
3.4 DispatcherServlet处理doDispatch方法
DispatcherServlet中doDispatch方法如下图所示
我们进入getHandler方法内部,可以看到HandlerExecutionChain的获取是从handlerMapping中获取的。
/*** Return the HandlerExecutionChain for this request.* <p>Tries all handler mappings in order.* @param request current HTTP request* @return the HandlerExecutionChain, or {@code null} if no handler could be found*/
@Nullable
protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {if (this.handlerMappings != null) {for (HandlerMapping mapping: this.handlerMappings) {HandlerExecutionChain handler = mapping.getHandler(request);if (handler != null) {return handler;}}}return null;
}
我们再回顾一下我们开头所画的那张图,如下所示,可以看到第2步是从HandlerMapping(处理器映射器)中获取处理器执行链(HanlderExcutionChain)。
什么是HandlerMapping?HandlerMapping在SpringMVC中扮演着请求路由的核心角色,其作用是将客户端请求(如HTTP请求)映射到对应的处理器(如Controller方法)。HandlerMapping是我们添加在controller接口上的注解,如下图所示,@RequestMapping注解的方法由RequestMappingHandlerMapping实现映射。从HandlerMapping中可以获取到HandlerExecutionChain,这个处理器执行器链中包含目标处理器及相关的拦截器(HandlerInterceptor),实现权限检查、日志等统一逻辑。
映射过程:当请求http://localhost:8080/firstController时,RequestMappingHandlerMapping根据URL和HTTP方法匹配到sayHello方法。
接着看doDispatch方法,如下图,在获取到HandlerExecutionChain之后,紧接着就该获取HandlerAdapter了,如下图所示,HandlerAdapter(处理器适配器)的核心作用是统一调用不同类型的处理器(Handler),解决不同处理器接口的兼容性问题。它充当了请求处理逻辑与具体处理器实现之间的适配层,使DispatcherServlet能够以统一的方式调用各种处理器(如@Controller、Servlet、HttpRequestHandler等)。
接着我们代码来到执行前置拦截器,如下图所示,那么我们有必要说下拦截器的例子,这样更好理解。
我们假如自定义了一个拦截器,如下所示代码,里面重点重写了preHandle和postHandle方法,顾名思义,preHandler是要在调用controller方法前先调用,比如做一些权限校验啊之类的。postHandle方法则是在执行完controller接口后,视图渲染前做一些特殊处理。
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;public class CustomInterceptor implements HandlerInterceptor {@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {// 在 Controller 方法执行前调用// 返回 true 继续执行,false 中断请求return true;}@Overridepublic void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) {// 在 Controller 方法执行后、视图渲染前调用}@Overridepublic void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {// 在视图渲染完成后调用(整个请求处理完毕)}
}
有了自定义拦截器类,那么怎么让自定义拦截器被springmvc管理,我们通过下面的方式来处理,如下所示,将自定义拦截器添加到springmvc的拦截器链中。配置的拦截所有请求路径(排除登录地址)
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;@Configuration
public class WebConfig implements WebMvcConfigurer {@Overridepublic void addInterceptors(InterceptorRegistry registry) {registry.addInterceptor(new CustomInterceptor()).addPathPatterns("/**") // 拦截所有路径.excludePathPatterns("/login"); // 排除特定路径}
}
看完拦截器的例子,我们再回到下图的位置,执行前置拦截器就可以执行到我们刚才自定义拦截器的preHandle方法。执行完前置拦截器后,就到了ha.handle这行代码(这个对应我们流程图中的第5步),它是执行controller方法的意思,比如我们例子中的sayHello方法,执行sayHello方法后,返回的是ModelAndView(mv变量就是ModelAndView,这一步对应的是流程图中的第7步),返回ModelAndView对应我们流程中的第6步。ModelAndView是一个核心对象,用于封装控制器(Controller)处理请求后的返回结果,包含一下两部分信息:1)模型数据(Model):传递给视图(View)的动态数据(如查询结果、表单提交内容)。2)视图信息(View):决定客户端最终看到哪个页面(视图名称或视图对象)。它是连接控制器与视图层的桥梁。
在生成ModelAndView后,就开始执行拦截器的后置处理(如上图中的applyPostHandle方法),这个会执行到我们自定义拦截器的postHandle方法。
接着我们分析下面的流程,我们再次回顾一下我们的流程图,如下,我们该分析第8步了。
我们再回到刚才分析到的执行后置拦截器方法那里,代码再往下执行就到processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);这行了,我们需要进入到内部查看源码。
processDispatchResult方法内部源码如下,跳过不重要的代码,直接看标出来的render方法,如下图所示。
接着我们进入到render方法内部,源码如下,可以看到第8步视图解析就是将从ModelAndView中获取到的viewName和model等参数交给ViewResolver(视图解析器)来处理。处理完之后,就生成View对象并返回,这个对应流程图中的第9步。
protected View resolveViewName(String viewName, @Nullable Map < String, Object > model,Locale locale, HttpServletRequest request) throws Exception {if (this.viewResolvers != null) {for (ViewResolver viewResolver: this.viewResolvers) {View view = viewResolver.resolveViewName(viewName, locale);if (view != null) {return view;}}}return null;
}
接着回到如下图所示代码位置,在生成View对象后,接下来就到了view.render方法那一行代码。
上图中view.render方法实际执行的是AbstractView中的render方法,如下图所示,我们直接看最后一行代码renderMergedOutputModel(mergedModel, getRequestToExpose(request), response);
然后看renderMergedOutputModel方法内部的源码,这个方法有很多实现类,我们应该看哪个呢?这个我们在spring-mvc.xml文件中已经配置了,如下图所示。
从上图我们知道应该去InternalResourceViewResolver这个实现类中去看源码,如下图所示,这个方法处理的内容就对应了我们流程图中的第10步,这一步会将Model中的数据填充到View中并且根据用户使用的展示组件如HTML、jsp、freemarker,生成对应的组件,最后进行转发。
最后就是第11步,rd.forward方法将将处理好的HTML或者jsp等返给浏览器进行渲染展示。从而我们完成了整个处理流程的分析。