本文是基于慕课网小马哥的 《Spring Boot 2.0深度实践之核心技术篇》的内容结合自己的需要和理解做的笔记。
理解 Spring Web MVC 架构
在解释Spring Web Mvc 的架构之前,首先我们要了解一下基于Servlet的基础架构之上构建的一种J2EE的设计模式--Front Controller(前端总控制器模式)
通过流程图我们可以了解到:
- 当客户端发送请求到前端总控制器(Front Controller),在这里前端总控制器有两种实现《Servlet》和《JSP》。
- 前端总控制器通过委派发送给应用控制器(ApplicationController)。
- 应用控制器通过委派调用一个命令对象(Command),然后分发给对应的视图处理器(View)。
介绍完前端总控制器,接下来对于理解SpringMvc架构就很简单了。
通过流程图我们可以了解到:
- 通过客户端发送的请求到前端总控制器(Front Controller) 也就是我们经常提到的DispatcherServlet。
- 前端总控制器委派给Controller,也就是我们常用的SpringMVC使用的控制层,也就是标注@Controller的业务模型。
- 然后通过DispatcherServlet将业务处理的结果委派给视图渲染模型。
- 最终返回视图对象给DispatcherServlet,响应结果。
SpringMvc核心组件以及交互流程
SpringMvc核心组件
- 处理器管理
- 映射:HandlerMapping
- 适配:HandlerAdapter
- 执行:HandlerExecutionChain
- 渲染
- 视图解析:ViewResolver
- 国际化: LocaleResolver,LocaleContextResolver
- 个性化:ThemeResolver
- 异常处理
- HandlerExceptionResolver
各个组件的介绍内容在Web on Servlet Stack中的 1.2.2. Special Bean Types章节中。
SpringMvc交互流程
流程说明
- 用户发送请求至前端控制器 DispatcherServlet。
- DispatcherServlet 收到请求调用 HandlerMapping 处理器映射器。处理器映射器根据请求 url 找到具体的处理器,生成处理器对象及处理器拦截器(如果有则生成)一并返回给 DispatcherServlet。
- DispatcherServlet 通过 HandlerAdapter 处理器适配器调用处理器。
- HandlerAdapter 执行Controller层中的对应方法。
- Controller 执行完成返回 ModelAndView或ViewName。 HandlerAdapter 将 handler 执行结果 ModelAndView 返回给 DispatcherServlet。
- DispatcherServlet 将 ModelAndView 传给 ViewReslover 视图解析器。
- ViewReslover 解析后返回具体 View 对象。 DispatcherServlet 对 View 进行渲染视图(即将模型数据填充至视图中)。
- DispatcherServlet 响应用户。
源码理解交互流程
准备一个简单的SpringMVC项目
在Debugger源码之前,先让我们创建一个简单的spring项目,只需要一个简单的请求返回一个JSP页面就可以。
我创建的spring项目全部都使用了注解驱动(版本必须是Spring Framework 3.1 +,Servlet 3.0)。
在这里不阐述是如何实现自动装配Spring MVC项目的,后面会有单独的介绍,先简单给出代码让我们的程序跑起来,方便一步一步Debugger。
项目目录如下:
pom.xml
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.0.4.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
这里需要注意一下 我使用的是IntelliJ IDEA 商业版,所以不需要内置tomcat插件。
WEB-INF/jsp/index.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>Title</title>
</head>
<body>
<p>Hello World</p>
</body>
</html>
替换web.xml的配置类 --DefaultAnnotationConfigDispatcherServletInitializer
package com.web.servlet.support;
import com.web.configuration.DispatchServletConfiguration;
import org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer;
/**
* web.xml的配置
*/
public class DefaultAnnotationConfigDispatcherServletInitializer
extends AbstractAnnotationConfigDispatcherServletInitializer {
@Override
protected Class<?>[] getRootConfigClasses() {
return new Class[0];
}
@Override
protected Class<?>[] getServletConfigClasses() { //配置DispatcherServlet
return new Class[]{DispatchServletConfiguration.class};
}
@Override
protected String[] getServletMappings() {
return new String[]{"/"};
}
}
替换application-context.xml的配置类 --WebMvcConfig
package com.web.configuration;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.web.servlet.DispatcherServlet;
/**
* {@link DispatcherServlet}
* 扫描使用注解驱动的包
* 相当于 <context:component-scan base-package="com.web"/>
*/
@ComponentScan(basePackages = "com.web")
public class DispatchServletConfiguration {
}
以上的XML配置和java代码通过对应的注释已经一一对应解释了。
简单的Controller层 --HelloWorldController
package com.web.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
/**
* 简单controller
*/
@Controller
public class HelloWorldController {
@RequestMapping("/index")
public String index() {
return "index";
}
}
这里全部代码已经给出。下面让我们开始调试吧。让我们启动tomcat,访问http://localhost:8080/index。
执行流程
流程的核心就是 org.springframework.web.servlet.DispatcherServlet
这个类。
核心方法就是#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 {
processedRequest = checkMultipart(request);
multipartRequestParsed = (processedRequest != request);
// Determine handler for the current request.
// 确定当前请求的处理器也就是各种HandlerMapping
mappedHandler = getHandler(processedRequest);
if (mappedHandler == null) {
noHandlerFound(processedRequest, response);
return;
}
// Determine handler adapter for the current request.
// 确定当前请求的适配器
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
// Process last-modified header, if supported by the handler.
String method = request.getMethod();
boolean isGet = "GET".equals(method);
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;
}
}
if (!mappedHandler.applyPreHandle(processedRequest, response)) {
return;
}
// Actually invoke the handler.
// 调用实际的方法
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
if (asyncManager.isConcurrentHandlingStarted()) {
return;
}
applyDefaultViewName(processedRequest, mv);
mappedHandler.applyPostHandle(processedRequest, response, mv);
}
catch (Exception ex) {
dispatchException = ex;
}
catch (Throwable err) {
// As of 4.3, we're processing Errors thrown from handler methods as well,
// making them available for @ExceptionHandler methods and other scenarios.
dispatchException = new NestedServletException("Handler dispatch failed", err);
}
processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
}
catch (Exception ex) {
triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
}
catch (Throwable err) {
triggerAfterCompletion(processedRequest, response, mappedHandler,
new NestedServletException("Handler processing failed", err));
}
finally {
if (asyncManager.isConcurrentHandlingStarted()) {
// Instead of postHandle and afterCompletion
if (mappedHandler != null) {
mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
}
}
else {
// Clean up any resources used by a multipart request.
if (multipartRequestParsed) {
cleanupMultipart(processedRequest);
}
}
}
}
根据上面的源码我们可以看到核心的过程
// Determine handler for the current request.
// 确定当前请求的处理器也就是各种HandlerMapping
mappedHandler = getHandler(processedRequest);
// Determine handler adapter for the current request.
// 确定当前请求的适配器
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
// Actually invoke the handler.
// 调用实际的方法
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
现在让我们分步来debugger这几步,一点一点理解其流程。
1.org.springframework.web.servlet.DispatcherServlet#getHandler
我们可以看到spring默认实现了 HandlerMapping
的5个实现类,而第一个就是我们配置常用的 RequestMappingHandlerMapping
。
接着往下看,通过RequestMappingHandlerMapping
获取当前请求的处理程序执行链也就是 HandlerExecutionChain
包含当前处理程序对象和任何处理程序拦截器。
我们可以清楚的看到,我们请求的URL路径直接映射到了public java.lang.String com.web.controller.HelloWorldController.index()
这个方法。
2.org.springframework.web.servlet.DispatcherServlet#getHandlerAdapter
我们可以看到spring默认实现了 HandlerAdapter
的3个实现类,而第一个就是我们配置常用的RequestMappingHandlerAdapter
然后往下Debugger
这一步是判断当前的HandlerMethod是否是期望实现的处理程序。最后返回对应的适配处理器。
3.org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter#handle
使用给定的适配处理程序来处理此请求,返回ModelAndView
对象,这里viewName是 “index”,我们可以对应刚刚我们写的HelloWorldController#index()
方法,确实方法返回的就是"index" 字符串。
4.org.springframework.web.servlet.DispatcherServlet#processDispatchResult
这个方法的目的就是 解析程序选择处理或者处理程序调用的结果无论是ModelAndView或者Exception。
最后通过render
方法来渲染视图,我们在进入render
方法中看一下就可以查找到我们想要的org.springframework.web.servlet.view.JstlView
和对应返回的资源URL。
至此,SpringMvc大致的执行流程已经介绍完了。我们通过Debugger源码再对应上面的交互流程图,就可以理解SpringMVC的架构和流程了。