引:现在基于所有的Web应用都离不开Spring,用了Spring,你自然会去用他家的MVC框架——SpringMVC!所以掌握SpringMVC也是非常重要的。
SpringMVC请求处理流程总览
我们可以用下面一张图来介绍SpringMVC的核心组件和请求处理流程:
流程中出现的核心组件如下:
- DispatcherServlet是springmvc中的前端控制器,负责接收request并将request转发给对应的处理组件.
- HanlerMapping是springmvc中完成url到controller映射的组件.DispatcherServlet接收request,然后从HandlerMapping查找处理request的controller.
- Controller(HandlerAdapter)处理request,并返回ModelAndView对象,Controller是springmvc中负责处理request的组件(类似于struts2中的Action)
- ModelAndView是封装Model对象和View对象的组件
- ViewResolver是视图解析器,负责解析ModelAndView对象生成对应的View对象
- View视图组件,复制渲染页面
整个流程的大致流程如下:
- 当request到来时,DispatcherServlet对request进行捕获,并执行doService方法,继而执行doDispatch方法。
- HandlerMapping解析请求,并且返回HandlerExecutionChain(其中包含对应的controller和interceptors)
- 通过HandlerExecutionChain得到Handler相关类,Handler先执行拦截器的pre相关方法,接着执行handler方法,最后调用拦截器的post相关方法
- 解析handler方法返回的ModelAndView
- 根据ViewResolver(可以在配置文件中配置,也就是视图解析器)生成View对象
- View对象渲染页面并response给客户端
SpringMVC请求处理初始化源码分析
首先,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>进入DispatchServlet的分析,先看它的类图:
它是一个Servlet的子类,那么我们就要专注于它的init、service(后续分析)、doGet、doPost等相关方法,在它的父类HttpServeltBean,我们找到了init方法,如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17public 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
15protected 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
28protected 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
7protected 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
70protected 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
9public 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,先看他的类图:
我们从UrlBaseViewResolver对象的loadView方法->buildView方法可以看到如下代码:
1 | protected AbstractUrlBasedView buildView(String viewName) throws Exception { |
结合类图可以发现不同的解析器生成的View对象是不一样的。
获得View对象之后就可以调用View对象的render方法渲染页面。
参考
- 《深入分析Java Web技术内幕》
- 一步步分析SpringMVC源码