揭秘SpringMVC(一)_.SpringMVC大全解

引:现在基于所有的Web应用都离不开Spring,用了Spring,你自然会去用他家的MVC框架——SpringMVC!所以掌握SpringMVC也是非常重要的。

SpringMVC请求处理流程总览

我们可以用下面一张图来介绍SpringMVC的核心组件和请求处理流程:

springmvc

流程中出现的核心组件如下:

  1. DispatcherServlet是springmvc中的前端控制器,负责接收request并将request转发给对应的处理组件.
  2. HanlerMapping是springmvc中完成url到controller映射的组件.DispatcherServlet接收request,然后从HandlerMapping查找处理request的controller.
  3. Controller(HandlerAdapter)处理request,并返回ModelAndView对象,Controller是springmvc中负责处理request的组件(类似于struts2中的Action)
  4. ModelAndView是封装Model对象和View对象的组件
  5. ViewResolver是视图解析器,负责解析ModelAndView对象生成对应的View对象
  6. View视图组件,复制渲染页面

整个流程的大致流程如下:

  1. 当request到来时,DispatcherServlet对request进行捕获,并执行doService方法,继而执行doDispatch方法。
  2. HandlerMapping解析请求,并且返回HandlerExecutionChain(其中包含对应的controller和interceptors)
  3. 通过HandlerExecutionChain得到Handler相关类,Handler先执行拦截器的pre相关方法,接着执行handler方法,最后调用拦截器的post相关方法
  4. 解析handler方法返回的ModelAndView
  5. 根据ViewResolver(可以在配置文件中配置,也就是视图解析器)生成View对象
  6. View对象渲染页面并response给客户端

SpringMVC请求处理初始化源码分析

  1. 首先,Tomcat每次启动时都会加载并解析/WEB-INF/web.xml文件,所以可以先从web.xml找突破口(DispatcherServlet的配置),主要代码如下(我们每次都会这样配置):

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    <servlet >
    <servlet-name >spring-mvc</servlet-name>
    <!-- DispatcherServlet类,这个类在spring-mvc的包下面 -->
    <servlet-class >org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <!-- 初始化参数,这里主要是读取SpringMVC的一些配置信息,比如:
    配置注解驱动,静态资源映射,视图解析器,自动扫描装配等相关信息。
    -->
    <init-param >
    <param-name >contextConfigLocation</param-name>
    <param-value >classpath:/spring-mvc.xml</param-value>
    </init-param>
    <!-- 启动时加载 -->
    <load-on-startup >1</load-on-startup>
    </servlet>
    <servlet-mapping >
    <servlet-name >spring-mvc</servlet-name>
    <url-pattern >/</url-pattern>
    </servlet-mapping>
  2. 进入DispatchServlet的分析,先看它的类图:

    DispatchServlet

    它是一个Servlet的子类,那么我们就要专注于它的init、service(后续分析)、doGet、doPost等相关方法,在它的父类HttpServeltBean,我们找到了init方法,如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    public final void init() throws ServletException {
    try {
    // 获取Servlet中的init参数,并创建了一个BeanWapper对象,由子类真正执行BeanWrapper的初始化工作
    // 但是HttpServeltBean的子类并没有覆盖其initBeanWrapper方法,所以创建的BeanWrapper没有用
    PropertyValues pvs = new HttpServletBean.ServletConfigPropertyValues(this.getServletConfig(), this.requiredProperties);
    BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this);
    ResourceLoader resourceLoader = new ServletContextResourceLoader(this.getServletContext());
    bw.registerCustomEditor(Resource.class, new ResourceEditor(resourceLoader, this.getEnvironment()));
    this.initBeanWrapper(bw);
    bw.setPropertyValues(pvs, true);
    } catch (BeansException var4) {
    this.logger.error("Failed to set bean properties on servlet '" + this.getServletName() + "'", var4);
    throw var4;
    }
    // 这里进要进入到Framework的initServletBean方法了
    this.initServletBean();
    }

    从上面的init方法中,我们由它的initServletBean方法进入到FrameworkServlet的initServletBean方法,如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    protected final void initServletBean() throws ServletException {
    long startTime = System.currentTimeMillis();

    try {
    // 创建Spring容器,并调用onRefresh方法来完成配置文件的加载
    this.webApplicationContext = this.initWebApplicationContext();
    this.initFrameworkServlet();
    } catch (ServletException var5) {
    this.logger.error("Context initialization failed", var5);
    throw var5;
    } catch (RuntimeException var6) {
    this.logger.error("Context initialization failed", var6);
    throw var6;
    }
    }

    在容器加载的过程中会调用DispatchServlet的initStrategies方法来完成Dispatchservlet中定义的初始化工作,看DispatchServlet的源码:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    protected void onRefresh(ApplicationContext context) {
    this.initStrategies(context);
    }

    // 初始化SpringMVC框架需要的8个组件,这8个组件对应8个bean对象保存在DispatchServlet类中
    protected void initStrategies(ApplicationContext context) {
    // 用于处理文件上传服务,将Request包装成DefaultMultipartHttpServletRequest
    this.initMultipartResolver(context);
    // 用于处理应用的国际化问题,控制应用中的字符编码问题
    this.initLocaleResolver(context);
    // 用于定义一个主题
    this.initThemeResolver(context);
    // 用于定义用户设置的请求映射关系,将用户请求的URL映射后才能一个个Handler实例
    // 如果没有定义HandlerMapping,将获取默认的BeanNameURLHandlerMapping和DefaultAnnotaionHandlerMapping
    this.initHandlerMappings(context);
    // 用于根据Handler的类型定义不同的处理规则(调用Controller实例),默认的为:
    // HttpRequestHandlerAdapter、SimpleControllerHandlerAdapter、ThrowawayControllerAdapter、AnnotationMethodHandlerAdapter
    this.initHandlerAdapters(context);
    // 当Handle处理出错时,会通过这个Handler来统一处理,默认为SimpleMappingExceptionResolver,
    // 将错误日志记录在日志文件中,并且跳转到默认的错误页面
    this.initHandlerExceptionResolvers(context);
    // 将指定的ViewName按照定义的requestToViewNameTranslator替换成想要的格式,如加上前缀或者后缀。
    this.initRequestToViewNameTranslator(context);
    // 用于将View解析成页面,在ViewResolvers中可以设置多个解析策略,默认的解析策略为InternalResourceViewResolver,按照JSP页面来解析
    this.initViewResolvers(context);
    // 为一个请求存储意图为另外一个请求所使用的属性提供了一条途径(通常存储在session)
    this.initFlashMapManager(context);
    }

SpringMVC请求处理流程源码分析

关注完初始化init方法,我们要进入正式的流程分析了,其实就是在sevice方法里面(在其父类FrameworkServlet里),我们看看下面的源码:

1
2
3
4
5
6
7
protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
if(HttpMethod.PATCH.matches(request.getMethod())) {
this.processRequest(request, response);
} else {
super.service(request, response);
}
}

根据service方法,我们一步步调试进入service –> processRequest –> doService(将ApplicationContext、localeResolver、themeResolver等对象添加到request中以便后面使用) –> doDispatch,我们最终将目光定位在doDispatch,因为从它的方法体就可以看出它是整个SpringMVC的核心方法。我们看看DispatchServlet里面的doDispatch方法源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
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;
// 解析请求(匹配URL),获取HandlerExecutionChain对象
mappedHandler = this.getHandler(processedRequest);
if(mappedHandler == null || mappedHandler.getHandler() == null) {
this.noHandlerFound(processedRequest, response);
return;
}
// 从HandlerExecutionChain对象获取HandlerAdapter对象,实际上是从HandlerMapping对象中获取
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;
}
}

// 在controller方法执行前,执行拦截器的相关方法(pre) if(!mappedHandler.applyPreHandle(processedRequest, response)) {
return;
}
// 执行HandlerAdapter对象的handle方法,返回ModelAndView
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
if(asyncManager.isConcurrentHandlingStarted()) {
return;
}

this.applyDefaultViewName(processedRequest, mv);
// 在controller方法执行后,执行拦截器的相关方法(post)
mappedHandler.applyPostHandle(processedRequest, response, mv);
} catch (Exception var19) {
dispatchException = var19;
}
// 进行视图解析
this.processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
} catch (Exception var20) {
this.triggerAfterCompletion(processedRequest, response, mappedHandler, var20);
} catch (Error var21) {
this.triggerAfterCompletionWithError(processedRequest, response, mappedHandler, var21);
}

} finally {
if(asyncManager.isConcurrentHandlingStarted()) {
if(mappedHandler != null) {
mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
}
} else if(multipartRequestParsed) {
this.cleanupMultipart(processedRequest);
}

}
}

上面是整体的流程,下面我们具体到对MVC架构的三个模块的分析:

Control(C)

Spring MVC的Control主要由HandlerMapping和HandlerAdapters两个组件提供。

HandlerMapping并没有规定这个URL与应用的处理类如何映射,在这个接口中只定义了根据URL必须返回一个由HandlerExecutionChain代理的处理链,我们可以在这里处理链中添加任意的HandlerAdapters实例来处理这个URL对应的请求。这个和Servlet规范中的filter处理是类似的。

HandlerMapping的初始化

(可以参照HandlerMapping的子类SimpleUrlHandlerMapping里面的initApplicationContext方法代码)

HandlerMapping的初始化工作完成的两个最重要的工作就是将URL与Handler的对应关系保存在handlerMap集合中,并将所有的interceptors对象保存在adaptedInterceptors数组中,等到请求到来时执行所有的adaptedInterceptors数组中的Interceptor对象,所有的Interceptor对象必须实现HandlerInterceptor接口。

HandlerAdapter(可以看成Controller)的初始化

(可以参照HandlerAdapter的子类SimpleControllerHandlerAdapter里面的代码)

HandlerAdapter的初始化工作主要是创建一个HandlerAdapter对象,将这个HandlerAdapter对象保存在DispatcherServlet的HandlerAdapters集合中。当SpringMVC将某个URL对应到某个Handler时,在HandlerAdapters集合中查询那个HandlerAdapters对象supports这个Handler,那么HandlerAdapters就会被返回(设计模式),并调用这个HandlerAdapters接口对应的方法。如果这个HandlerAdapters对象是SimpleControllerHandlerAdapter,则将调用其ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler)方法。如果用户没有定义HandlerAdapter的实现类,默认的为:HttpRequestHandlerAdapter、SimpleControllerHandlerAdapter、ThrowawayControllerAdapter、AnnotationMethodHandlerAdapter。

Control的调用逻辑

根据DispatcherServlet的doDispath方法我们可以看到通过getHandler方法匹配到某个Handler并返回这个Handler的处理链HandlerExecutionChain对象,而这个HandlerExecutionChain对象将会包含一个匹配上的HandlerAdapter以及用户自定义的多个HandlerInterceptor对象。我们先看HandlerInterceptor接口,在HandlerInterceptor接口中有三个方法如下:

1
2
3
4
5
6
7
8
9
public interface HandlerInterceptor {
// 在Handler执行前
boolean preHandle(HttpServletRequest var1, HttpServletResponse var2, Object var3) throws Exception;
// 在Handler执行后
void postHandle(HttpServletRequest var1, HttpServletResponse var2, Object var3, ModelAndView var4) throws Exception;
// 在View渲染完成后,DispatchServlet返回之前执行。
// PS:当preHandler返回false时,当前的请求将在执行完该方法后直接返回,Handler不再执行
void afterCompletion(HttpServletRequest var1, HttpServletResponse var2, Object var3, Exception var4) throws Exception;
}

我们然后看看HandlerExecutionChain类的getHandler方法,你会发现返回的是Object对象,所以在这里Handler对象是没有类型的,Handler的类型是由HandlerAdapter(匹配到的)决定的。接下里执行Handler对象的具体方法,如果Handler对象的相应方法返回一个ModelAndView对象,接下去就去执行View渲染。

Model(M)

Model实例既在业务逻辑层被使用,也在渲染页面中被使用,我们这里主要讲一下在页面模板渲染中的使用。

如果Handler返回了ModelAndView对象,那么说明Handler需要传一个Model实例给View去渲染模板。可以说ModelAndView对象就是连接业务逻辑层与View视图层的桥梁,对SpringMVC来说它也是连接Handler与View的桥梁。

ModelAndView对象会持有一个ModelMap对象和一个View对象(可以查看ModelAndView的源码),ModelMap对象就是执行模板渲染时所需要的变量对应的实例(对应到Struts2的值栈),如JSP通过request.getAttribute(String)获得JSTL标签名对应的对象。ModelMap其实也是个Map,在Handler中将模板需要的对象存在这个Map中,然后传递到View对应的ViewResolvers中。

View(V)

Spring MVC的View主要由RequestToViewNameTranslator和ViewResolver两个组件提供。

  • RequestToViewNameTranslator支持用户自定义对ViewName的解析,如加上前缀或者后缀等。
  • ViewResolver会根据用户的请求的ViewName创建合适的模板引擎(解析器)来渲染最终的页面。ViewResolver会根据ViewName创建一个View对象,调用View对象的render方法渲染页面。

我们重点关注这个ViewResolver,先看他的类图:

ViewResolver

我们从UrlBaseViewResolver对象的loadView方法->buildView方法可以看到如下代码:

1
2
3
4
5
6
7
8
9
10
protected AbstractUrlBasedView buildView(String viewName) throws Exception {
AbstractUrlBasedView view = (AbstractUrlBasedView)BeanUtils.instantiateClass(this.getViewClass());
view.setUrl(this.getPrefix() + viewName + this.getSuffix());
String contentType = this.getContentType();
if(contentType != null) {
view.setContentType(contentType);
}
... 省略
return view;
}

结合类图可以发现不同的解析器生成的View对象是不一样的。

获得View对象之后就可以调用View对象的render方法渲染页面。

参考

  1. 《深入分析Java Web技术内幕》
  2. 一步步分析SpringMVC源码