SpringMVC(一):请求处理流程与关键类

学习完IOC容器的初始化,来看一下SpringMVC的实现。

一、Spring MVC的请求处理流程

首先必须知道Spring MVC处理请求的流程,从接受请求到生成响应,关于这个流程有很多种说法,虽然有略微差别但主体思路都是相同的,找到一个比较直观的图片

  1. 首先用户发送请求——>DispatcherServlet,前端控制器收到请求后自己不进行处理,而是委托给其他的解析器进行处理,作为统一访问点,进行全局的流程控制;
  2. DispatcherServlet——>HandlerMapping, HandlerMapping将会把请求映射为HandlerExecutionChain对象(包含一个Handler处理器(页面控制器)对象、多个HandlerInterceptor拦截器)对象,通过这种策略模式,很容易添加新的映射策略;
  3. DispatcherServlet——>HandlerAdapter,DispatcherServlet 根据获得的Handler,选择一个合适的HandlerAdapter。HandlerAdapter将会把处理器包装为适配器,从而支持多种类型的处理器,即适配器设计模式的应用,从而很容易支持很多类型的处理器;
  4. HandlerAdapter——>处理器功能处理方法的调用,HandlerAdapter将会根据适配的结果调用真正的处理器的功能处理方法,完成功能处理;
  5. Handler执行完成后,返回一个ModelAndView对象(包含模型数据、逻辑视图名);
  6. ModelAndView的逻辑视图名——> ViewResolver, ViewResolver将把逻辑视图名解析为具体的View,通过这种策略模式,很容易更换其他视图技术;
  7. View——>渲染,View会根据传进来的Model模型数据进行渲染,此处的Model实际是一个Map数据结构,因此很容易支持其他视图技术;
  8. 返回控制权给DispatcherServlet,由DispatcherServlet返回响应给用户,到此一个流程结束。

二、源码中的相关类与接口

上面这个经典的流程就是SpringMVC的核心,这其中涉及到几个关键的类和接口。

1、DispatcherServlet

从流程中可以看见,DispatcherServlet是整个流程的核心,先来看一下它的继承关系:

QQ截图20170109110308

它继承自HttpServlet,也就是说它是一个标准的Servlet,所以它的使用方法就是在web.xml中配置,然后再web容器初始化的时候加载。

这个类中定义了整个处理流程,后面再详细分析它。

2、HandlerMapping

2.1、源码

DispatcherServlet处理请求的第一步就是根据url来获取相应的HandlerExecutionChain ,它是一个接口:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package org.springframework.web.servlet;

import javax.servlet.http.HttpServletRequest;

public interface HandlerMapping {

String PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE = HandlerMapping.class.getName() + ".pathWithinHandlerMapping";

String BEST_MATCHING_PATTERN_ATTRIBUTE = HandlerMapping.class.getName() + ".bestMatchingPattern";


String INTROSPECT_TYPE_LEVEL_MAPPING = HandlerMapping.class.getName() + ".introspectTypeLevelMapping";


String URI_TEMPLATE_VARIABLES_ATTRIBUTE = HandlerMapping.class.getName() + ".uriTemplateVariables";

String MATRIX_VARIABLES_ATTRIBUTE = HandlerMapping.class.getName() + ".matrixVariables";

String PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE = HandlerMapping.class.getName() + ".producibleMediaTypes";

HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception;
}

这个接口中有一堆字符串,还有唯一一个关键的方法:

HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception;

它会返回一个HandlerExecutionChain对象。

2.2、调用

而它在哪里进行调用呢?回到DispatcherServlet的处理流程,当,由标准Servlet类处理方法doGet或者doPost,

  • DispatcherServlet中有一个HandlerMapping类的List
  • DispatcherServlet接收到web请求后会遍历这个List
  • 该web请求的HttpServletRequest对象为参数,依次调用其getHandler方法,第一个不为null的调用结果,将被返回。

DispatcherServlet类中的这个方法如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/** List of HandlerMappings used by this servlet */
private List<HandlerMapping> handlerMappings;

protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
for (HandlerMapping hm : this.handlerMappings) {
if (logger.isTraceEnabled()) {
logger.trace(
"Testing handler map [" + hm + "] in DispatcherServlet with name '" + getServletName() + "'");
}
HandlerExecutionChain handler = hm.getHandler(request);
if (handler != null) {
return handler;
}
}
return null;
}

2.3、作用

上面这个方法就对应了流程中的第二步,一个web请求经过处理后,会得到一个HandlerExecutionChain对象,这就是SpringMVC对URl映射给出的回答。需要留意的是,HandlerMapping接口的getHandler方法参数是HttpServletRequest,这意味着,HandlerMapping的实现类可以利用HttpServletRequest中的所有信息来做出这个HandlerExecutionChain对象的生成”决策“。这包括,请求头、url路径、cookie、session、参数等等一切你从一个web请求中可以得到的任何东西(最常用的是url路径)。

SpirngMVC的第一个扩展点,就出现在这里。我们可以编写任意的HandlerMapping实现类,依据任何策略来决定一个web请求到HandlerExecutionChain对象的生成。

这里涉及到一个设计模式:策略模式

2.4、策略模式

策略模式属于对象的行为模式。其用意是针对一组算法,将每一个算法封装到具有共同接口的独立的类中,从而使得它们可以相互替换。策略模式使得算法可以在不影响到客户端的情况下发生变化。

这个模式涉及到三个角色:

  • 环境(Context)角色:持有一个Strategy的引用。对应的就是DispatcherServlet,它持有多个策略,根据不同的请求进行判断。
  • 抽象策略(Strategy)角色:这是一个抽象角色,通常由一个接口或抽象类实现。此角色给出所有的具体策略类所需的接口。对应的就是HandlerMapping接口。
  • 具体策略(ConcreteStrategy)角色:包装了相关的算法或行为。对应一个具体的HandlerMapping实现类。

优点

  • 策略模式提供了管理相关的算法族的办法。策略类的等级结构定义了一个算法或行为族。恰当使用继承可以把公共的代码移到父类里面,从而避免代码重复
  • 使用策略模式可以避免使用多重条件(if-else)语句。多重条件语句不易维护,它把采取哪一种算法或采取哪一种行为的逻辑与算法或行为的逻辑混合在一起,统统列在一个多重条件语句里面,比使用继承的办法还要原始和落后。

缺点

  • 客户端必须知道所有的策略类,并自行决定使用哪一个策略类。这就意味着客户端必须理解这些算法的区别,以便适时选择恰当的算法类。换言之,策略模式只适用于客户端知道算法或行为的情况。对应的就是DispatcherServlet中有一个HandlerMapping的List。
  • 由于策略模式把每个具体的策略实现都单独封装成为类,如果备选的策略很多的话,那么对象的数目就会很可观。

3、HandlerExecutionChain

如果getHandler的返回不为null,那么则返回它的HandlerExecutionChain对象。

从名字可以直观的看得出,这个对象是一个执行链的封装。熟悉Struts2的都知道,Action对象也是被层层拦截器包装,这里可以做个类比,说明SpringMVC确实是吸收了Struts2的部分设计思想。

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
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
package org.springframework.web.servlet;

public class HandlerExecutionChain {

private static final Log logger = LogFactory.getLog(HandlerExecutionChain.class);

private final Object handler;

private HandlerInterceptor[] interceptors;

private List<HandlerInterceptor> interceptorList;

private int interceptorIndex = -1;

public HandlerExecutionChain(Object handler) {
this(handler, (HandlerInterceptor[]) null);
}

public HandlerExecutionChain(Object handler, HandlerInterceptor... interceptors) {
if (handler instanceof HandlerExecutionChain) {
HandlerExecutionChain originalChain = (HandlerExecutionChain) handler;
this.handler = originalChain.getHandler();
this.interceptorList = new ArrayList<>();
CollectionUtils.mergeArrayIntoCollection(originalChain.getInterceptors(), this.interceptorList);
CollectionUtils.mergeArrayIntoCollection(interceptors, this.interceptorList);
}
else {
this.handler = handler;
this.interceptors = interceptors;
}
}

public Object getHandler() {
return this.handler;
}

public void addInterceptor(HandlerInterceptor interceptor) {
initInterceptorList().add(interceptor);
}

public void addInterceptors(HandlerInterceptor... interceptors) {
if (!ObjectUtils.isEmpty(interceptors)) {
initInterceptorList().addAll(Arrays.asList(interceptors));
}
}

private List<HandlerInterceptor> initInterceptorList() {
if (this.interceptorList == null) {
this.interceptorList = new ArrayList<>();
if (this.interceptors != null) {
// An interceptor array specified through the constructor
this.interceptorList.addAll(Arrays.asList(this.interceptors));
}
}
this.interceptors = null;
return this.interceptorList;
}

public HandlerInterceptor[] getInterceptors() {
if (this.interceptors == null && this.interceptorList != null) {
this.interceptors = this.interceptorList.toArray(new HandlerInterceptor[this.interceptorList.size()]);
}
return this.interceptors;
}

boolean applyPreHandle(HttpServletRequest request, HttpServletResponse response) throws Exception {
HandlerInterceptor[] interceptors = getInterceptors();
if (!ObjectUtils.isEmpty(interceptors)) {
for (int i = 0; i < interceptors.length; i++) {
HandlerInterceptor interceptor = interceptors[i];
if (!interceptor.preHandle(request, response, this.handler)) {
triggerAfterCompletion(request, response, null);
return false;
}
this.interceptorIndex = i;
}
}
return true;
}

void applyPostHandle(HttpServletRequest request, HttpServletResponse response, ModelAndView mv) throws Exception {
HandlerInterceptor[] interceptors = getInterceptors();
if (!ObjectUtils.isEmpty(interceptors)) {
for (int i = interceptors.length - 1; i >= 0; i--) {
HandlerInterceptor interceptor = interceptors[i];
interceptor.postHandle(request, response, this.handler, mv);
}
}
}

void triggerAfterCompletion(HttpServletRequest request, HttpServletResponse response, Exception ex)
throws Exception {

HandlerInterceptor[] interceptors = getInterceptors();
if (!ObjectUtils.isEmpty(interceptors)) {
for (int i = this.interceptorIndex; i >= 0; i--) {
HandlerInterceptor interceptor = interceptors[i];
try {
interceptor.afterCompletion(request, response, this.handler, ex);
}
catch (Throwable ex2) {
logger.error("HandlerInterceptor.afterCompletion threw exception", ex2);
}
}
}
}

void applyAfterConcurrentHandlingStarted(HttpServletRequest request, HttpServletResponse response) {
HandlerInterceptor[] interceptors = getInterceptors();
if (!ObjectUtils.isEmpty(interceptors)) {
for (int i = interceptors.length - 1; i >= 0; i--) {
if (interceptors[i] instanceof AsyncHandlerInterceptor) {
try {
AsyncHandlerInterceptor asyncInterceptor = (AsyncHandlerInterceptor) interceptors[i];
asyncInterceptor.afterConcurrentHandlingStarted(request, response, this.handler);
}
catch (Throwable ex) {
logger.error("Interceptor [" + interceptors[i] + "] failed in afterConcurrentHandlingStarted", ex);
}
}
}
}
}

@Override
public String toString() {
if (this.handler == null) {
return "HandlerExecutionChain with no handler";
}
StringBuilder sb = new StringBuilder();
sb.append("HandlerExecutionChain with handler [").append(this.handler).append("]");
if (!CollectionUtils.isEmpty(this.interceptorList)) {
sb.append(" and ").append(this.interceptorList.size()).append(" interceptor");
if (this.interceptorList.size() > 1) {
sb.append("s");
}
}
return sb.toString();
}

}

有点长,关键地方是最上面的两个属性:

1
2
3
private final Object handler;

private HandlerInterceptor[] interceptors;

正如上面流程中说的。一个HandlerExecutionChain对象包含一个Handler处理器(页面控制器)对象、多个HandlerInterceptor拦截器对象。

和Strust2中的实现一样,下面的过程都将围绕这个执行链展开。

4、HandlerInterceptor

下面看一下拦截器,它是一个接口,里面有三个方法,这个在项目确实使用过,比如实现用户角色判断,就需要自己定义一个拦截器

看一下在项目中自己写的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class MyInterceptor extends HandlerInterceptorAdapter {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
System.out.println(">>>MyInterceptor>>>>>"+request.getRequestURI());
if(request.getRequestURI().equals("/psxt")||request.getRequestURI().equals("/psxt/login")
||request.getRequestURI().equals("/psxt/superadmin")
||request.getRequestURI().equals("/psxt/schooladmin")
||request.getRequestURI().equals("/psxt/teacheradmin")){
return true;
}

User user = (User) request.getSession().getAttribute(SessionKey.USERNAME.name());
if(user==null||user.getRole()==0){
response.sendRedirect(request.getContextPath() + "/psxt302");
return false;
}

return true;
}
}

这里只重写了一个方法,用于用户角色的判断,而这个接口实际上有三个方法,用于重写,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
package org.springframework.web.servlet;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.springframework.web.method.HandlerMethod;

public interface HandlerInterceptor {
default boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {

return true;
}

default void postHandle(
HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView)
throws Exception {
}

default void afterCompletion(
HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)
throws Exception {
}
}

从这三个方法的名称也可以看出,主要是围绕handler的执行展开:

  • 在真正调用其handler对象前,HandlerInterceptor接口实现类组成的数组将会被遍历,其preHandle方法会被依次调用,然后真正的handler对象将被调用。
  • handler对象被调用后,就生成了需要的响应数据,在将处理结果写到HttpServletResponse对象之前(SpringMVC称为渲染视图),其postHandle方法会被依次调用。
  • 视图渲染完成后,最后afterCompletion方法会被依次调用,整个web请求的处理过程就结束了。

HandlerInterceptor,是SpringMVC的第二个扩展点的暴露,通过自定义拦截器,我们可以在一个请求被真正处理之前、请求被处理但还没输出到响应中、请求已经被输出到响应中之后这三个时间点去做任何我们想要做的事情。Struts2框架的成功,就是源于这种拦截器的设计,SpringMVC吸收了这种设计思想,并推陈出新,更合理的划分了三个不同的时间点,从而给web请求处理这个流程,提供了更大的扩展性。

5、HandlerAdapter

接下来就是这个handler了,涉及到一个接口HandlerAdapter。感觉这个接口和HandlerMapping很像,采用和它一样的分析方式

5.1、源码

接口定义的源代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
package org.springframework.web.servlet;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public interface HandlerAdapter {

boolean supports(Object handler);

ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception;

long getLastModified(HttpServletRequest request, Object handler);

}

5.2、调用

在DispatcherServlet中,除了上面提到的HandlerMapping实现类的列表,同样也注册了一个HandlerAdapter实现类组成的列表,有代码为证。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
/** List of HandlerMappings used by this servlet */
private List<HandlerMapping> handlerMappings;

/** List of HandlerAdapters used by this servlet */
private List<HandlerAdapter> handlerAdapters;

protected HandlerAdapter getHandlerAdapter(Object handler) throws ServletException {
for (HandlerAdapter ha : this.handlerAdapters) {
if (logger.isTraceEnabled()) {
logger.trace("Testing handler adapter [" + ha + "]");
}
if (ha.supports(handler)) {
return ha;
}
}
throw new ServletException("No adapter for handler [" + handler +
"]: The DispatcherServlet configuration needs to include a HandlerAdapter that supports this handler");
}

5.3、作用

HandlerExecutionChain中的handler对象会被作为参数传递进去,在DispatcherServlet类中注册的HandlerAdapter实现类列表会被遍历,然后返回第一个supports方法返回true的HandlerAdapter对象,用这个HandlerAdapter实现类中的handle方法处理handler对象,并返回ModelAndView这个包含了视图和数据的对象。

HandlerAdapter就是SpringMVC提供的第三个扩展点,你可以提供自己的实现类来处理handler对象。

5.4、适配器模式

这里又涉及到一个适配器模式:

适配器模式把一个类的接口变换成客户端所期待的另一种接口,从而使原本因接口不匹配而无法在一起工作的两个类能够在一起工作。

模式所涉及的角色有:

  • 目标(Target)角色:这就是所期待得到的接口。注意:由于这里讨论的是类适配器模式,因此目标不可以是类。
  • 源(Adapee)角色:现在需要适配的类。
  • 适配器(Adaper)角色:适配器类是本模式的核心。适配器把源接口转换成目标接口。显然,这一角色不可以是接口,而必须是具体类。

虽然这个接口的名字中带有Adapter,但是不知道这里到底是不是适配器模式,详细的分析可以参考

http://zouruixin.iteye.com/blog/1441846,觉得这个讲解的很明白。

正如上面流程中说到的HandlerAdapter将会把处理器包装为适配器,从而支持多种类型的处理器,即适配器设计模式的应用,从而很容易支持很多类型的处理器;这里的处理器指的应该就是Controller。

由于Controller的类型不同,有多种实现方式,那么调用方式就不是确定的,如果需要直接调用Controller方法,需要在代码中写成如下形式:

1
2
3
4
5
6
7
8
if(mappedHandler.getHandler() instanceof MultiActionController){  
((MultiActionController)mappedHandler.getHandler()).xxx
}else if(mappedHandler.getHandler() instanceof XXX){
...
}else if(...){
...
}
...

这样假设如果我们增加一个HardController,就要在代码中加入一行 if(mappedHandler.getHandler() instanceof HardController)
这种形式就使得程序难以维护,也违反了设计模式中的开闭原则 – 对扩展开放,对修改关闭。

因此Spring定义了一个适配接口,使得每一种Controller有一种对应的适配器实现类,
让适配器代替Controller执行相应的方法。这样在扩展Controller 时,只需要增加一个适配器类就完成了SpringMVC的扩展了,真的是很精巧的做法!

在一个典型的SpringMVC调用中,

  1. HandlerExecutionChain中封装handler对象就是用@Controller注解标识的类的一个实例
  2. 根据类级别和方法级别的@RequestMapping注解,由默认注册的RequestMappingHandlerMapping生成HandlerExecutionChain对象
  3. RequestMappingHandlerAdapter类来执行这个HandlerExecutionChain对象
  4. 生成最终的ModelAndView对象后,再由具体的View对象的render方法渲染视图。

三、总结

  1. 把源码编译后导入到eclipse里面以后看源码的效率提高了不少,学到一个Ctrl+T快捷键
  2. 了解整个MVC框架处理请求的流程
  3. 学习了两个新的设计模式:

    • 策略模式
    • 适配器模式
  4. 进一步体会到了Spring在设计上的优雅与巧妙,Spring中的一个原则:开闭原则:对扩展开放,对修改关闭
    • 类中所有的变量声明,几乎都以接口的形式给出,并没有绑定在具体的实现类上。
    • 使用模版方法模式,在父类中对基础行为进行定义,让子类实现模版方法扩展行为。

四、参考地址

http://blog.csdn.net/zuoluoboy/article/details/19766131

http://jinnianshilongnian.iteye.com/blog/1594806

http://www.cnblogs.com/java-my-life/archive/2012/05/10/2491891.html

http://www.cnblogs.com/java-my-life/archive/2012/04/13/2442795.html

https://my.oschina.net/lichhao/blog/99039