SpringMvc配置详解及源码分析

SpringMVC配置详解

配置前端控制器DispatcherServlet

SpringMVC是一个基于DispatcherServlet的MVC框架,每一个请求最先访问的都是DispatcherServlet,DispatcherServlet是继承自HttpServlet的,DispatcherServlet负责转发每一个Request请求给相应的Handler,Handler处理以后再返回相应的视图(View)和模型(Model),返回的视图和模型都可以不指定,即可以只返回Model或只返回View或都不返回。

首先,在web.xml文件中声明DispatcherServlet:

web.xml
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
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
version="3.1">
<!--<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>
/WEB-INF/applicationContext.xml,
classpath:springmvc.xml,
</param-value>
</context-param>-->
<!--<context-param>
<param-name>log4jConfigLocation</param-name>
<param-value>log4j.properties</param-value>
</context-param>-->
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<!--SpringMVC前端控制器-->
<servlet>
<servlet-name>dispatcher</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<!--
contextConfigLocation 配置SpringMVC加载的配置文件(配置处理器映射器、适配器等)
如果不配置,默认加载的是/WEB_INF/servlet名称-servlet.xml(dispatcher-servlet.xml)
-->
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:spring/spring-mvc.xml</param-value>
</init-param>
<!--
1)load-on-startup元素标记容器是否在启动的时候就加载这个servlet(实例化并调用其
init()方法)。
2)它的值必须是一个整数,表示servlet应该被载入的顺序
2)当值为0或者大于0时,表示容器在应用启动时就加载并初始化这个servlet;
3)当值小于0或者没有指定时,则表示容器在该servlet被选择时才会去加载。
4)正数的值越小,该servlet的优先级越高,应用启动时就越先加载
5)当值相同时,容器就会自己选择顺序来加载。
所以,<load-on-startup>x</load-on-startup>,
中x的取值1,2,3,4,5代表的是优先级,而非启动延迟时间。
-->
<load-on-startup>1</load-on-startup>
<!--
<async-supported>子标签,该标签的默认取值为false,
要启用异步处理支持,则将其设为true即可
-->
<async-supported>true</async-supported>
</servlet>

<servlet-mapping>
<servlet-name>dispatcher</servlet-name>
<!--
① *.action,访问以.action结尾的由DispatcherServlet进行解析;
② /,所有/的地址都由DispatcherServlet进行解析,
对于静态的文件的解析需要配置不让DispatcherServlet进行解析,
使用此种风格可以实现RESTFull风格的url解析;
③ /*,这样配置错误,使用这种配置时,最终要转发到jsp页面时,
仍然会由DsipatcherServlet解析jsp地址,不能根据jsp页面找到handler,会报错
-->
<url-pattern>*.action</url-pattern>
</servlet-mapping>

<!--welcome pages-->
<welcome-file-list>
<welcome-file>index.jsp</welcome-file>
</welcome-file-list>
</web-app>

配置处理器适配器、映射器、视图解析器等

然后,在classpath下的spring-mvc.xml中配置处理器适配器:

spring-mvc.xml
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
142
143
144
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<!--
说明:前端控制器加载处理映射器、适配器、视图解析器等组件,如果不在spring-mvc.xml中配置,则会自动使用spring-webmvc-4.3.1.RELEASE.jar包中org\springframework.web.servlet中DispatcherSerlvet.properties配置文件中的默认配置。
-->


<!--启用spring的一些annotation -->
<!--<context:annotation-config/>-->

<!-- ****************配置Handler 开始*********************-->
<bean id="queryController" name="/query.action" class="com.syshlang.smm.controller.QueryController"></bean>
<bean id="queryHttpController" name="/httpquery.action"
class="com.syshlang.smm.controller.QueryHttpController">
</bean>
<!--
对于注解的Handler可以单个配置,实际开发中建议使用组件扫描
可以扫描controller、service、...这里让扫描controller,指定controller的包
-->
<!--<bean class="com.syshlang.smm.controller.QueryAnnotationController" />-->
<context:component-scan base-package="com.syshlang.smm.controller">
</context:component-scan>


<!-- ****************配置Handler 结束*********************-->


<!-- *********************配置处理器映射器 开始*************************************** -->
<!--
所有的适配器都实现了HandlerMapping接口
-->
<!--第一种方式:最简单的,一个class对应一个handler(非注解)
将bean的name当作url来查找,需要在配置Handler时配置bean的name(就是url),如上
-->
<bean class="org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping">
</bean>
<!--第二种方式 集中映射配置 (非注解)
SimpleUrlHandlerMapping是BeanNameUrlHandlerMapping的增强版本,
它可以将url和处理器的bean的id进行统一的映射配置
-->
<bean class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">
<property name="mappings">
<props>
<prop key="/query0.action">queryController</prop>
<prop key="/query1.action">queryController</prop>
<prop key="/httpquery0.action">queryHttpController</prop>
<prop key="/httpquery1.action">queryHttpController</prop>
<!--<prop key="url地址">Controller的bean的id</prop>-->
</props>
</property>
</bean>

<!-- 第三种方式 注解映射器
在spring3.1之前使用org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping注解映射器。

在spring3.1之后使用org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping注解映射器
-->
<bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping"></bean>

<!--
结论:① 多个映射器可以并存;
② 一个bean可以对应多个url;
③HandlerMapping 无需配置,springmvc可以默认启动;
④使用注解的映射器和注解的适配器。(注解的映射器和注解的适配器必须配对使用)。
-->

<!-- ********************************配置处理器映射器 结束************************** -->

<!-- *********************配置处理器适配器 开始*************************************** -->
<!--
所有的处理器适配器都实现HandlerAdapter接口
-->
<!--①配置处理器适配器 (非注解)
public boolean supports(Object handler) {
return handler instanceof Controller;
}

SimpleControllerHandlerAdapter中的support方法可以看出:
编写的handler需要实现Controller接口
public interface Controller {
ModelAndView handleRequest(HttpServletRequest var1, HttpServletResponse var2) throws Exception;
}
由此,开发Handler时需要实现Controller接口才能由org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter来执行
-->
<bean class="org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter">
</bean>
<!--
②HttpRequestHandlerAdapter 是http请求处理适配器, (非注解)
所有实现了HttpRequestHandler接口的bean通过此适配器进行适配、执行
public boolean supports(Object handler) {
return handler instanceof HttpRequestHandler;
}

由HttpRequestHandlerAdapter中的support方法可以看出:
编写的handler需要实现HttpRequestHandler接口
public interface HttpRequestHandler {
void handleRequest(HttpServletRequest var1, HttpServletResponse var2) throws ServletException, IOException;
}
-->
<bean class="org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter"></bean>

<!--③注解适配器
在spring3.1之前使用org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter注解适配器。

在spring3.1之后使用org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter注解适配器。

-->
<bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter"></bean>
<!-- *********************配置处理器适配器 结束*************************************** -->

<!-- 使用 mvc:annotation-driven代替上边注解映射器和注解适配器配置
mvc:annotation-driven默认加载很多的参数绑定方法,
比如json转换解析器就默认加载了,如果使用mvc:annotation-driven不用配置上边的RequestMappingHandlerMapping和RequestMappingHandlerAdapter
实际开发时使用mvc:annotation-driven
-->
<!-- <mvc:annotation-driven></mvc:annotation-driven> -->


<!-- *********************配置视图解析器 开始*************************************** -->
<!--视图解析器
解析jsp文件,默认使用jstl的标签
classpatch下要有jstl的jar包
-->
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver"></bean>
<!-- *********************配置视图解析器 结束*************************************** -->

<bean id="log4jInitialization"
class="org.springframework.beans.factory.config.MethodInvokingFactoryBean">
<property name="targetClass" value="org.springframework.util.Log4jConfigurer" />
<property name="targetMethod" value="initLogging" />
<property name="arguments">
<list>
<value>classpath:log4j.properties</value>
</list>
</property>
</bean>

</beans>

开发Handler

在此处只举一例,如下:
通过实现 controller接口,由org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter适配器执行的情况。

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
package com.syshlang.smm.controller;

import com.syshlang.smm.pojo.QueryPojo;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.Controller;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.ArrayList;
import java.util.List;

/**
* Created by sunys on 2017/7/1 2:44.
* Description: 使用SimpleControllerHandlerAdapter处理器适配器实现Controller接口的处理器
*/
public class QueryController implements Controller{

//添加一个日志器
private static final Logger logger = LoggerFactory.getLogger(QueryController.class);

@Override
public ModelAndView handleRequest(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) throws Exception {
//调用service层,查询数据库
List<QueryPojo> list = new ArrayList<QueryPojo>();
QueryPojo pojo = new QueryPojo();
pojo.setId("1");
pojo.setName("aaaaaa");
list.add(pojo);
//返回ModelAndView
ModelAndView modelAndView = new ModelAndView();
//相当于request的setAttribut,在jsp页面通过list取值
modelAndView.addObject("listpojo",list);
modelAndView.addObject("des","使用SimpleControllerHandlerAdapter处理器适配器实现Controller接口的处理器");
//指定视图View
modelAndView.setViewName("/view/query.jsp");
//输出日志文件
logger.info("the first jsp pages");
return modelAndView;
}
}


源码分析

源码分析

根据配置过程,通过前端控制器源码分析SpringMVC的执行过程。

第一步:前端控制器接收请求

在org.springframework.web.servlet.DispatcherServlet中可以看到doDispatch方法。前端控制器接收请求,.action类型的URL通过过滤器进入DispatcherServlet类,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
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;
Object dispatchException = null;

try {
processedRequest = this.checkMultipart(request);
multipartRequestParsed = processedRequest != request;
mappedHandler = this.getHandler(processedRequest);
if (mappedHandler == null || mappedHandler.getHandler() == null) {
this.noHandlerFound(processedRequest, response);
return;
}

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;
}
}

if (!mappedHandler.applyPreHandle(processedRequest, response)) {
return;
}

mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
if (asyncManager.isConcurrentHandlingStarted()) {
return;
}

this.applyDefaultViewName(processedRequest, mv);
mappedHandler.applyPostHandle(processedRequest, response, mv);
} catch (Exception var20) {
dispatchException = var20;
} catch (Throwable var21) {
dispatchException = new NestedServletException("Handler dispatch failed", var21);
}

this.processDispatchResult(processedRequest, response, mappedHandler, mv, (Exception)dispatchException);
} catch (Exception var22) {
this.triggerAfterCompletion(processedRequest, response, mappedHandler, var22);
} catch (Throwable var23) {
this.triggerAfterCompletion(processedRequest, response, mappedHandler, new NestedServletException("Handler processing failed", var23));
}

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

}
}

第二步:前端控制器调用处理器映射器查找 Handler

在doDispatch()方法中调用了DispatcherServlet类的getHandler方法。映射器根据request当中的URL,找到了Handler,最终返回一个执行器的链(HandlerExecutionChain)。这个链里面有Handler。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
Iterator var2 = this.handlerMappings.iterator();

HandlerExecutionChain handler;
do {
if (!var2.hasNext()) {
return null;
}

HandlerMapping hm = (HandlerMapping)var2.next();
if (this.logger.isTraceEnabled()) {
this.logger.trace("Testing handler map [" + hm + "] in DispatcherServlet with name '" + this.getServletName() + "'");
}

handler = hm.getHandler(request);
} while(handler == null);

return handler;
}

第三步:调用处理器适配器执行Handler,得到执行结果ModelAndView

1
2
3
...
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
...

第四步:视图渲染,将model数据填充到request域。

视图解析,得到view:
在doDispatch()方法中

1
this.processDispatchResult(processedRequest, response, mappedHandler, mv, (Exception)dispatchException);
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
private void processDispatchResult(HttpServletRequest request, HttpServletResponse response, HandlerExecutionChain mappedHandler, ModelAndView mv, Exception exception) throws Exception {
boolean errorView = false;
if (exception != null) {
if (exception instanceof ModelAndViewDefiningException) {
this.logger.debug("ModelAndViewDefiningException encountered", exception);
mv = ((ModelAndViewDefiningException)exception).getModelAndView();
} else {
Object handler = mappedHandler != null ? mappedHandler.getHandler() : null;
mv = this.processHandlerException(request, response, handler, exception);
errorView = mv != null;
}
}

if (mv != null && !mv.wasCleared()) {
this.render(mv, request, response);
if (errorView) {
WebUtils.clearErrorRequestAttributes(request);
}
} else if (this.logger.isDebugEnabled()) {
this.logger.debug("Null ModelAndView returned to DispatcherServlet with name '" + this.getServletName() + "': assuming HandlerAdapter completed request handling");
}

if (!WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) {
if (mappedHandler != null) {
mappedHandler.triggerAfterCompletion(request, response, (Exception)null);
}

}
}

在processDispatchResulth()方法中

1
this.render(mv, request, response);
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
protected void render(ModelAndView mv, HttpServletRequest request, HttpServletResponse response) throws Exception {
Locale locale = this.localeResolver.resolveLocale(request);
response.setLocale(locale);
View view;
if (mv.isReference()) {
view = this.resolveViewName(mv.getViewName(), mv.getModelInternal(), locale, request);
if (view == null) {
throw new ServletException("Could not resolve view with name '" + mv.getViewName() + "' in servlet with name '" + this.getServletName() + "'");
}
} else {
view = mv.getView();
if (view == null) {
throw new ServletException("ModelAndView [" + mv + "] neither contains a view name nor a " + "View object in servlet with name '" + this.getServletName() + "'");
}
}

if (this.logger.isDebugEnabled()) {
this.logger.debug("Rendering view [" + view + "] in DispatcherServlet with name '" + this.getServletName() + "'");
}

try {
if (mv.getStatus() != null) {
response.setStatus(mv.getStatus().value());
}

view.render(mv.getModelInternal(), request, response);
} catch (Exception var7) {
if (this.logger.isDebugEnabled()) {
this.logger.debug("Error rendering view [" + view + "] in DispatcherServlet with name '" + this.getServletName() + "'", var7);
}

throw var7;
}
}

在render()方法中

1
view = this.resolveViewName(mv.getViewName(), mv.getModelInternal(), locale, request);

调用view的渲染方法,将model数据填充到request域

1
view.render(mv.getModelInternal(), request, response);

根据InternalResourceView的继承关系:org.springframework.web.servlet.view.AbstractView ,org.springframework.web.servlet.view.AbstractUrlBasedView,org.springframework.web.servlet.view.InternalResourceView 最终找到render方法在AbstractView中,如下代码所示:

1
2
3
4
5
6
7
8
9
public void render(Map<String, ?> model, HttpServletRequest request, HttpServletResponse response) throws Exception {
if (this.logger.isTraceEnabled()) {
this.logger.trace("Rendering view with name '" + this.beanName + "' with model " + model + " and static attributes " + this.staticAttributes);
}

Map<String, Object> mergedModel = this.createMergedOutputModel(model, request, response);
this.prepareResponse(request, response);
this.renderMergedOutputModel(mergedModel, this.getRequestToExpose(request), response);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
protected void exposeModelAsRequestAttributes(Map<String, Object> model, HttpServletRequest request) throws Exception {
Iterator var3 = model.entrySet().iterator();

while(var3.hasNext()) {
Entry<String, Object> entry = (Entry)var3.next();
String modelName = (String)entry.getKey();
Object modelValue = entry.getValue();
if (modelValue != null) {
request.setAttribute(modelName, modelValue);
if (this.logger.isDebugEnabled()) {
this.logger.debug("Added model object '" + modelName + "' of type [" + modelValue.getClass().getName() + "] to request in view with name '" + this.getBeanName() + "'");
}
} else {
request.removeAttribute(modelName);
if (this.logger.isDebugEnabled()) {
this.logger.debug("Removed model object '" + modelName + "' from request in view with name '" + this.getBeanName() + "'");
}
}
}

}