1. Spring mvc 流程
其时序图:
整体流程图:
SpringMVC 工作流程描述:
- 用户向服务器发送请求,请求被Spring 前端控制Servelt
DispatcherServlet
捕获; DispatcherServlet
对请求URL进行解析,得到请求资源标识符(URI)。然后根据该URI,调用HandlerMapping
获得该Handler
配置的所有相关的对象(包括Handler对象以及Handler对象对应的拦截器),最后以HandlerExecutionChain
对象的形式返回;DispatcherServlet
根据获得的Handler
,选择一个合适的HandlerAdapter
。(附注:如果成功获得HandlerAdapter
后,此时将开始执行拦截器的preHandler(…)方法)- 提取
Request
中的模型数据,填充Handler
入参,开始执行Handler(Controller)。 在填充Handler的入参过程中,根据你的配置,Spring将帮你做一些额外的工作:- HttpMessageConveter: 将请求消息(如Json、xml等数据)转换成一个对象,将对象转换为指定的响应信息
- 数据转换:对请求消息进行数据转换。如String转换成Integer、Double等
- 数据根式化:对请求消息进行数据格式化。 如将字符串转换成格式化数字或格式化日期等
- 数据验证: 验证数据的有效性(长度、格式等),验证结果存储到BindingResult或Error中
- Handler执行完成后,向
DispatcherServlet
返回一个ModelAndView
对象; - 根据返回的
ModelAndView
,选择一个适合的ViewResolver
(必须是已经注册到Spring容器中的ViewResolver
)返回给DispatcherServlet
; ViewResolver
结合Model
和View
,来渲染视图- 将渲染结果返回给客户端。
2. HttpServlet & FrameworkServlet
下面我们先看看 DispatcherServlet
的两个父类: HttpServlet
和 FrameworkServlet
。
FrameworkServlet
重写了 HttpServlet
的 service
方法。 我们这里先来看看 FrameworkServlet#service
方法。
@Override protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // 解析请求方式 HttpMethod httpMethod = HttpMethod.resolve(request.getMethod()); if (httpMethod == HttpMethod.PATCH || httpMethod == null) { processRequest(request, response); } else { super.service(request, response); } } 复制代码
这里我们需要关注两个点 : processRequest(request, response);
和 super.service(request, response);
。下面我们一一来看
2.1 HttpServlet#service
我们知道 HttpServlet
类中分别提供了相应的服务方法( doGet、doPost
等),如下图
同时, HttpServlet
会根据请求的不同形式引导到对应导函数中处理,如下 HttpServlet#service(HttpServletRequest, HttpServletResponse)
:
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { String method = req.getMethod(); // 如果是 get 方法 if (method.equals(METHOD_GET)) { // lastModified 缓存判断 long lastModified = getLastModified(req); if (lastModified == -1) { // servlet doesn't support if-modified-since, no reason // to go through further expensive logic doGet(req, resp); } else { // 如果设置了缓存时间,则判断在ifModifiedSince 之后的时间,是否被修改。 long ifModifiedSince; try { ifModifiedSince = req.getDateHeader(HEADER_IFMODSINCE); } catch (IllegalArgumentException iae) { // Invalid date header - proceed as if none was set ifModifiedSince = -1; } if (ifModifiedSince < (lastModified / 1000 * 1000)) { // 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 less maybeSetLastModified(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); } } 复制代码
而这几个函数最常用的就是 doGet()
和 doPost()
。这两个方法被 FrameworkServlet
重写了。我们来看看在 FrameworkServlet
中的实现。
@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); } ... 复制代码
我们可以很清楚的看到,对于大部分的请求,还是依赖于 HttpServlet#service(HttpServletRequest, HttpServletResponse)
来进行一个请求的分发。对于我们常见的 doGet()
和 doPost()
方法都是直接调用 processRequest(request, response);
, 而 processRequest
方法 的具体实现在 FrameworkServlet#processRequest
中 。
2.2 FrameworkServlet#processRequest
因此。接下来我们就来看看 org.springframework.web.servlet.FrameworkServlet#processRequest
:
protected final void processRequest(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // 记录当前时间,用于记录web请求的记录时间 long startTime = System.currentTimeMillis(); Throwable failureCause = null; // 1234 的目的是为了保证当前线程的 LocaleContext 和 RequestAttributes 在当前请求后还能恢复,所以提取保存 // 1. 提取当前线程的 LocaleContext 属性 LocaleContext previousLocaleContext = LocaleContextHolder.getLocaleContext(); // 2. 根据当前的request 创建对应的 LocaleContext ,并绑定到当前线程 LocaleContext localeContext = buildLocaleContext(request); // 3. 提取当前线程的 RequestAttributes 属性 RequestAttributes previousAttributes = RequestContextHolder.getRequestAttributes(); // 4. 根据当前的request 创建对应的 RequestAttributes ,并绑定到当前线程 ServletRequestAttributes requestAttributes = buildRequestAttributes(request, response, previousAttributes); WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request); asyncManager.registerCallableInterceptor(FrameworkServlet.class.getName(), new RequestBindingInterceptor()); initContextHolders(request, localeContext, requestAttributes); try { // todo 5. 委托给 doservice方法进行进一步处理 doService(request, response); } catch (ServletException | IOException ex) { failureCause = ex; throw ex; } catch (Throwable ex) { failureCause = ex; throw new NestedServletException("Request processing failed", ex); } finally { // 6. 请求结束,恢复线程原状 resetContextHolders(request, previousLocaleContext, previousAttributes); if (requestAttributes != null) { requestAttributes.requestCompleted(); } logResult(request, response, failureCause, asyncManager); // 发布请求结束的通知事件,无论成功与否 publishRequestHandledEvent(request, response, startTime, failureCause); } } 复制代码
由于逻辑都被封装到 doService(request, response);
中,所以这里还是比较简单。而doService又被 DispatcherServlet
实现了。因此我们这里来看看 DispatcherServlet#doService
。
- DispatcherServlet#doService
@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作用域中 方便获取, 设置一些Spring 上下文 request.setAttribute(WEB_APPLICATION_CONTEXT_ATTRIBUTE, getWebApplicationContext()); request.setAttribute(LOCALE_RESOLVER_ATTRIBUTE, this.localeResolver); request.setAttribute(THEME_RESOLVER_ATTRIBUTE, this.themeResolver); requ