spring mvc
- web.xml 配置说明
1 |
|
2 | <web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee" |
3 | xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" |
4 | xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd" |
5 | version="4.0"> |
6 | |
7 | <!--1. listener 监听器--> |
8 | <!--spring 配置文件的加载位置--> |
9 | <!--最常用的上下文载入器是一个Servlet 监听器,其名称为ContextLoaderListener--> |
10 | <listener> |
11 | <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> |
12 | </listener> |
13 | <context-param> |
14 | <param-name>contextConfigLocation</param-name> |
15 | <param-value>classpath*:/applicationContext.xml</param-value> |
16 | </context-param> |
17 | |
18 | <!--自定义监听器监听多环境配置中激活的prifile--> |
19 | <listener> |
20 | <listener-class>com.zbcn.web.pub.listener.ProfileContextListener</listener-class> |
21 | </listener> |
22 | <!-- 多环境配置 在上下文context-param中设置profile.active的默认值 --> |
23 | <!-- 设置active后default失效,web启动时会加载对应的环境信息 --> |
24 | <context-param> |
25 | <param-name>spring.profiles.active</param-name> |
26 | <param-value>test</param-value> |
27 | </context-param> |
28 | |
29 | <!-- logback的监听器--> |
30 | <listener> |
31 | <listener-class>ch.qos.logback.ext.spring.web.LogbackConfigListener</listener-class> |
32 | </listener> |
33 | <context-param> |
34 | <param-name>logbackConfigLocation</param-name> |
35 | <param-value> classpath:/logback.xml</param-value> |
36 | </context-param> |
37 | |
38 | <!--2. servlet 容器--> |
39 | <servlet> |
40 | <servlet-name>dispatcher</servlet-name> |
41 | <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> |
42 | <init-param> |
43 | <!--配置dispatcher_servlet.xml作为mvc的配置文件--> |
44 | <!--如果不配置该属性,则默认取 servlet-name 对应的值 如:dispatcher.xml--> |
45 | <param-name>contextConfigLocation</param-name> |
46 | <param-value>classpath:/dispatcher-servlet.xml</param-value> |
47 | </init-param> |
48 | <!--多环境支持 可选--> |
49 | <init-param> |
50 | <param-name>spring.profiles.default</param-name> |
51 | <param-value>dev</param-value> |
52 | </init-param> |
53 | <load-on-startup>1</load-on-startup> |
54 | <!--添加异步支持:可选--> |
55 | <async-supported>true</async-supported> |
56 | </servlet> |
57 | <!--映射路径--> |
58 | <servlet-mapping> |
59 | <servlet-name>dispatcher</servlet-name> |
60 | <url-pattern>/</url-pattern> |
61 | </servlet-mapping> |
62 | |
63 | |
64 | <!--拦截器:字符集控制--> |
65 | <filter> |
66 | <filter-name>encodingFilter</filter-name> |
67 | <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class> |
68 | <init-param> |
69 | <param-name>encoding</param-name> |
70 | <param-value>UTF-8</param-value> |
71 | </init-param> |
72 | <init-param> |
73 | <param-name>forceEncoding</param-name> |
74 | <param-value>true</param-value> |
75 | </init-param> |
76 | </filter> |
77 | <filter-mapping> |
78 | <filter-name>encodingFilter</filter-name> |
79 | <url-pattern>/*</url-pattern> |
80 | </filter-mapping> |
81 | </web-app> |
映射路径 /* 和/ 的区别:
- /* :覆盖所有其它的servlet,不管你发出了什么样的请求,最终都会在这个servlet中结束。会匹配所有的url:路径型的和后缀型的url(包括/springmvc,.jsp,.js和*.html等)
- /: 覆盖任何其它的servlet。它仅仅替换了servlet容器中内建的默认servlet.种形式通常只用来请求静态资源(CSS/JS/image等)和展示目录的列表.会匹配到/springmvc这样的路径型url,不会匹配到模式为*.jsp
contextConfigLocation:Spring 的核心配置文件,
DispatcherServlet:包含了SpringMVC 的请求逻辑, Spring 使用此类拦截Web 请求并进行相应的逻辑处理。
ContextloaderListener
- 当使用编程方式时,我们使用
ApplicationContext ac = new ClassPathXmlApplicationContext(" applicationContext.xml");
来加载spring 的容器,但是在springMvc 的web 项目下,通常是使用 context-paramd的方式注册,使用ContextloaderListener的方式监听。1
<listener>
2
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
3
</listener>
4
<context-param>
5
<param-name>contextConfigLocation</param-name>
6
<param-value>classpath*:/applicationContext.xml</param-value>
7
</context-param>
- ContextloaderListener 的作用是在启动web 容器时,自动装配 ApplicationContext的信息。因为他实现了 ServletContextListener 接口,在web.xml 配置这个监昕器,启动容器时,就会默认执行它实现的方法,使用ServletContextListener 接口,开发者能够在为客户端请求提供服务之前向ServletContext 中添加任意的对象。这个对象在ServletContext 启动的时候被初始化, 然后在ServletContext 整个运行期间都是可见的。
- 每一个Web 应用都有一个ServletContext 与之相关联。ServletContext 对象在应用启动时被创建,在应用关闭的时候被销毁。ServletContext 在全局范围内有效,类似于应用中的一个全局变量。
- 在ServletContextListener 中的核心逻辑便是初始化WebApplicationContext 实例并存放至ServletContext 中。
ServletContextlistener 的使用
创建自定义的 ServletContextlistener
1
public class MyDataContextListener implements ServletContextListener {
2
3
private ServletContext context = null;
4
5
//该方法在ServletContext 启动之后被调用,并准备好处理客户端请求
6
7
public void contextInitialized(ServletContextEvent sce) {
8
this.context = sce.getServletContext();
9
//可以实现自己的逻辑并将结果记录在属性中
10
context.setAttribute("myData", "test ServletContextListener");
11
}
12
13
//在servlet关闭的时候调用
14
15
public void contextDestroyed(ServletContextEvent sce) {
16
this.context = null;
17
}
18
}
web.xml 中配置监听
1
<!--自定义contextListener-->
2
<listener>
3
<listener-class>com.zbcn.web.listener.MyDataContextListener</listener-class>
4
</listener>
一旦Web 应用启动的时候,我们就能在任意的S巳rvlet 或者JSP 中通过下面的方式获取我们初始化的参数,
1
```
2
3
### Spring 中的ContextloaderListener
4
- ServletContext 启动之后会调用ServletContextListener 的contextlnitialized 方法,那么,我们就从这个函数开始进行分析。
5
```java
6
//ContextLoaderListener extends ContextLoader implements ServletContextListener
7
8
public void contextInitialized(ServletContextEvent event) {
9
initWebApplicationContext(event.getServletContext());
10
}
这里涉及了一个常用类WebApplicationContext :在Web 应用中,我们会用到WebApplicationContext,继承自AplicationContext,在ApplicationContext 的基础上又追加了一些特定于Web 的操作及属性,非常类似于我们通过编程方式使用Spring 时使用的
ClassPathXmlApplicationCont巳xt 类提供的功能.初始化 WebApplicationContext
1
//ContextLoader
2
public WebApplicationContext initWebApplicationContext(ServletContext servletContext) {
3
if (servletContext.getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE) != null) {
4
////web.xml 中存在多次ContextLoader 定义
5
throw new IllegalStateException(
6
"Cannot initialize context because there is already a root application context present - " +
7
"check whether you have multiple ContextLoader* definitions in your web.xml!");
8
}
9
10
Log logger = LogFactory.getLog(ContextLoader.class);
11
servletContext.log("Initializing Spring root WebApplicationContext");
12
if (logger.isInfoEnabled()) {
13
logger.info("Root WebApplicationContext: initialization started");
14
}
15
long startTime = System.currentTimeMillis();
16
17
try {
18
// Store context in local instance variable, to guarantee that
19
// it is available on ServletContext shutdown.
20
if (this.context == null) {
21
//初始化 webApplicationContext
22
this.context = createWebApplicationContext(servletContext);
23
}
24
if (this.context instanceof ConfigurableWebApplicationContext) {
25
ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) this.context;
26
if (!cwac.isActive()) {
27
// The context has not yet been refreshed -> provide services such as
28
// setting the parent context, setting the application context id, etc
29
if (cwac.getParent() == null) {
30
// The context instance was injected without an explicit parent ->
31
// determine parent for root web application context, if any.
32
ApplicationContext parent = loadParentContext(servletContext);
33
cwac.setParent(parent);
34
}
35
configureAndRefreshWebApplicationContext(cwac, servletContext);
36
}
37
}
38
//将WebApplicationContext记录在 servletContext中
39
servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context);
40
41
ClassLoader ccl = Thread.currentThread().getContextClassLoader();
42
if (ccl == ContextLoader.class.getClassLoader()) {
43
currentContext = this.context;
44
}
45
else if (ccl != null) {
46
currentContextPerThread.put(ccl, this.context);
47
}
48
49
if (logger.isDebugEnabled()) {
50
logger.debug("Published root WebApplicationContext as ServletContext attribute with name [" +
51
WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE + "]");
52
}
53
if (logger.isInfoEnabled()) {
54
long elapsedTime = System.currentTimeMillis() - startTime;
55
logger.info("Root WebApplicationContext: initialization completed in " + elapsedTime + " ms");
56
}
57
58
return this.context;
59
}
60
catch (RuntimeException ex) {
61
logger.error("Context initialization failed", ex);
62
servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, ex);
63
throw ex;
64
}
65
catch (Error err) {
66
logger.error("Context initialization failed", err);
67
servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, err);
68
throw err;
69
}
70
}
大体步骤:
WebApplicationContext 存在性的验证:在配置中只允许声明一次ServletContextListener , 多次声明会扰乱Spring 的执行逻辑,所以这里首先做的就是对此验证,在Spring 中如果创建WebApplicationContext 实例会记录在ServletContext 中以方便全局调用,而使用的key 就是
WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRBUTE
创建WebApplicationContext 实例。
1
//ContextLoader
2
protected WebApplicationContext createWebApplicationContext(ServletContext sc) {
3
//获取applicationContext 容器创建所需要的class
4
Class<?> contextClass = determineContextClass(sc);
5
if (!ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass)) {
6
throw new ApplicationContextException("Custom context class [" + contextClass.getName() +
7
"] is not of type [" + ConfigurableWebApplicationContext.class.getName() + "]");
8
}
9
return (ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);
10
}
11
```
12
在ContextLoader 类中有这样的静态代码块
13
```java
14
//ContextLoader
15
private static final Properties defaultStrategies;
16
static {
17
// Load default strategy implementations from properties file.
18
// This is currently strictly internal and not meant to be customized
19
// by application developers.
20
try {
21
ClassPathResource resource = new ClassPathResource(DEFAULT_STRATEGIES_PATH, ContextLoader.class);
22
defaultStrategies = PropertiesLoaderUtils.loadProperties(resource);
23
}
24
catch (IOException ex) {
25
throw new IllegalStateException("Could not load 'ContextLoader.properties': " + ex.getMessage());
26
}
27
}
根据以上静态代码块的内容,我们推断在当前类ContextLoader 同样目录下必定会存在属性文件ContextLoader.properties ,在初始化的过程中,程序首先会读取ContextLoader 类的同目录下的属性文件ContextLoader.properties ,并根据其中的配置提取将要实现WebApplicationContext 接口的实现类,并根据这个实现类通过反射的方式进行实例的创建。
将实例记录在servletContext 中。
映射当前的类加载器与创建的实例到全局变量currentContextPerThread 中
DispatcherServlet
spring MVC 真正的实现逻辑是 dispatcherServlet. DispactherServlet 是 实现 Servlet 接口的实现类.
servlet 是一个java 编写的程序,此程序是基于http协议的,在服务器端运行的(如:tomcat). 是按照servlet 规范编写的一个java 类,主要是处理客户端的请求并将其结果发送到客户端. servlet 的生命周期是按照servlet 容器来控制的,分为三个阶段:初始化,运行,销毁
初始化阶段
- servlet 容器加载servlet 类,把servlet 类的.class 文件中的数据读到内存中。
- servlet 容器创建一个ServletConfig 对象。ServletConfig 对象包含了servlet 的初始化配置信息。
- servlet 容器创建一个servlet 对象。
- servlet 容器调用servlet 对象的init 方法进行初始化。
运行阶段
当servlet 容器接收到一个请求时, servlet 容器会针对这个请求创建servletRequest 和servletResponse 对象,然后调用servic巳方法.并把这两个参数传递给service 方法。service 方法通过servletRequest 对象获得请求的信息。并处理该请求。再通过servletResponse 对象生成这个请求的响应结果。然后销毁servletRequest 和servletResponse 对象。不管这个请求是post 提交的还是get 提交的,最终这个请求都会由service 方法来处理。
销毁阶段
当web 容器被终止时,selvet 容器首先会调用 sevlet 的destory 方法,然后再销毁 sevlet 容器,同时销毁sevletConfig对象。可以在 sevlet 的destory 方法中,释放sevlet容器占用的资源。 如关闭数据库链接,关闭文件输入输出流等。
sevlet 的框架是由两个java 组成:javax.sevlet 和java.sevlet.http.
javax.sevlet 包中定义了所有的sevlet 都必须实现或者扩展的通用接口和类。
javax.servlet.http 包中定义了采用http通信协议的HttpServlet类。servlet 被设计成请求驱动, servlet 的请求可能包含多个数据项,当Web 容器接收到某个servlet 请求时, servlet 把请求封装成一个HttServletRequest 对象.然后把对象传给servlet 的对应的服务方法。
HTTP 的请求方式包括delete 、get 、options 、post 、put 和trace ,在HttpServlet 类中分别提
供了相应的服务方法,它们是doDelete()、doGet() 、doOptions() 、doPost() 、doPut()和 doTrace() 。
servlet 的使用
建立servlet
1
public MyServlet extends HttpServlet{
2
//初始化方法
3
public void init();
4
public void doGet(HttpServletRequest request, HttpServletResponse response) {
5
handleLogic ( request , response);
6
}
7
public void doPost(HttpServletRequest request, HttpServletResponse response) {
8
handleLogic(request , response );
9
}
10
11
//处理逻辑的核心方法
12
public void handleLogic();
13
}
- init 方法保证在Servlet加载的时候能做一些逻辑操作
- 而HttpServlet 类则会帮助我们根据方法类型的不同而将逻辑引人不同的函数
- 子类中我们只需要重写对应的函数逻辑便可: 如handleLogic方法。
添加配置
1
<servlet>
2
<servlet-name>myservlet</servlet-name>
3
<servlet-class>test.servlet.MyServlet</servlet- class>
4
<load-on-startup>l</load-on-startup>
5
</servlet>
6
<servlet- mapping>
7
<servlet-name>myservlet</servlet-name>
8
<url-pattern >*.htm</url-pattern>
9
</servlet-mapping>
DispatcherServlet 的初始化
对于 DispatcherServlet, 在其父类 HttpServletBean 中重写了 Servlet 的init 方法。
1 | //HttpServletBean extends HttpServlet implements EnvironmentCapable, EnvironmentAware |
2 |
|
3 | public final void init() throws ServletException { |
4 | //解析init-param 并且填充到pvs中 |
5 | // Set bean properties from init parameters. |
6 | PropertyValues pvs = new ServletConfigPropertyValues(getServletConfig(), this.requiredProperties); |
7 | if (!pvs.isEmpty()) { |
8 | try { |
9 | //将当前的servlet 转换为一个beanWrapper,从而能够已spring的方式来对init-param 的值进行处理。 |
10 | BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this); |
11 | ResourceLoader resourceLoader = new ServletContextResourceLoader(getServletContext()); |
12 | //注册自定义属性编辑器,一旦遇到Resource 类型的属性将会使用ResourceEditor进行解析 |
13 | bw.registerCustomEditor(Resource.class, new ResourceEditor(resourceLoader, getEnvironment())); |
14 | //空实现, 留给子类覆盖 |
15 | initBeanWrapper(bw); |
16 | bw.setPropertyValues(pvs, true); |
17 | } |
18 | catch (BeansException ex) { |
19 | if (logger.isErrorEnabled()) { |
20 | logger.error("Failed to set bean properties on servlet '" + getServletName() + "'", ex); |
21 | } |
22 | throw ex; |
23 | } |
24 | } |
25 | // Let subclasses do whatever initialization they like. |
26 | initServletBean(); |
27 | |
28 | if (logger.isDebugEnabled()) { |
29 | logger.debug("Servlet '" + getServletName() + "' configured successfully"); |
30 | } |
31 | } |
- spring的 DispartcherServlet的初始化主要是将servlet 类型的实例转换为BeanWarpper 类型,以便于spring 提供的注入功能对属性值进行注入,如:contextAttribute 、contextClass 、nameSpace 、contextConfigLocation 等,都可以在web.xml 文件中以初始化参数的方
式配置在servlet 的声明中。 - DisPatcherServlet 继承自FrameWorkServlet.Frameworkservlet 类上包含对应的同名属性, Spring 会保证这些参数被注入到对应的值中。
属性注入主要包含以下几个步骤。
封装及验证初始化参数
ServletConfigPropertyValues 除了封装属性外还有对属性验证的功能。1
//ServletConfigPropertyValues
2
public ServletConfigPropertyValues(ServletConfig config, Set<String> requiredProperties)
3
throws ServletException {
4
5
Set<String> missingProps = (!CollectionUtils.isEmpty(requiredProperties) ?
6
new HashSet<String>(requiredProperties) : null);
7
Enumeration<String> paramNames = config.getInitParameterNames();
8
while (paramNames.hasMoreElements()) {
9
String property = paramNames.nextElement();
10
Object value = config.getInitParameter(property);
11
addPropertyValue(new PropertyValue(property, value));
12
if (missingProps != null) {
13
missingProps.remove(property);
14
}
15
}
16
// Fail if we are still missing properties.
17
if (!CollectionUtils.isEmpty(missingProps)) {
18
throw new ServletException(
19
"Initialization from ServletConfig for servlet '" + config.getServletName() +
20
"' failed; the following required properties were missing: " +
21
StringUtils.collectionToDelimitedString(missingProps, ", "));
22
}
23
}
24
}
从代码中得知,封装属性主要是对初始化的参数进行封装,也就是servlet 中配置的
中配置的封装.当然,用户可以通过对requiredProperties 参数的初始化来强制验证某些属性的必要性.这样,在属性封装的过程中,一旦检测到requiredProperties 中的属性没有指定初始值,就会抛出异常。 将当前servlet 实例转化成BeanWrapper 实例
PropertyAccessorFactory.forBeanPropertyAccess 是spring中提供的一个工具方法,用于将指定实例转换为spring可以处理的beanWrapper 实例。注册相对于Resource 的属性编辑器
属性编辑器,我们在上文中已经介绍并且分析过其原理, 这里使用属性编辑器的目的是在对当前实例( DispatcherServlet )属性注入过程中一旦遇到Resource 类型的属性就会使用ResourceEditor 去解析。属性注入
BeanWrapper 为Spring 中的方法, 支持Spring 的自动注入。其实我们最常用的属性注入无非是contextAttribute、contextClass 、nameSpace 、contextConfigLocation 等。servletBean 的初始化
在ContextLoaderListener 加载的时候已经创建了WebApplicationContext 实例,而在这个函数中最重要的就是对这个实例进行进一步的补充初始化。继续查看initServletBean()方法 。父类FrameworkServlet 覆盖了HttpServletBean 中的initServletBean函数:1
//FrameworkServlet
2
protected final void initServletBean() throws ServletException {
3
getServletContext().log("Initializing Spring FrameworkServlet '" + getServletName() + "'");
4
if (this.logger.isInfoEnabled()) {
5
this.logger.info("FrameworkServlet '" + getServletName() + "': initialization started");
6
}
7
long startTime = System.currentTimeMillis();
8
9
try {
10
this.webApplicationContext = initWebApplicationContext();
11
//设计为子类覆盖
12
initFrameworkServlet();
13
}
14
catch (ServletException ex) {
15
this.logger.error("Context initialization failed", ex);
16
throw ex;
17
}
18
catch (RuntimeException ex) {
19
this.logger.error("Context initialization failed", ex);
20
throw ex;
21
}
22
if (this.logger.isInfoEnabled()) {
23
long elapsedTime = System.currentTimeMillis() - startTime;
24
this.logger.info("FrameworkServlet '" + getServletName() + "': initialization completed in " +
25
elapsedTime + " ms");
26
}
27
}
关键逻辑是WebApplicationContext 的初始化,initWebApplicationContext()
WebApplicationContext 的初始化
initWebApplicationContext() 的主要逻辑就是 初始化WebApplicationContext,核心工作是创建或者刷新 WebApplicationContext实例,并对servlet功能所使用的变量进行进初始化。
1 | //FrameworkServlet |
2 | protected WebApplicationContext initWebApplicationContext() { |
3 | WebApplicationContext rootContext = |
4 | WebApplicationContextUtils.getWebApplicationContext(getServletContext()); |
5 | WebApplicationContext wac = null; |
6 | |
7 | if (this.webApplicationContext != null) { |
8 | // A context instance was injected at construction time -> use it |
9 | wac = this.webApplicationContext; |
10 | if (wac instanceof ConfigurableWebApplicationContext) { |
11 | ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) wac; |
12 | if (!cwac.isActive()) { |
13 | // The context has not yet been refreshed -> provide services such as |
14 | // setting the parent context, setting the application context id, etc |
15 | if (cwac.getParent() == null) { |
16 | // The context instance was injected without an explicit parent -> set |
17 | // the root application context (if any; may be null) as the parent |
18 | cwac.setParent(rootContext); |
19 | } |
20 | //刷新上下文环境 |
21 | configureAndRefreshWebApplicationContext(cwac); |
22 | } |
23 | } |
24 | } |
25 | if (wac == null) { |
26 | // No context instance was injected at construction time -> see if one |
27 | // has been registered in the servlet context. If one exists, it is assumed |
28 | // that the parent context (if any) has already been set and that the |
29 | // user has performed any initialization such as setting the context id |
30 | //根据contextAttribute 属性加载WebApplicationContext |
31 | wac = findWebApplicationContext(); |
32 | } |
33 | if (wac == null) { |
34 | // No context instance is defined for this servlet -> create a local one |
35 | wac = createWebApplicationContext(rootContext); |
36 | } |
37 | if (!this.refreshEventReceived) { |
38 | // Either the context is not a ConfigurableApplicationContext with refresh |
39 | // support or the context injected at construction time had already been |
40 | // refreshed -> trigger initial onRefresh manually here. |
41 | onRefresh(wac); |
42 | } |
43 | if (this.publishContext) { |
44 | // Publish the context as a servlet context attribute. |
45 | String attrName = getServletContextAttributeName(); |
46 | getServletContext().setAttribute(attrName, wac); |
47 | if (this.logger.isDebugEnabled()) { |
48 | this.logger.debug("Published WebApplicationContext of servlet '" + getServletName() + |
49 | "' as ServletContext attribute with name [" + attrName + "]"); |
50 | } |
51 | } |
52 | |
53 | return wac; |
54 | } |
寻找或创建对应的WebApplicationContext 实例
通过构造函数的注入进行初始化
在Web 中包含SpringWeb 的核心逻辑的 DispatcherServlet 只可以被声明为一次,在Spring 中已经存在验证,所以这就确保了如果this.webApplicationContext !=null ,则可以直接判定this.webApplicationContext 已经通过构造函数初始化。
通过contextAttribute 进行初始化。
通过在web.xml 文件中配置的servlet 参数contextAttribute 来查找ServletContext 中对应的属性,默认为
WebApplicationContext.class.getName()+".ROOT"
,也就是在ContextLoaderListener加载时会创建WebApplicationContext 实例.并将实例以WebApplicationContext.class.getName()+". ROOT"
为key 放入ServletContext 中.当然我们可以重写初始化逻辑使用自己创建的WebApplicationContext , 并在servlet 的配置中通过初始化参数contextAttribute 指定key 。1
//FrameworkServlet
2
protected WebApplicationContext findWebApplicationContext() {
3
String attrName = getContextAttribute();
4
if (attrName == null) {
5
return null;
6
}
7
WebApplicationContext wac =
8
WebApplicationContextUtils.getWebApplicationContext(getServletContext(), attrName);
9
if (wac == null) {
10
throw new IllegalStateException("No WebApplicationContext found: initializer not registered?");
11
}
12
return wac;
13
}
重新创建WebApplicationContext 实例。
1
//FrameworkServlet
2
protected WebApplicationContext createWebApplicationContext(ApplicationContext parent) {
3
////获取servlet 的初始化参数contextClass , 如果没有配置默认为XmlWebApplicationContext.class
4
Class<?> contextClass = getContextClass();
5
if (this.logger.isDebugEnabled()) {
6
this.logger.debug("Servlet with name '" + getServletName() +
7
"' will try to create custom WebApplicationContext context of class '" +
8
contextClass.getName() + "'" + ", using parent context [" + parent + "]");
9
}
10
if (!ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass)) {
11
throw new ApplicationContextException(
12
"Fatal initialization error in servlet with name '" + getServletName() +
13
"': custom WebApplicationContext class [" + contextClass.getName() +
14
"] is not of type ConfigurableWebApplicationContext");
15
}
16
//通过反射实例化context
17
ConfigurableWebApplicationContext wac =
18
(ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);
19
//设置环境变量
20
wac.setEnvironment(getEnvironment());
21
//parent 为在ContextLoaderListener 中创建的实例
22
//在ContextLoaderListener 加载的时候初始化的WebApplicationContext 类型实例
23
wac.setParent(parent);
24
////获取contextConfigLocation 属性,配置在servlet 初始化参数中
25
wac.setConfigLocation(getContextConfigLocation());
26
//初始化Spring 环境包括加载配置文件等
27
configureAndRefreshWebApplicationContext(wac);
28
29
return wac;
30
}
- configureAndRefreshWebApplicationContext
无论是通过构造函数注入还是单独创建, 都会调用configureAndRefreshWebApplicationContext方法来对已经创建的WebApplicationContext 实例进行配置及刷新,那么这个步骤又做了哪些工
作呢?1
protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac) {
2
if (ObjectUtils.identityToString(wac).equals(wac.getId())) {
3
// The application context id is still set to its original default value
4
// -> assign a more useful id based on available information
5
if (this.contextId != null) {
6
wac.setId(this.contextId);
7
}
8
else {
9
// Generate default id...
10
wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX +
11
ObjectUtils.getDisplayString(getServletContext().getContextPath()) + '/' + getServletName());
12
}
13
}
14
15
wac.setServletContext(getServletContext());
16
wac.setServletConfig(getServletConfig());
17
wac.setNamespace(getNamespace());
18
wac.addApplicationListener(new SourceFilteringListener(wac, new ContextRefreshListener()));
19
20
// The wac environment's #initPropertySources will be called in any case when the context
21
// is refreshed; do it eagerly here to ensure servlet property sources are in place for
22
// use in any post-processing or initialization that occurs below prior to #refresh
23
ConfigurableEnvironment env = wac.getEnvironment();
24
if (env instanceof ConfigurableWebEnvironment) {
25
((ConfigurableWebEnvironment) env).initPropertySources(getServletContext(), getServletConfig());
26
}
27
28
postProcessWebApplicationContext(wac);
29
applyInitializers(wac);
30
//加载配置文件及整合parent 到wac
31
wac.refresh();
32
}
- 刷新 onRefresh
onRefresh 是FrameworkServlet 类中提供的模板方法,在其子类DispatcherServlet 中进行了重写,主要用于刷新Spring 在Web 功能实现中所必须使用的全局变量.
1 | //DispatcherServlet |
2 |
|
3 | protected void onRefresh(ApplicationContext context) { |
4 | initStrategies(context); |
5 | } |
6 | |
7 | /** |
8 | * Initialize the strategy objects that this servlet uses. |
9 | * <p>May be overridden in subclasses in order to initialize further strategy objects. |
10 | */ |
11 | protected void initStrategies(ApplicationContext context) { |
12 | //初始化MultipartResolver |
13 | initMultipartResolver(context); |
14 | //初始化LocaleResolver |
15 | initLocaleResolver(context); |
16 | //初始化ThemeResolver |
17 | initThemeResolver(context); |
18 | //初始化HandlerMappings |
19 | initHandlerMappings(context); |
20 | //初始化HandlerAdapters |
21 | initHandlerAdapters(context); |
22 | //初始化HandlerExceptionResolvers |
23 | initHandlerExceptionResolvers(context); |
24 | //初始化RequestToViewNameTranslator |
25 | initRequestToViewNameTranslator(context); |
26 | //初始化ViewResolvers |
27 | initViewResolvers(context); |
28 | //初始化FlashMapManager |
29 | initFlashMapManager(context); |
30 | } |
初始化MultipartResolver
在spring中multipartiResolver 主要使用来处理上传文件的。默认情况下, Spring 是没有multipart处理的,因为一些开发者想要自己处理它们。如果想使用Spring 的multipart, 则需要在Web应用的上下文中添加multipart 解析器。这样,每个请求就会被检查是否包含multipart,然而,如果请求中包含multipart ,那么上下文中定义的MultipartResolver 就会解析它。这样请求中的multipart 属性就会像其他属性一样被处理。
常用配置如下:
1 | <!--配置文件上传相关--> |
2 | <bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver"> |
3 | <!-- 设定文件上传的最大值--> |
4 | <property name="maxUploadSize" value="10485760"></property> |
5 | <!-- 设定文件上传时写入内存的最大值,如果小于这个参数不会生成临时文件,默认为10240 --> |
6 | <property name="maxInMemorySize" value="4096"></property> |
7 | <!-- 设定默认编码 --> |
8 | <property name="defaultEncoding" value="UTF-8"></property> |
9 | </bean> |
当然,CommonsMultipartResolver 还提供了其他功能用于帮助用户完成上传功能,可以通过查看 CommonsMultipartResolver的属性获得。
MultipartResolver 就是在initMultipartResolv巳r 中被加入到DispatcherServlet 中的
1 | ////DispatcherServlet |
2 | private void initMultipartResolver(ApplicationContext context) { |
3 | try { |
4 | this.multipartResolver = context.getBean(MULTIPART_RESOLVER_BEAN_NAME, MultipartResolver.class); |
5 | if (logger.isDebugEnabled()) { |
6 | logger.debug("Using MultipartResolver [" + this.multipartResolver + "]"); |
7 | } |
8 | } |
9 | catch (NoSuchBeanDefinitionException ex) { |
10 | // Default is no multipart resolver. |
11 | this.multipartResolver = null; |
12 | if (logger.isDebugEnabled()) { |
13 | logger.debug("Unable to locate MultipartResolver with name '" + MULTIPART_RESOLVER_BEAN_NAME + |
14 | "': no multipart request handling provided"); |
15 | } |
16 | } |
17 | } |
因为之前的步骤已经完成了Spring 中配置文件的解析,所以在这里只要在配置文件注册过都可以通过ApplicationContext 提供的getBean 方法来直接获取对应bean ,进而初始化MultipartResolver 中的multipartResoIver 变量.
初始化LocaleResolve
在Spring 的国际化配置中一共有3 种使用方式。
基于URL 参数的配置
通过URL 参数来控制国际化, 比如你在页面上加一句
<a href="?locale=zh_CN”>
,简体中文来控制项目中使用的国际化参数,而提供这个功能的就是AcceptHeaderLocaleResolver , 默认的参数名为locale,注意大小写。里面放的就是你的提交参数,比如en_US, zh_CN 之类的,具体配置如下:<bean id="localeResolver" class="org.Springframework.web.servlet .il8n.AcceptHeaderLocaleResolver" />
基于session 的配置。
它通过检验用户会话中预置的属性来解析区域.最常用的是根据用户本次会话过程中的语言设定决定语言种类(例如,用户登录时选择语言种类,则此次登录周期内统一使用此语言设
定),如果该会话属性不存在,它会根据accept-language HTTP
头部确定默认区域。<bean id="localeResolver" class="org.Springframework.web.servlet .il8n.SessionLocaleResolver"/>
- 基于cookie 的国际化配置
CookieLocaleResolver 用于通过浏览器的cookie 设置取得Locale 对象。这种策略在应用程序不支持会话或者状态必须保存在客户端时有用,配置如下:<bean id=”localeResolver" class="org.Springframework.web.servlet .il8n.CookieLocaleResol ver"/>
这3 种方式都可以解决国际化的问题,但是, 对于LocalResolver 的使用基础是在DispatcherServlet 中的初始化。
1 | private void initLocaleResolver(ApplicationContext context) { |
2 | try { |
3 | this.localeResolver = context.getBean(LOCALE_RESOLVER_BEAN_NAME, LocaleResolver.class); |
4 | if (logger.isDebugEnabled()) { |
5 | logger.debug("Using LocaleResolver [" + this.localeResolver + "]"); |
6 | } |
7 | } |
8 | catch (NoSuchBeanDefinitionException ex) { |
9 | // We need to use the default. |
10 | this.localeResolver = getDefaultStrategy(context, LocaleResolver.class); |
11 | if (logger.isDebugEnabled()) { |
12 | logger.debug("Unable to locate LocaleResolver with name '" + LOCALE_RESOLVER_BEAN_NAME + |
13 | "': using default [" + this.localeResolver + "]"); |
14 | } |
15 | } |
16 | } |
初始化ThemeResolver
在Web 开发中经常会遇到通过主题Theme 来控制网页风格,这将进一步改善用户体验。简单地说, 一个主题就是一组静态资源( 比如样式表和图片).它们可以影响应用程序的视觉效果。Spring 中的主题功能和国际化功能非常类似。Spring 主题功能的构成主要包括如下内容。
- 主题资源
org.springframework.ui.context.ThemeSource
是Spring 中主题资源的接口, Spring 的主题需要通过 ThemeSource 接口来实现存放主题信息的资源.
org.springframework.ui.context.support.ResourceBundleThemeSource
是ThemeSource 接口默认实现类(也就是通过 ResourceBundle 资源的方式定义主题),在Spring 中的配置如下:
1 | <bean id="themeSource" class="org.springframework.ui.context.support.ResourceBundleThemeSource" > |
2 | <property name= "basenamePrefix" value= "com.test.”>< lproperty> |
3 | </bean> |
默认状态下是在类路径根目录下查找相应的资源文件, 也可以通过basenamePrefix 来制定。这样, DispatcherServlet 就会在com.test 包下查找资源文件。
主题解析器
ThemeSource 定义了一些主题资源,那么不同的用户使用什么主题资源由谁定义呢?org.springframework.web.servlet.ThemeResolver
是主题解析器的接口, 主题解析的工作便由它的子类来完成。
对于主题解析器的子类主要有3 个比较常用的实现。以主题文件summer.properties 为例。① FixedThemeResolver 用于选择一个固定的主题。
1
<bean id="themeResolver" class="org Springframework.web. servlet.theme.FixedThemeResolver” >
2
<property name="defaultThemeName" value= "summer"/>
3
</bean>
以上配置的作用是设置主题文件为summer.properties ,在整个项目内固定不变。
② CookieThemeResolver 用于实现用户所选的主题, 以cookie 的形式存放在客户端的机器上,配置如下:
1
<bean id= "themeResolver" class ="org.springframework.web.servlet.theme.CookieThemeResolver" >
2
<property name="defaultThemeName" value="summer"/>
3
</bean>
③ SessionThemeResolver 用于主题保存在用户的HTTP Session 中。
1
<bean id="sessionResolver" class="org.springframework.web.servlet.theme.SessionThemeResolver">
2
<property name="defaultThemeName" value="summer"/>
3
</bean>
以上配置用于设置主题名称,并且将该名称保存在用户的HttpSession 中。
④ AbstractThemeResolver 是一个抽象类被SessionThemeResolver 和FixedThemeResolver继承,用户也可以继承它来自定义主题解析器。拦截器
如果需要根据用户请求来改变主题, 那么Spring 提供了一个已经实现的拦截器—ThemeChangeInterceptor 拦截器了,配置如下:
1
<bean id=”themeChangeInterceptor” class="org.springframework.web.servlet.theme.ThemeChangeInterceptor">
2
<property name ="paramName" value="themeName"></property>
3
</bean>
其中设置用户请求参数名为themeName ,即URL 为?themeName=具体的主题名称。此外,还需要在handlerMapping 中配置拦截器。当然需要在HandleMapping 中添加拦截器。
1
<property name="interceptors">
2
<list>
3
<ref local="themeChangeinterceptor" />
4
</list>
5
</property>
再来查看解析器的初始化工作,与其他变量的初始化工作相同,主题文件解析器的初始化工作并没有任何需要特别说明的地方.
1
private void initThemeResolver(ApplicationContext context) {
2
try {
3
this.themeResolver = context.getBean(THEME_RESOLVER_BEAN_NAME, ThemeResolver.class);
4
if (logger.isDebugEnabled()) {
5
logger.debug("Using ThemeResolver [" + this.themeResolver + "]");
6
}
7
}
8
catch (NoSuchBeanDefinitionException ex) {
9
// We need to use the default.
10
this.themeResolver = getDefaultStrategy(context, ThemeResolver.class);
11
if (logger.isDebugEnabled()) {
12
logger.debug("Unable to locate ThemeResolver with name '" + THEME_RESOLVER_BEAN_NAME +
13
"': using default [" + this.themeResolver + "]");
14
}
15
}
16
}
初始化HandlerMappings
当客户端发出Request 时DispatcherServlet 会将Request 提交给HandlerMapping ,然后HanlerMapping 根据WebApplicationContext 的配置来回传给DispatcherServlet 相应的 Controller.
在基于SpringMVC 的Web 应用程序中,我们可以为DispatcherServlet 提供多个HandlerMapping 供其使用.
DispatcherServlet 在选用HandlerMapping 的过程中, 将根据我们所指定的一系列HandlerMapping 的优先级进行排序,然后优先使用优先级在前的HandlerMapping
如果当前的HandlerMapping 能够返回可用的Handler,DispatcherServlet 则使用当前返回的Handler进行Web 请求的处理,而不再继续询问其他的HandierMapping
否则, DispatcherServlet 将继续按照各个HandlerMapping 的优先级进行询问, 直到获取一个可用的Handler 为止。
初始化方法如下:
1 | private void initHandlerMappings(ApplicationContext context) { |
2 | this.handlerMappings = null; |
3 | |
4 | if (this.detectAllHandlerMappings) { |
5 | // Find all HandlerMappings in the ApplicationContext, including ancestor contexts. |
6 | Map<String, HandlerMapping> matchingBeans = |
7 | BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerMapping.class, true, false); |
8 | if (!matchingBeans.isEmpty()) { |
9 | this.handlerMappings = new ArrayList<HandlerMapping>(matchingBeans.values()); |
10 | // We keep HandlerMappings in sorted order. |
11 | AnnotationAwareOrderComparator.sort(this.handlerMappings); |
12 | } |
13 | } |
14 | else { |
15 | try { |
16 | HandlerMapping hm = context.getBean(HANDLER_MAPPING_BEAN_NAME, HandlerMapping.class); |
17 | this.handlerMappings = Collections.singletonList(hm); |
18 | } |
19 | catch (NoSuchBeanDefinitionException ex) { |
20 | // Ignore, we'll add a default HandlerMapping later. |
21 | } |
22 | } |
23 | |
24 | // Ensure we have at least one HandlerMapping, by registering |
25 | // a default HandlerMapping if no other mappings are found. |
26 | if (this.handlerMappings == null) { |
27 | this.handlerMappings = getDefaultStrategies(context, HandlerMapping.class); |
28 | if (logger.isDebugEnabled()) { |
29 | logger.debug("No HandlerMappings found in servlet '" + getServletName() + "': using default"); |
30 | } |
31 | } |
32 | } |
默认情况下, SpringMVC 将加载当前系统中所有实现了HandlerMapping 接口的bean,如果只期望SpringMVC 加载指定的handlermapping 时,可以修改web.xml 中的DispatcherServlet的初始参数,将detectAllHandlerMappings 的值设置为false :
1 | <init-pararn> |
2 | <pararn-narne>detectAllHandlerMappings</pararn-narne> |
3 | <pararn-value>false</pararn-value> |
4 | </init-pararn> |
此时, SpringMVC 将查找名为”handlerMapping”的bean ,并作为当前系统中唯一的handlermapping.如果没有定义handlerMapping 的话,则SpringMVC 将按照 org.Springframework.web.servlet.DispatcherServlet
所在目录下的DispatcherServlet.properties 中所定义的 org.Springframework.web.servlet.HandlerMapping
的内容来加载默认的handlerMapping.(用户没有自定义Strategies的情况下).
初始化HandlerAdapters
从名字也能联想到这是一个典型的适配器模式的使用,在计算机编程中,适配器模式将一个类的接口适配成用户所期待的。使用适配器,可以使接口不兼容而无法在一起工作的类协同工作,做法是将类自己的接口包裹在一个己存在的类中。那么在处理handler 时为什么会使用适配器模式呢?回答这个问题我们首先要分析它的初始化逻辑
1 | private void initHandlerAdapters(ApplicationContext context) { |
2 | this.handlerAdapters = null; |
3 | |
4 | if (this.detectAllHandlerAdapters) { |
5 | // Find all HandlerAdapters in the ApplicationContext, including ancestor contexts. |
6 | Map<String, HandlerAdapter> matchingBeans = |
7 | BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerAdapter.class, true, false); |
8 | if (!matchingBeans.isEmpty()) { |
9 | this.handlerAdapters = new ArrayList<HandlerAdapter>(matchingBeans.values()); |
10 | // We keep HandlerAdapters in sorted order. |
11 | AnnotationAwareOrderComparator.sort(this.handlerAdapters); |
12 | } |
13 | } |
14 | else { |
15 | try { |
16 | HandlerAdapter ha = context.getBean(HANDLER_ADAPTER_BEAN_NAME, HandlerAdapter.class); |
17 | this.handlerAdapters = Collections.singletonList(ha); |
18 | } |
19 | catch (NoSuchBeanDefinitionException ex) { |
20 | // Ignore, we'll add a default HandlerAdapter later. |
21 | } |
22 | } |
23 | |
24 | // Ensure we have at least some HandlerAdapters, by registering |
25 | // default HandlerAdapters if no other adapters are found. |
26 | if (this.handlerAdapters == null) { |
27 | this.handlerAdapters = getDefaultStrategies(context, HandlerAdapter.class); |
28 | if (logger.isDebugEnabled()) { |
29 | logger.debug("No HandlerAdapters found in servlet '" + getServletName() + "': using default"); |
30 | } |
31 | } |
32 | } |
同样在初始化的过程中涉及了一个变量detectAllHandlerAdapters , detectAllHandlerAdapters作用和detectAllHandlerMappings 类似,只不过作用对象为handlerAdapter。亦可通过如下配置来强制系统只加载beanname 为“handlerAdapter” handlerAdapter。
1 | <int-param> |
2 | <param-name>detectAllHandlerAdapters</param-name> |
3 | <param-value>false</param-value> |
4 | </init-param > |
如果无法找到对应的bean ,那么系统会尝试加载默认的适配器。
1 | protected <T> List<T> getDefaultStrategies(ApplicationContext context, Class<T> strategyInterface) { |
2 | String key = strategyInterface.getName(); |
3 | String value = defaultStrategies.getProperty(key); |
4 | if (value != null) { |
5 | String[] classNames = StringUtils.commaDelimitedListToStringArray(value); |
6 | List<T> strategies = new ArrayList<T>(classNames.length); |
7 | for (String className : classNames) { |
8 | try { |
9 | Class<?> clazz = ClassUtils.forName(className, DispatcherServlet.class.getClassLoader()); |
10 | Object strategy = createDefaultStrategy(context, clazz); |
11 | strategies.add((T) strategy); |
12 | } |
13 | catch (ClassNotFoundException ex) { |
14 | throw new BeanInitializationException( |
15 | "Could not find DispatcherServlet's default strategy class [" + className + |
16 | "] for interface [" + key + "]", ex); |
17 | } |
18 | catch (LinkageError err) { |
19 | throw new BeanInitializationException( |
20 | "Error loading DispatcherServlet's default strategy class [" + className + |
21 | "] for interface [" + key + "]: problem with class file or dependent class", err); |
22 | } |
23 | } |
24 | return strategies; |
25 | } |
26 | else { |
27 | return new LinkedList<T>(); |
28 | } |
29 | } |
在getDefaultStrategies 函数中, Spring 会尝试从defaultStrategies 中加载对应的HandlerAdapter的属性,那么defaultStrategies 是如何初始化的呢?
1 | private static final Properties defaultStrategies; |
2 | static { |
3 | // Load default strategy implementations from properties file. |
4 | // This is currently strictly internal and not meant to be customized |
5 | // by application developers. |
6 | try { |
7 | ClassPathResource resource = new ClassPathResource(DEFAULT_STRATEGIES_PATH, DispatcherServlet.class); |
8 | defaultStrategies = PropertiesLoaderUtils.loadProperties(resource); |
9 | } |
10 | catch (IOException ex) { |
11 | throw new IllegalStateException("Could not load '" + DEFAULT_STRATEGIES_PATH + "': " + ex.getMessage()); |
12 | } |
13 | } |
在系统加载的时候, defaultStrategies 根据当前路径DispatcherServlet.properties 来初始化本身,查看DispatcherServlet. properties 中对应于HandlerAdapter 的属性:
1 | org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter,\ = |
2 | org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter,\ |
3 | org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter |
由此得知,如果程序开发人员没有在配置文件中定义自己的适配器,那么Spring 会默认加载配置文件中的3 个适配器。
作为总控制器的派遣器servlet 通过处理器映射得到处理器后,会轮询处理器适配器模块,查找能够处理当前HTTP 请求的处理器适配器的实现,处理器适配器模块根据处理器映射返回的处理器类型,例如简单的控制器类型、注解控制器类型或者远程调用处理器类型,来选择某一个适当的处理器适配器的实现,从而适配当前的HTTP 请求。
HTTP 请求处理器适配器( HttpRequestHandlerAdapter )。
HTTP 请求处理器适配器仅仅支持对HTTP 请求处理器的适配。它简单地将HTTP 请求对象和响应对象传递给HTTP 请求处理器的实现,它并不需要返回值。它主要应用在基于HTTP的远程调用的实现上。
简单控制器处理器适配器( SimpleControllerHandlerAdapter )
这个实现类将HTTP 请求适配到一个控制器的实现进行处理,这里控制器的实现是一个简单的控制器接口的实现。简单控制器处理器适配器被设计成一个框架类的实现,不需要被改写,客户化的业务逻辑通常是在控制器接口的实现类中实现的。
注解方法处理器适配器(AnnotationMethodHandlerAdapter )。
这个类的实现是基于注解的实现,它需要结合注解方法映射和注解方法处理器协同工作。它通过解析声明在注解控制器的请求映射信息来解析相应的处理器方法来处理当前的HTTP 请求。在处理的过程中,它通过反射来发现探测处理器方法的参数,调用处理器方法,并且映射返回值到模型和控制器对象,最后返回模型和控制器对象给作为主控制器的派遣器Servlet。
所以我们现在基本上可以回答之前的问题了, Spring 中所使用的Handler 并没有任何特殊的联系,但是为了统一处理, Spring 提供了不同情况下的适配器。
初始化HandlerExceptionResolvers
基于HandlerExceptionResolver 接口的异常处理,使用这种方式只需要实现resolveException方法,该方法返回一个Mode!AndView 对象,在方法内部对异常的类型进行判断,然后尝试生成对应的ModelAndView 对象,如果方法返回null了,则spring 会继续寻找其他的实现了HandlerExceptionResolver 接口的bean。
Spring 会搜索所有注册在其环境中的实现了HandlerExceptionResolver 接口的bean ,逐个执行, 直到返回了一个ModelAndView 对象。
1 |
|
2 | public class MyExceptionHandler implements HandlerExceptionResolver { |
3 | private static final Logger log = LoggerFactory.getLogger(MyExceptionHandler.class); |
4 | |
5 | public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) { |
6 | request.setAttribute("exception", ex.toString()) ; |
7 | request.setAttribute ("exceptionStack", ex) ; |
8 | log.error ( ex.toString(), ex) ; |
9 | return new ModelAndView( "error/exception"); |
10 | } |
11 | } |
这个类必须声明到Spring 中去,让Spring 管理它。
- 初始化方式:
1
private void initHandlerExceptionResolvers(ApplicationContext context) {
2
this.handlerExceptionResolvers = null;
3
4
if (this.detectAllHandlerExceptionResolvers) {
5
// Find all HandlerExceptionResolvers in the ApplicationContext, including ancestor contexts.
6
Map<String, HandlerExceptionResolver> matchingBeans = BeanFactoryUtils
7
.beansOfTypeIncludingAncestors(context, HandlerExceptionResolver.class, true, false);
8
if (!matchingBeans.isEmpty()) {
9
this.handlerExceptionResolvers = new ArrayList<HandlerExceptionResolver>(matchingBeans.values());
10
// We keep HandlerExceptionResolvers in sorted order.
11
AnnotationAwareOrderComparator.sort(this.handlerExceptionResolvers);
12
}
13
}
14
else {
15
try {
16
HandlerExceptionResolver her =
17
context.getBean(HANDLER_EXCEPTION_RESOLVER_BEAN_NAME, HandlerExceptionResolver.class);
18
this.handlerExceptionResolvers = Collections.singletonList(her);
19
}
20
catch (NoSuchBeanDefinitionException ex) {
21
// Ignore, no HandlerExceptionResolver is fine too.
22
}
23
}
24
// Ensure we have at least some HandlerExceptionResolvers, by registering
25
// default HandlerExceptionResolvers if no other resolvers are found.
26
if (this.handlerExceptionResolvers == null) {
27
this.handlerExceptionResolvers = getDefaultStrategies(context, HandlerExceptionResolver.class);
28
if (logger.isDebugEnabled()) {
29
logger.debug("No HandlerExceptionResolvers found in servlet '" + getServletName() + "': using default");
30
}
31
}
32
}
初始化RequestToViewNameTranslator
当Controller 处理器方法没有返回一个View 对象或逻辑视图名称,并且在该方法中没有直接往respons巳的输出流里面写数据的时候,Spring 就会采用约定好的方式提供一个逻辑视图名称。这个逻辑视图名称是通过Spring 定义的org.springframework.web.servlet.RequestToViewNameTranslator
接口的getViewName 方法来实现的.我们可以实现自己的RequestToViewNameTranslator 接口来约定好没有返回视图名称的时候如何确定视图名称. Spring 已经给我们提供了一个它自己的实现.那就是 org.springframework.web.servlet.view.DefaultRequestToViewNameTranslator
。
在介绍Defau ltRequestToView Nam巳Translator 是如何约定视图名称之前,先来看一下它支
持用户定义的属性。
- prefix :前缀,表示约定好的视图名称需要加上的前缀,默认是空串。
- suffix :后缀,表示约定好的视图名称需要加上的后缀, 默认是空串。
- separator :分隔符,默认是斜杠“/” 。
- stripLeadingS !ash :如果首字符是分隔符,是否要去除, 默认是true 。
- stripTrailingSlash :如果最后一个字符是分隔符,是否要去除,默认是true 。
- stripExtension :如果请求路径包含扩展名是否要去除, 默认是true 。
- ur!Decode : 是否需妥对URL 解码,默认是true 。它会采用request 指定的编码或者IS0-8859-1 编码对URL 进行解码。
当我们没有在SpringMVC 的配置文件中手动的定义一个名为viewNameTranlator 的Bean的时候, Spring 就会为我们提供一个默认的viewNameTranslator , 即DefaultRequestToViewNameTranslator 。
接下来看一下, 当Controller 处理器方法没有返回逻辑视图名称时,DefaultRequestToViewNameTranslator 是如何约定视图名称的。DefaultRequestToView N ameTranslator 会获取到请求的
Url,然后根据提供的属性做一些改造, 把改造之后的结果作为视图名称返回。这里以请求路径http://localhost/app/test/index.html 为例。来说明一下DefaultRequestToViewNameTranslator
是如何工作的。该请求路径对应的请求U阳为/test/index.html ,我们来看以下几种情况, 它分别对应的逻辑视图名称是什么。
- prefix 和suffix 如果都存在,其他为默认值, 那么对应返回的逻辑视图名称应该是prefixtest/indexsuffix 。
- stripLeadingSlash 和stripExtension 都为false , 其他默认, 这时候对应的逻辑视图名称是
/product/index.html
。 - 都采用默认配置时,返回的逻辑视图名称应该是
product/index
如果逻辑视图名称跟请求路径相同或者相关关系都是一样的, 那么我们就可以采用Spring为我们事先约定好的逻辑视图名称返回,这可以大大简化我们的开发工作,而以上功能实现的关键属性viewNameTranslator ,则是initRequestToViewNameTranslator 中完成。
1 | private void initRequestToViewNameTranslator(ApplicationContext context) { |
2 | try { |
3 | this.viewNameTranslator = |
4 | context.getBean(REQUEST_TO_VIEW_NAME_TRANSLATOR_BEAN_NAME, RequestToViewNameTranslator.class); |
5 | if (logger.isDebugEnabled()) { |
6 | logger.debug("Using RequestToViewNameTranslator [" + this.viewNameTranslator + "]"); |
7 | } |
8 | } |
9 | catch (NoSuchBeanDefinitionException ex) { |
10 | // We need to use the default. |
11 | this.viewNameTranslator = getDefaultStrategy(context, RequestToViewNameTranslator.class); |
12 | if (logger.isDebugEnabled()) { |
13 | logger.debug("Unable to locate RequestToViewNameTranslator with name '" + |
14 | REQUEST_TO_VIEW_NAME_TRANSLATOR_BEAN_NAME + "': using default [" + this.viewNameTranslator + |
15 | "]"); |
16 | } |
17 | } |
18 | } |
初始化ViewResolvers
在SpringMVC 中, 当Controller 将请求处理结果放入到ModelAndView 中以后,DispatcherServlet 会根据ModelAndView 选择合适的视图进行渲染.
那么在SpringMVC 中是如何选择合适的View 呢? View 对象是是如何创建的呢? 答案就在ViewResolver 中。ViewResolver接口定义了resolverViewName 方法,根据viewName 创建合适类型的View 实现。
那么如何配置ViewResolver 呢?在Spring 中, ViewResolver 作为Spring Bean 存在,可以
在Spring 配置文件中进行配置,例如下面的代码,配置了JSP 相关的viewResolver。
1 | <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver"> |
2 | <property name="prefix" value="/WEB- INF/views/"/> |
3 | <property name="suffix" value=".jsp"/> |
4 | </bean> |
viewResolvers 属性的初始化工作在initViewResolvers 中完成。
1 | private void initViewResolvers(ApplicationContext context) { |
2 | this.viewResolvers = null; |
3 | |
4 | if (this.detectAllViewResolvers) { |
5 | // Find all ViewResolvers in the ApplicationContext, including ancestor contexts. |
6 | Map<String, ViewResolver> matchingBeans = |
7 | BeanFactoryUtils.beansOfTypeIncludingAncestors(context, ViewResolver.class, true, false); |
8 | if (!matchingBeans.isEmpty()) { |
9 | this.viewResolvers = new ArrayList<ViewResolver>(matchingBeans.values()); |
10 | // We keep ViewResolvers in sorted order. |
11 | AnnotationAwareOrderComparator.sort(this.viewResolvers); |
12 | } |
13 | } |
14 | else { |
15 | try { |
16 | ViewResolver vr = context.getBean(VIEW_RESOLVER_BEAN_NAME, ViewResolver.class); |
17 | this.viewResolvers = Collections.singletonList(vr); |
18 | } |
19 | catch (NoSuchBeanDefinitionException ex) { |
20 | // Ignore, we'll add a default ViewResolver later. |
21 | } |
22 | } |
23 | // Ensure we have at least one ViewResolver, by registering |
24 | // a default ViewResolver if no other resolvers are found. |
25 | if (this.viewResolvers == null) { |
26 | this.viewResolvers = getDefaultStrategies(context, ViewResolver.class); |
27 | if (logger.isDebugEnabled()) { |
28 | logger.debug("No ViewResolvers found in servlet '" + getServletName() + "': using default"); |
29 | } |
30 | } |
31 | } |
初始化FlashMapManager
SpringMVC Flash attributes 提供了一个请求存储属性,可供其他请求使用。在使用重定向时候非常必要,例如Post/Redirect/Get 模式.Flash attributes 在重定向之前暂存(就像存在session中)以便重定向之后还能使用,并立即删除。
SpringMVC 有两个主要的抽象来支持flash attributes 。FlashMap 用于保持flash attributes ,而FlashMapManager 用于存储、检索、管理FlashMap 实例。
flash attribute 支持默认开启(”on” )并不需要显式启用,它永远不会导致HTTP Session 的创建。这两个FlashMap 实例都可以通过静态方法RequestContextUtils 从SpringMVC 的任何位置访问。
flashMapManager 的初始化在initFlashMapManager 中完成。
1 | private void initFlashMapManager(ApplicationContext context) { |
2 | try { |
3 | this.flashMapManager = context.getBean(FLASH_MAP_MANAGER_BEAN_NAME, FlashMapManager.class); |
4 | if (logger.isDebugEnabled()) { |
5 | logger.debug("Using FlashMapManager [" + this.flashMapManager + "]"); |
6 | } |
7 | } |
8 | catch (NoSuchBeanDefinitionException ex) { |
9 | // We need to use the default. |
10 | this.flashMapManager = getDefaultStrategy(context, FlashMapManager.class); |
11 | if (logger.isDebugEnabled()) { |
12 | logger.debug("Unable to locate FlashMapManager with name '" + |
13 | FLASH_MAP_MANAGER_BEAN_NAME + "': using default [" + this.flashMapManager + "]"); |
14 | } |
15 | } |
16 | } |
DispatcherServlet 的逻辑处理
根据之前的示例,我们知道在HttpSe叫et 类中分别提供了相应的服务方法,它们是 doDelete() 、doGet() 、doOptions() 、doPost() 、doPut() 和doTrace(),它会根据请求的不同形式将程序引导至对应的函数进行处理。这几个函数中最常用的函数无非就是doGet() 和doPost(), 那么我们就直接查看DispatcherServlet 中对于这两个函数的逻辑实现。
1 | //FrameworkServlet |
2 | |
3 | protected final void doGet(HttpServletRequest request, HttpServletResponse response) |
4 | throws ServletException, IOException { |
5 | |
6 | processRequest(request, response); |
7 | } |
8 | |
9 | |
10 | protected final void doPost(HttpServletRequest request, HttpServletResponse response) |
11 | throws ServletException, IOException { |
12 | |
13 | processRequest(request, response); |
14 | } |
对于不同的方法, Spring 并没有做特殊处理,而是统一将程序再一次地引导至processRequest( request, response)中。
1 | ////FrameworkServlet |
2 | protected final void processRequest(HttpServletRequest request, HttpServletResponse response) |
3 | throws ServletException, IOException { |
4 | //记录当前时间,用于计算web请求总的花费时间 |
5 | long startTime = System.currentTimeMillis(); |
6 | Throwable failureCause = null; |
7 | |
8 | LocaleContext previousLocaleContext = LocaleContextHolder.getLocaleContext(); |
9 | LocaleContext localeContext = buildLocaleContext(request); |
10 | |
11 | RequestAttributes previousAttributes = RequestContextHolder.getRequestAttributes(); |
12 | ServletRequestAttributes requestAttributes = buildRequestAttributes(request, response, previousAttributes); |
13 | |
14 | WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request); |
15 | asyncManager.registerCallableInterceptor(FrameworkServlet.class.getName(), new RequestBindingInterceptor()); |
16 | |
17 | initContextHolders(request, localeContext, requestAttributes); |
18 | |
19 | try { |
20 | doService(request, response); |
21 | } |
22 | catch (ServletException ex) { |
23 | failureCause = ex; |
24 | throw ex; |
25 | } |
26 | catch (IOException ex) { |
27 | failureCause = ex; |
28 | throw ex; |
29 | } |
30 | catch (Throwable ex) { |
31 | failureCause = ex; |
32 | throw new NestedServletException("Request processing failed", ex); |
33 | } |
34 | |
35 | finally { |
36 | resetContextHolders(request, previousLocaleContext, previousAttributes); |
37 | if (requestAttributes != null) { |
38 | requestAttributes.requestCompleted(); |
39 | } |
40 | |
41 | if (logger.isDebugEnabled()) { |
42 | if (failureCause != null) { |
43 | this.logger.debug("Could not complete request", failureCause); |
44 | } |
45 | else { |
46 | if (asyncManager.isConcurrentHandlingStarted()) { |
47 | logger.debug("Leaving response open for concurrent processing"); |
48 | } |
49 | else { |
50 | this.logger.debug("Successfully completed request"); |
51 | } |
52 | } |
53 | } |
54 | |
55 | publishRequestHandledEvent(request, response, startTime, failureCause); |
56 | } |
57 | } |
通过以上方法可以看出,service 方法为核心处理方法,其他方法为准备工作:
- 为了保证当前线程的LocaleContext 以及RequestAttributes 可以在当前请求后还能恢复,提取当前线程的两个属性
- 根据当前request 创建对应的LocaleContext 和RequestAttributes ,并绑定到当前线程。
- 委托给doService 方法进一步处理。
- 请求处理结束后恢复线程到原始状态。
- 请求处理结束后无论成功与否发布事件通知。
看 doService 方法的处理
1 | //DispatcherServlet |
2 | protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception { |
3 | if (logger.isDebugEnabled()) { |
4 | String resumed = WebAsyncUtils.getAsyncManager(request).hasConcurrentResult() ? " resumed" : ""; |
5 | logger.debug("DispatcherServlet with name '" + getServletName() + "'" + resumed + |
6 | " processing " + request.getMethod() + " request for [" + getRequestUri(request) + "]"); |
7 | } |
8 | |
9 | // Keep a snapshot of the request attributes in case of an include, |
10 | // to be able to restore the original attributes after the include. |
11 | Map<String, Object> attributesSnapshot = null; |
12 | if (WebUtils.isIncludeRequest(request)) { |
13 | attributesSnapshot = new HashMap<String, Object>(); |
14 | Enumeration<?> attrNames = request.getAttributeNames(); |
15 | while (attrNames.hasMoreElements()) { |
16 | String attrName = (String) attrNames.nextElement(); |
17 | if (this.cleanupAfterInclude || attrName.startsWith(DEFAULT_STRATEGIES_PREFIX)) { |
18 | attributesSnapshot.put(attrName, request.getAttribute(attrName)); |
19 | } |
20 | } |
21 | } |
22 | |
23 | // Make framework objects available to handlers and view objects. |
24 | request.setAttribute(WEB_APPLICATION_CONTEXT_ATTRIBUTE, getWebApplicationContext()); |
25 | request.setAttribute(LOCALE_RESOLVER_ATTRIBUTE, this.localeResolver); |
26 | request.setAttribute(THEME_RESOLVER_ATTRIBUTE, this.themeResolver); |
27 | request.setAttribute(THEME_SOURCE_ATTRIBUTE, getThemeSource()); |
28 | |
29 | FlashMap inputFlashMap = this.flashMapManager.retrieveAndUpdate(request, response); |
30 | if (inputFlashMap != null) { |
31 | request.setAttribute(INPUT_FLASH_MAP_ATTRIBUTE, Collections.unmodifiableMap(inputFlashMap)); |
32 | } |
33 | request.setAttribute(OUTPUT_FLASH_MAP_ATTRIBUTE, new FlashMap()); |
34 | request.setAttribute(FLASH_MAP_MANAGER_ATTRIBUTE, this.flashMapManager); |
35 | |
36 | try { |
37 | doDispatch(request, response); |
38 | } |
39 | finally { |
40 | if (!WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) { |
41 | // Restore the original attribute snapshot, in case of an include. |
42 | if (attributesSnapshot != null) { |
43 | restoreAttributesAfterInclude(request, attributesSnapshot); |
44 | } |
45 | } |
46 | } |
47 | } |
doService中做了一些请求前的准备工作。将已经初始化的功能辅助工具变量,比如localeResolver ,themeResolver 等设置在request 属性中, 而这些属性会在接下来的处理中派上用场。
1 | //DispatcherServlet |
2 | protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception { |
3 | HttpServletRequest processedRequest = request; |
4 | HandlerExecutionChain mappedHandler = null; |
5 | boolean multipartRequestParsed = false; |
6 | |
7 | WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request); |
8 | |
9 | try { |
10 | ModelAndView mv = null; |
11 | Exception dispatchException = null; |
12 | |
13 | try { |
14 | ////如果是MultipartContent 类型的request则转换request 为MultipartHttpServletRequest 类型的request |
15 | processedRequest = checkMultipart(request); |
16 | multipartRequestParsed = (processedRequest != request); |
17 | // 依据request 查找对应的handler |
18 | // Determine handler for the current request. |
19 | mappedHandler = getHandler(processedRequest); |
20 | if (mappedHandler == null || mappedHandler.getHandler() == null) { |
21 | //如果没有找到对应的handler 则通过response 反馈错误信息 |
22 | noHandlerFound(processedRequest, response); |
23 | return; |
24 | } |
25 | //根据当前的handler 寻找对应的HandlerAdapter |
26 | // Determine handler adapter for the current request. |
27 | HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler()); |
28 | ////如果当前handler 支持last-modified 头处理 |
29 | // Process last-modified header, if supported by the handler. |
30 | String method = request.getMethod(); |
31 | boolean isGet = "GET".equals(method); |
32 | if (isGet || "HEAD".equals(method)) { |
33 | long lastModified = ha.getLastModified(request, mappedHandler.getHandler()); |
34 | if (logger.isDebugEnabled()) { |
35 | logger.debug("Last-Modified value for [" + getRequestUri(request) + "] is: " + lastModified); |
36 | } |
37 | if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) { |
38 | return; |
39 | } |
40 | } |
41 | //拦截榕的preHandler 方法的调用 |
42 | if (!mappedHandler.applyPreHandle(processedRequest, response)) { |
43 | return; |
44 | } |
45 | //真正的激活handler 并返回视图 |
46 | // Actually invoke the handler. |
47 | mv = ha.handle(processedRequest, response, mappedHandler.getHandler()); |
48 | |
49 | if (asyncManager.isConcurrentHandlingStarted()) { |
50 | return; |
51 | } |
52 | //视图名称转换应用于馆要添加前缀后缀的情况 |
53 | applyDefaultViewName(processedRequest, mv); |
54 | //应用所有拦截器的postHandle 方法 |
55 | mappedHandler.applyPostHandle(processedRequest, response, mv); |
56 | } |
57 | catch (Exception ex) { |
58 | dispatchException = ex; |
59 | } |
60 | catch (Throwable err) { |
61 | // As of 4.3, we're processing Errors thrown from handler methods as well, |
62 | // making them available for @ExceptionHandler methods and other scenarios. |
63 | dispatchException = new NestedServletException("Handler dispatch failed", err); |
64 | } |
65 | // 处理最终结果 |
66 | processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException); |
67 | } |
68 | catch (Exception ex) { |
69 | triggerAfterCompletion(processedRequest, response, mappedHandler, ex); |
70 | } |
71 | catch (Throwable err) { |
72 | triggerAfterCompletion(processedRequest, response, mappedHandler, |
73 | new NestedServletException("Handler processing failed", err)); |
74 | } |
75 | finally { |
76 | if (asyncManager.isConcurrentHandlingStarted()) { |
77 | // Instead of postHandle and afterCompletion |
78 | if (mappedHandler != null) { |
79 | mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response); |
80 | } |
81 | } |
82 | else { |
83 | // Clean up any resources used by a multipart request. |
84 | if (multipartRequestParsed) { |
85 | cleanupMultipart(processedRequest); |
86 | } |
87 | } |
88 | } |
89 | } |
doDispatch 函数中展示了Spring 请求处理所涉及的主要逻辑,而我们之前设置在request中的各种辅助属性也都有被派上了用场。下面回顾一下逻辑处理的全过程。
MultipartContent 类型的request 处理
对于请求的处理, Spring 首先考虑的是对于Multipart 的处理, 如果是MultipartContent 类型的request ,则转换request 为MultipartHttpServletRequest 类型的request。
1 | //DispatcherServlet |
2 | protected HttpServletRequest checkMultipart(HttpServletRequest request) throws MultipartException { |
3 | if (this.multipartResolver != null && this.multipartResolver.isMultipart(request)) { |
4 | if (WebUtils.getNativeRequest(request, MultipartHttpServletRequest.class) != null) { |
5 | logger.debug("Request is already a MultipartHttpServletRequest - if not in a forward, " + |
6 | "this typically results from an additional MultipartFilter in web.xml"); |
7 | } |
8 | else if (hasMultipartException(request) ) { |
9 | logger.debug("Multipart resolution failed for current request before - " + |
10 | "skipping re-resolution for undisturbed error rendering"); |
11 | } |
12 | else { |
13 | try { |
14 | return this.multipartResolver.resolveMultipart(request); |
15 | } |
16 | catch (MultipartException ex) { |
17 | if (request.getAttribute(WebUtils.ERROR_EXCEPTION_ATTRIBUTE) != null) { |
18 | logger.debug("Multipart resolution failed for error dispatch", ex); |
19 | // Keep processing error dispatch with regular request handle below |
20 | } |
21 | else { |
22 | throw ex; |
23 | } |
24 | } |
25 | } |
26 | } |
27 | // If not returned before: return original request. |
28 | return request; |
29 | } |
根据request 信息寻找对应的Handler
在Spring 中最简单的映射处理器配置如下:
1 | <bean id= "simpleUrlMapping" |
2 | class = "org.Springframework.web.servlet.handler.SimpleUrlHandlerMapping"> |
3 | <property name="mappings"> |
4 | <props> |
5 | <prop key="/userlist.htm">userController</prop> |
6 | </props> |
7 | </property> |
8 | </bean> |
在Spring 加载的过程中, Spring 会将类型为SimpleUrlHandlerMapping 的实例加载到this.handlerMappings 中,按照常理推断,根据request 提取对应的Handler ,无非就是提取当前实例中的userController ,但是userController 为继承自AbstractController 类型实例,与HandlerExecutionChain 并无任何关联,那么这一步是如何封装的呢?
1 | //DispatcherServlet |
2 | protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception { |
3 | for (HandlerMapping hm : this.handlerMappings) { |
4 | if (logger.isTraceEnabled()) { |
5 | logger.trace( |
6 | "Testing handler map [" + hm + "] in DispatcherServlet with name '" + getServletName() + "'"); |
7 | } |
8 | HandlerExecutionChain handler = hm.getHandler(request); |
9 | if (handler != null) { |
10 | return handler; |
11 | } |
12 | } |
13 | return null; |
14 | } |
在之前的内容我们提过, 在系统启动时Spring 会将所有的映射类型的bean 注册到this.handlerMappings 变量中,所以此函数的目的就是遍历所有的HandlerMapping , 并调用其getHandler 方法进行封装处理.以SimpleUrlHandlerMapping 为例查看其getHandler 方法如下:
1 | //AbstractHandlerMapping |
2 | public final HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception { |
3 | //根据request 获取对应的handler |
4 | Object handler = getHandlerInternal(request); |
5 | if (handler == null) { |
6 | //如果没有对应request 的handler 则使用默认的handler |
7 | handler = getDefaultHandler(); |
8 | } |
9 | //如果也没有提供默认的handler 则无法继续处理返回null |
10 | if (handler == null) { |
11 | return null; |
12 | } |
13 | // Bean name or resolved handler? |
14 | if (handler instanceof String) { |
15 | String handlerName = (String) handler; |
16 | handler = getApplicationContext().getBean(handlerName); |
17 | } |
18 | |
19 | HandlerExecutionChain executionChain = getHandlerExecutionChain(handler, request); |
20 | if (CorsUtils.isCorsRequest(request)) { |
21 | CorsConfiguration globalConfig = this.globalCorsConfigSource.getCorsConfiguration(request); |
22 | CorsConfiguration handlerConfig = getCorsConfiguration(handler, request); |
23 | CorsConfiguration config = (globalConfig != null ? globalConfig.combine(handlerConfig) : handlerConfig); |
24 | executionChain = getCorsHandlerExecutionChain(request, executionChain, config); |
25 | } |
26 | return executionChain; |
27 | } |
函数中首先会使用getHandlerlntemal 方法根据request 信息获取对应的Handler.如果以SimpleUrlHandlerMapping 为例分析, 那么我们推断此步骤提供的功能很可能就是根据URL 找到匹配的Controller 并返回,当然如果没有找到对应的Controller 处理器那么程序会尝试去查找配置中的默认处理器,当然,当查找的controller 为String 类型时,那就意味着返回的是配置的bean 名称,需要根据bean 名称查找对应的bean,最后,还要通过getHandlerExecutionChain 方法对返回的Handler 进行封装,以保证满足返回类型的匹配。
- 根据request 查找对应的Handler
1
//AbstractUrlHandlerMapping
2
protected Object getHandlerInternal(HttpServletRequest request) throws Exception {
3
//截取用于匹配的url 有效路径
4
String lookupPath = getUrlPathHelper().getLookupPathForRequest(request);
5
//根据路径寻找Handler
6
Object handler = lookupHandler(lookupPath, request);
7
if (handler == null) {
8
// We need to care for the default handler directly, since we need to
9
// expose the PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE for it as well.
10
Object rawHandler = null;
11
if ("/".equals(lookupPath)) {
12
//如果请求的路径仅仅是"/" ,那么使用RootHandler 进行处理
13
rawHandler = getRootHandler();
14
}
15
if (rawHandler == null) {
16
//无法找到handler 则使用默认handler
17
rawHandler = getDefaultHandler();
18
}
19
if (rawHandler != null) {
20
//根据beanName 获取对应的bean
21
// Bean name or resolved handler?
22
if (rawHandler instanceof String) {
23
String handlerName = (String) rawHandler;
24
rawHandler = getApplicationContext().getBean(handlerName);
25
}
26
//模版方法
27
validateHandler(rawHandler, request);
28
handler = buildPathExposingHandler(rawHandler, lookupPath, lookupPath, null);
29
}
30
}
31
if (handler != null && logger.isDebugEnabled()) {
32
logger.debug("Mapping [" + lookupPath + "] to " + handler);
33
}
34
else if (handler == null && logger.isTraceEnabled()) {
35
logger.trace("No handler mapping found for [" + lookupPath + "]");
36
}
37
return handler;
38
}
39
40
//lookUpHandler() 根据URL 获取对应Handler 的匹配规则代码实现起来虽然很长,但是并不难理解,考虑了直接匹配与通配符两种情况。
41
protected Object lookupHandler(String urlPath, HttpServletRequest request) throws Exception {
42
//直接匹配情况的处理
43
// Direct match?
44
Object handler = this.handlerMap.get(urlPath);
45
if (handler != null) {
46
// Bean name or resolved handler?
47
if (handler instanceof String) {
48
String handlerName = (String) handler;
49
handler = getApplicationContext().getBean(handlerName);
50
}
51
validateHandler(handler, request);
52
return buildPathExposingHandler(handler, urlPath, urlPath, null);
53
}
54
//通配符的处理
55
// Pattern match?
56
List<String> matchingPatterns = new ArrayList<String>();
57
for (String registeredPattern : this.handlerMap.keySet()) {
58
if (getPathMatcher().match(registeredPattern, urlPath)) {
59
matchingPatterns.add(registeredPattern);
60
}
61
else if (useTrailingSlashMatch()) {
62
if (!registeredPattern.endsWith("/") && getPathMatcher().match(registeredPattern + "/", urlPath)) {
63
matchingPatterns.add(registeredPattern +"/");
64
}
65
}
66
}
67
String bestMatch = null;
68
Comparator<String> patternComparator = getPathMatcher().getPatternComparator(urlPath);
69
if (!matchingPatterns.isEmpty()) {
70
Collections.sort(matchingPatterns, patternComparator);
71
if (logger.isDebugEnabled()) {
72
logger.debug("Matching patterns for request [" + urlPath + "] are " + matchingPatterns);
73
}
74
bestMatch = matchingPatterns.get(0);
75
}
76
if (bestMatch != null) {
77
handler = this.handlerMap.get(bestMatch);
78
if (handler == null) {
79
if (bestMatch.endsWith("/")) {
80
handler = this.handlerMap.get(bestMatch.substring(0, bestMatch.length() - 1));
81
}
82
if (handler == null) {
83
throw new IllegalStateException(
84
"Could not find handler for best pattern match [" + bestMatch + "]");
85
}
86
}
87
// Bean name or resolved handler?
88
if (handler instanceof String) {
89
String handlerName = (String) handler;
90
handler = getApplicationContext().getBean(handlerName);
91
}
92
validateHandler(handler, request);
93
String pathWithinMapping = getPathMatcher().extractPathWithinPattern(bestMatch, urlPath);
94
95
// There might be multiple 'best patterns', let's make sure we have the correct URI template variables
96
// for all of them
97
Map<String, String> uriTemplateVariables = new LinkedHashMap<String, String>();
98
for (String matchingPattern : matchingPatterns) {
99
if (patternComparator.compare(bestMatch, matchingPattern) == 0) {
100
Map<String, String> vars = getPathMatcher().extractUriTemplateVariables(matchingPattern, urlPath);
101
Map<String, String> decodedVars = getUrlPathHelper().decodePathVariables(request, vars);
102
uriTemplateVariables.putAll(decodedVars);
103
}
104
}
105
if (logger.isDebugEnabled()) {
106
logger.debug("URI Template variables for request [" + urlPath + "] are " + uriTemplateVariables);
107
}
108
return buildPathExposingHandler(handler, bestMatch, pathWithinMapping, uriTemplateVariables);
109
}
110
111
// No handler found...
112
return null;
113
}
114
//其中要提及的是buildPathExposingHandler 函数,它将Handler 封装成了HandlerExecutionChain 类型。
115
protected Object buildPathExposingHandler(Object rawHandler, String bestMatchingPattern,
116
String pathWithinMapping, Map<String, String> uriTemplateVariables) {
117
118
HandlerExecutionChain chain = new HandlerExecutionChain(rawHandler);
119
chain.addInterceptor(new PathExposingHandlerInterceptor(bestMatchingPattern, pathWithinMapping));
120
if (!CollectionUtils.isEmpty(uriTemplateVariables)) {
121
chain.addInterceptor(new UriTemplateVariablesHandlerInterceptor(uriTemplateVariables));
122
}
123
return chain;
124
}
- 加入拦截器到执行链
getHandlerExecutionChain 函数最主要的目的是将配置中的对应拦截器加入到执行链中,以保证这些拦截器可以有效地作用于目标对象。1
//AbstractHandlerMapping
2
protected HandlerExecutionChain getHandlerExecutionChain(Object handler, HttpServletRequest request) {
3
HandlerExecutionChain chain = (handler instanceof HandlerExecutionChain ?
4
(HandlerExecutionChain) handler : new HandlerExecutionChain(handler));
5
6
String lookupPath = this.urlPathHelper.getLookupPathForRequest(request);
7
for (HandlerInterceptor interceptor : this.adaptedInterceptors) {
8
if (interceptor instanceof MappedInterceptor) {
9
MappedInterceptor mappedInterceptor = (MappedInterceptor) interceptor;
10
if (mappedInterceptor.matches(lookupPath, this.pathMatcher)) {
11
chain.addInterceptor(mappedInterceptor.getInterceptor());
12
}
13
}
14
else {
15
chain.addInterceptor(interceptor);
16
}
17
}
18
return chain;
19
}
没找到对应的Handler 的错误处理
每个请求都应该对应着- Handler ,因为每个请求都会在后台有相应的逻辑对应,而逻辑的实现就是在Handler 中,所以一旦遇到没有找到Handler 的情况(正常情况下如果没有URL匹配的Handler ,开发人员可以设置默认的Handler 来处理请求,但是如果默认请求也未设置就会出现Handler 为空的情况),就只能通过respons巳向用户返回错误信息。
1 | //dispatcherServlet |
2 | protected void noHandlerFound(HttpServletRequest request, HttpServletResponse response) throws Exception { |
3 | if (pageNotFoundLogger.isWarnEnabled()) { |
4 | pageNotFoundLogger.warn("No mapping found for HTTP request with URI [" + getRequestUri(request) + |
5 | "] in DispatcherServlet with name '" + getServletName() + "'"); |
6 | } |
7 | if (this.throwExceptionIfNoHandlerFound) { |
8 | throw new NoHandlerFoundException(request.getMethod(), getRequestUri(request), |
9 | new ServletServerHttpRequest(request).getHeaders()); |
10 | } |
11 | else { |
12 | response.sendError(HttpServletResponse.SC_NOT_FOUND); |
13 | } |
14 | } |
根据当前Handler 寻找对应的HandlerAdapter
在WebApplicationContext 的初始化过程中我们讨论了HandlerAdapters 的初始化,了解了在默认情况下普通的Web 请求会交给SimpleControllerHandlerAdapter 去处理。下面我们以
SimpleControllerHandlerAdapter 为例来分析获取适配器的逻辑。
1 | //dispatcherServlet |
2 | protected HandlerAdapter getHandlerAdapter(Object handler) throws ServletException { |
3 | for (HandlerAdapter ha : this.handlerAdapters) { |
4 | if (logger.isTraceEnabled()) { |
5 | logger.trace("Testing handler adapter [" + ha + "]"); |
6 | } |
7 | if (ha.supports(handler)) { |
8 | return ha; |
9 | } |
10 | } |
11 | throw new ServletException("No adapter for handler [" + handler + |
12 | "]: The DispatcherServlet configuration needs to include a HandlerAdapter that supports this handler"); |
13 | } |
对于获取适配器的逻辑无非就是遍历所有适配器来选择合适的适配器并返回它,而 某个适配器是否适用于当前的Handler 逻辑被封装在具体的适配器中。
1 | //SimpleControllerHandlerAdapter implements HandlerAdapter |
2 |
|
3 | public boolean supports(Object handler) { |
4 | return (handler instanceof Controller); |
5 | } |
一切已经明了, SimpleControllerHandlerAdapter 就是用于处理普通的Web
请求的,而且对于SpringMVC 来说,我们会把逻辑封装至Controller 的子类中,例如我们之前的引导示例UserController 就是继承自AbstractController,而AbstractController 实现Controller 接口.
缓存处理
在研究Spring 对缓存处理的功能支持前,我们先了解一个概念:Last-Modified 缓存机制。
在客户端第一次输入URL 时, 服务器端会返回内容和状态码200,表示请求成功, 同时会添加一个
Last-Modified
的响应头,表示此文件在服务器上的最后更新时间,eg:Last-Modified : Wed, 14Mar2012 10:22:42 GMT
表示最后的响应时间为: 2012-03-14 10:22客户端第二次请求此URL 时,客户端会向服务器发送请求头
If-Modified-Since
,询问服务器该时间之后当前请求内容是否有被修改过,如If-Modified-Since: Wed, 14 Mar 2012 10:22:42 GMT
,如果服务器端的内容没有变化, 则自动返回HTTP 304
状态码(只要响应头,
内容为空,这样就节省了网络带宽)。
Spring 提供的对 Last-Modified
机制的支持,只需要实现LastModified 接口,如下所示:
1 | public class HelloWordLastModifiedCachedController extends AbstractController implements LastModified { |
2 | |
3 | private long lastModified; |
4 | |
5 | protected ModelAndView handleRequestInternal(HttpServletRequest request, HttpServletResponse response) throws Exception { |
6 | //点击后再次请求当前页面 |
7 | response.getWriter ().write ("<a href=''>this</a>") ; |
8 | return null; |
9 | } |
10 | |
11 | public long getLastModified(HttpServletRequest request) { |
12 | if ( lastModified == 01 ) { |
13 | //第一次或者逻辑有变化的时候, 应该重新返回内容最新修改的时间戳 |
14 | lastModified = System.currentTimeMillis(); |
15 | } |
16 | return lastModified; |
17 | } |
18 | } |
HelloWorldLastModifiedCacheController 只需要实现LastModified 接口的getLastModified方法,保证当内容发生改变时返回最新的修改时间即可。
Spring 判断是否过期, 通过判断请求的 If-Modified-Since
是否大于等于当前的getLastModified 方法的时间戳,如果是, 则认为没有修改。上面的controller 与普通的controller 并无太大差别, 声明如下:
1 | <bean id="/helloLastModified" class="com.zbcn.web.controller.HelloWordLastModifiedCachedController"/> |
Handlerlnterceptor 的处理
Servlet API 定义的servlet 过滤器可以在servlet 处理每个Web 请求的前后分别对它进行前置处理和后置处理。有些时候, 你可能只想处理由某些SpringMVC 处理程序处理的Web请求,并在这些处理程序返回的模型属性被传递到视图之前,对它们进行一些操作。
SpringMVC 允许你通过处理拦截Web 请求,进行前置处理和后置处理。处理拦截是在Spring 的Web 应用程序上下文中配置的,因此它们可以利用各种容器特性,并引用容器中声明的任何bean 。处理拦截是针对特殊的处理程序映射进行注册的,因此它只拦截通过这些处理程序映射的请求。每个处理拦截都必须实现HandlerInterceptor 接口,包含三个需要你实现的回调方法: preHandle() 、postHandle()和afterCompletion()。第一个和第二个方法分别是在处理程序处理请求之前和之后被调用的。第二个方法还允许访问返回的ModelAndView 对象, 因此可以在它里面操作模型属性。最后一个方法是在所有请求处理完成之后被调用的( 如视图呈现之后),以下是HandlerInterceptor 的简单实现:
1 |
|
2 | public class MyInterceptor implements HandlerInterceptor { |
3 | |
4 | public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { |
5 | long startTime = System.currentTimeMillis(); |
6 | request.setAttribute("startTime", startTime); ; |
7 | return true ; |
8 | } |
9 | |
10 | public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception { |
11 | long startTime = (Long) request.getAttribute("startTime"); |
12 | request.removeAttribute("startTime"); |
13 | long endTime= System.currentTimeMillis( ); |
14 | modelAndView.addObject("handleTime", endTime-startTime); |
15 | } |
16 | |
17 | public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { |
18 | |
19 | } |
20 | } |
在以上拦截器中,preHandle中记录了请求的开始时间,并将它保存到请求属性中。这个方法应该返回true ,允许DispatcherServlet 继续处理请求。否则, DispatcherServlet 会认为这个方法已经处理了请求, 直接将响应返回给用户。postHandle中,从请求属性中加载起始时间,并将它与当前时间进行比较。你可以计算总的持续时间, 然后把这个时间添加到模型中,传递给视图。
逻辑处理
对于逻辑处理其实是通过适配器中转调用Handler 并返回视图的,对应代码如下:
1 | // Actually invoke the handler. |
2 | mv = ha.handle(processedRequest, response, mappedHandler.getHandler()); |
同样,还是以引导示例为基础进行处理逻辑分析,之前分析过,对于普通的Web 请求,Spring默认使用SimpleControllerHandlerAdapter 类进行处理, 我们进入SimpleControllerHandlerAdapter 类的handle 方法如下
1 | //SimpleControllerHandlerAdapter |
2 |
|
3 | public ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler) |
4 | throws Exception { |
5 | |
6 | return ((Controller) handler).handleRequest(request, response); |
7 | } |
但是回顾引导示例中的UserController ,我们的逻辑是写在handleRequestinternal 函数中而不是handleRequest 函数,所以我们还需要进一步分析这期间所包含的处理流程。
1 | //AbstractController |
2 |
|
3 | public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) |
4 | throws Exception { |
5 | |
6 | if (HttpMethod.OPTIONS.matches(request.getMethod())) { |
7 | response.setHeader("Allow", getAllowHeader()); |
8 | return null; |
9 | } |
10 | |
11 | // Delegate to WebContentGenerator for checking and preparing. |
12 | checkRequest(request); |
13 | prepareResponse(response); |
14 | //如果需要session 内的同步执行 |
15 | // Execute handleRequestInternal in synchronized block if required. |
16 | if (this.synchronizeOnSession) { |
17 | HttpSession session = request.getSession(false); |
18 | if (session != null) { |
19 | Object mutex = WebUtils.getSessionMutex(session); |
20 | synchronized (mutex) { |
21 | // 调用用户的逻辑 |
22 | return handleRequestInternal(request, response); |
23 | } |
24 | } |
25 | } |
26 | //调用用户逻辑 |
27 | return handleRequestInternal(request, response); |
28 | } |
异常视图的处理
有时候系统运行过程中出现异常,而我们并不希望就此中断对用户的服务,而是至少告知客户当前系统在处理逻辑的过程中出现了异常,甚至告知他们因为什么原因导致的。Spring 中的异常处理机制会帮我们完成这个工作。其实,这里Spring 主要的工作就是将逻辑引导至HandlerExceptionResolver 类的resolveException 方法。而HandlerExceptionResolver 的使用,我们在讲解WebApplicationContext 的初始化的时-候已经介绍过了
processDispatchResult -> processHandlerException
1 | //DispatcherServlet |
2 | protected ModelAndView processHandlerException(HttpServletRequest request, HttpServletResponse response, |
3 | Object handler, Exception ex) throws Exception { |
4 | |
5 | // Check registered HandlerExceptionResolvers... |
6 | ModelAndView exMv = null; |
7 | for (HandlerExceptionResolver handlerExceptionResolver : this.handlerExceptionResolvers) { |
8 | exMv = handlerExceptionResolver.resolveException(request, response, handler, ex); |
9 | if (exMv != null) { |
10 | break; |
11 | } |
12 | } |
13 | if (exMv != null) { |
14 | if (exMv.isEmpty()) { |
15 | request.setAttribute(EXCEPTION_ATTRIBUTE, ex); |
16 | return null; |
17 | } |
18 | // We might still need view name translation for a plain error model... |
19 | if (!exMv.hasView()) { |
20 | exMv.setViewName(getDefaultViewName(request)); |
21 | } |
22 | if (logger.isDebugEnabled()) { |
23 | logger.debug("Handler execution resulted in exception - forwarding to resolved error view: " + exMv, ex); |
24 | } |
25 | WebUtils.exposeErrorRequestAttributes(request, ex, getServletName()); |
26 | return exMv; |
27 | } |
28 | |
29 | throw ex; |
30 | } |
根据视图跳转页面
无论是一个系统还是一个站点,最重要的工作都是与用户进行交互,用户操作系统后无论下发的命令成功与否都需要给用户一个反馈,以便于用户进行下一步的判断.所以,在逻辑处
1 | //DispatcherServlet |
2 | protected void render(ModelAndView mv, HttpServletRequest request, HttpServletResponse response) throws Exception { |
3 | // Determine locale for request and apply it to the response. |
4 | Locale locale = this.localeResolver.resolveLocale(request); |
5 | response.setLocale(locale); |
6 | |
7 | View view; |
8 | if (mv.isReference()) { |
9 | // We need to resolve the view name. |
10 | view = resolveViewName(mv.getViewName(), mv.getModelInternal(), locale, request); |
11 | if (view == null) { |
12 | throw new ServletException("Could not resolve view with name '" + mv.getViewName() + |
13 | "' in servlet with name '" + getServletName() + "'"); |
14 | } |
15 | } |
16 | else { |
17 | // No need to lookup: the ModelAndView object contains the actual View object. |
18 | view = mv.getView(); |
19 | if (view == null) { |
20 | throw new ServletException("ModelAndView [" + mv + "] neither contains a view name nor a " + |
21 | "View object in servlet with name '" + getServletName() + "'"); |
22 | } |
23 | } |
24 | |
25 | // Delegate to the View object for rendering. |
26 | if (logger.isDebugEnabled()) { |
27 | logger.debug("Rendering view [" + view + "] in DispatcherServlet with name '" + getServletName() + "'"); |
28 | } |
29 | try { |
30 | if (mv.getStatus() != null) { |
31 | response.setStatus(mv.getStatus().value()); |
32 | } |
33 | view.render(mv.getModelInternal(), request, response); |
34 | } |
35 | catch (Exception ex) { |
36 | if (logger.isDebugEnabled()) { |
37 | logger.debug("Error rendering view [" + view + "] in DispatcherServlet with name '" + |
38 | getServletName() + "'", ex); |
39 | } |
40 | throw ex; |
41 | } |
42 | } |
解析视图名称
在上文中我们提到DispatcherServ let 会根据Mode!AndView 选择合适的视图来进行渲染,而这一功能就是在resolveViewName 函数中完成的
1 | //DispatcherServlet |
2 | protected View resolveViewName(String viewName, Map<String, Object> model, Locale locale, |
3 | HttpServletRequest request) throws Exception { |
4 | |
5 | for (ViewResolver viewResolver : this.viewResolvers) { |
6 | View view = viewResolver.resolveViewName(viewName, locale); |
7 | if (view != null) { |
8 | return view; |
9 | } |
10 | } |
11 | return null; |
12 | } |
我们以org.Springframework.web.servletview.InternalResourceViewResolver 为例来分析
1 | //AbstractCachingViewResolver |
2 |
|
3 | public View resolveViewName(String viewName, Locale locale) throws Exception { |
4 | //不存在缓存的情况下直接创建视图 |
5 | if (!isCache()) { |
6 | return createView(viewName, locale); |
7 | } |
8 | else { |
9 | //直接从缓存中提取 |
10 | Object cacheKey = getCacheKey(viewName, locale); |
11 | View view = this.viewAccessCache.get(cacheKey); |
12 | if (view == null) { |
13 | synchronized (this.viewCreationCache) { |
14 | view = this.viewCreationCache.get(cacheKey); |
15 | if (view == null) { |
16 | // Ask the subclass to create the View object. |
17 | view = createView(viewName, locale); |
18 | if (view == null && this.cacheUnresolved) { |
19 | view = UNRESOLVED_VIEW; |
20 | } |
21 | if (view != null) { |
22 | this.viewAccessCache.put(cacheKey, view); |
23 | this.viewCreationCache.put(cacheKey, view); |
24 | if (logger.isTraceEnabled()) { |
25 | logger.trace("Cached view [" + cacheKey + "]"); |
26 | } |
27 | } |
28 | } |
29 | } |
30 | } |
31 | return (view != UNRESOLVED_VIEW ? view : null); |
32 | } |
33 | } |
在父类UrlBasedViewResolver 中重写了createView 函数。
1 | //UrlBasedViewResolver |
2 | protected View createView(String viewName, Locale locale) throws Exception { |
3 | // If this resolver is not supposed to handle the given view, |
4 | // return null to pass on to the next resolver in the chain. |
5 | //如果当前解析器不支持当前解析器如viewName 为空等情况 |
6 | if (!canHandle(viewName, locale)) { |
7 | return null; |
8 | } |
9 | //处理前缀为redire ct : xx 的情况 |
10 | // Check for special "redirect:" prefix. |
11 | if (viewName.startsWith(REDIRECT_URL_PREFIX)) { |
12 | String redirectUrl = viewName.substring(REDIRECT_URL_PREFIX.length()); |
13 | RedirectView view = new RedirectView(redirectUrl, isRedirectContextRelative(), isRedirectHttp10Compatible()); |
14 | view.setHosts(getRedirectHosts()); |
15 | return applyLifecycleMethods(viewName, view); |
16 | } |
17 | //处理前缀为forward : xx 的情况 |
18 | // Check for special "forward:" prefix. |
19 | if (viewName.startsWith(FORWARD_URL_PREFIX)) { |
20 | String forwardUrl = viewName.substring(FORWARD_URL_PREFIX.length()); |
21 | return new InternalResourceView(forwardUrl); |
22 | } |
23 | // Else fall back to superclass implementation: calling loadView. |
24 | return super.createView(viewName, locale); |
25 | } |
1 | //AbstractCachingViewResolver |
2 | protected View createView(String viewName, Locale locale) throws Exception { |
3 | return loadView(viewName, locale); |
4 | } |
1 | //UrlBasedViewResolver |
2 |
|
3 | protected View loadView(String viewName, Locale locale) throws Exception { |
4 | AbstractUrlBasedView view = buildView(viewName); |
5 | View result = applyLifecycleMethods(viewName, view); |
6 | return (view.checkResource(locale) ? result : null); |
7 | } |
8 | protected AbstractUrlBasedView buildView(String viewName) throws Exception { |
9 | AbstractUrlBasedView view = (AbstractUrlBasedView) BeanUtils.instantiateClass(getViewClass()); |
10 | //添加前缀以及后缀 |
11 | view.setUrl(getPrefix() + viewName + getSuffix()); |
12 | |
13 | String contentType = getContentType(); |
14 | if (contentType != null) { |
15 | //设置ContentType |
16 | view.setContentType(contentType); |
17 | } |
18 | |
19 | view.setRequestContextAttribute(getRequestContextAttribute()); |
20 | view.setAttributesMap(getAttributesMap()); |
21 | |
22 | Boolean exposePathVariables = getExposePathVariables(); |
23 | if (exposePathVariables != null) { |
24 | view.setExposePathVariables(exposePathVariables); |
25 | } |
26 | Boolean exposeContextBeansAsAttributes = getExposeContextBeansAsAttributes(); |
27 | if (exposeContextBeansAsAttributes != null) { |
28 | view.setExposeContextBeansAsAttributes(exposeContextBeansAsAttributes); |
29 | } |
30 | String[] exposedContextBeanNames = getExposedContextBeanNames(); |
31 | if (exposedContextBeanNames != null) { |
32 | view.setExposedContextBeanNames(exposedContextBeanNames); |
33 | } |
34 | |
35 | return view; |
36 | } |
- ,我们发现对于InternalResourceViewResolver 所提供的解析功能主要考虑到了几个方面的处理。
- 基于效率的考虑,提供了缓存的支持。
- 提供了对redirect:xx 和forward:xx 前缀的支持。
- 添加了前缀及后缀,并向View 中加入了必需的属性设置。
页面跳转
页面跳转当通过viewName 解析到对应的View 后,就可以进一步地处理跳转逻辑了。
1 | //AbstractView |
2 |
|
3 | public void render(Map<String, ?> model, HttpServletRequest request, HttpServletResponse response) throws Exception { |
4 | if (logger.isTraceEnabled()) { |
5 | logger.trace("Rendering view with name '" + this.beanName + "' with model " + model + |
6 | " and static attributes " + this.staticAttributes); |
7 | } |
8 | |
9 | Map<String, Object> mergedModel = createMergedOutputModel(model, request, response); |
10 | prepareResponse(request, response); |
11 | renderMergedOutputModel(mergedModel, getRequestToExpose(request), response); |
12 | } |
在引导示例中,我们了解到对于ModelView 的使用,可以将一些属性直接放入其中, 然后在页面上直接通过JSTL 语法或者原始的request 获取。这是一个很方便也很神奇的功能。但是实现却并不复杂,无非是把我们将要用到的属性放入request 中,以便在其他地方可以直接调用,而解析这些属性的工作就是在createMergedOutputModel 函数中完成的。
1 | ////AbstractView |
2 | protected Map<String, Object> createMergedOutputModel(Map<String, ?> model, HttpServletRequest request, |
3 | HttpServletResponse response) { |
4 | |
5 | "unchecked") ( |
6 | Map<String, Object> pathVars = (this.exposePathVariables ? |
7 | (Map<String, Object>) request.getAttribute(View.PATH_VARIABLES) : null); |
8 | |
9 | // Consolidate static and dynamic model attributes. |
10 | int size = this.staticAttributes.size(); |
11 | size += (model != null ? model.size() : 0); |
12 | size += (pathVars != null ? pathVars.size() : 0); |
13 | |
14 | Map<String, Object> mergedModel = new LinkedHashMap<String, Object>(size); |
15 | mergedModel.putAll(this.staticAttributes); |
16 | if (pathVars != null) { |
17 | mergedModel.putAll(pathVars); |
18 | } |
19 | if (model != null) { |
20 | mergedModel.putAll(model); |
21 | } |
22 | |
23 | // Expose RequestContext? |
24 | if (this.requestContextAttribute != null) { |
25 | mergedModel.put(this.requestContextAttribute, createRequestContext(request, response, mergedModel)); |
26 | } |
27 | |
28 | return mergedModel; |
29 | } |
30 | //页面跳转,抽象的方法,依据不同的页面类型选择不同的页面 |
31 | protected abstract void renderMergedOutputModel( |
32 | Map<String, Object> model, HttpServletRequest request, HttpServletResponse response) throws Exception; |
以 InternalResourceView 为例
1 |
|
2 | protected void renderMergedOutputModel( |
3 | Map<String, Object> model, HttpServletRequest request, HttpServletResponse response) throws Exception { |
4 | //将model 中的数据以属性的方式设置到request 中 |
5 | // Expose the model object as request attributes. |
6 | exposeModelAsRequestAttributes(model, request); |
7 | |
8 | // Expose helpers as request attributes, if any. |
9 | exposeHelpers(request); |
10 | |
11 | // Determine the path for the request dispatcher. |
12 | String dispatcherPath = prepareForRendering(request, response); |
13 | |
14 | // Obtain a RequestDispatcher for the target resource (typically a JSP). |
15 | RequestDispatcher rd = getRequestDispatcher(request, dispatcherPath); |
16 | if (rd == null) { |
17 | throw new ServletException("Could not get RequestDispatcher for [" + getUrl() + |
18 | "]: Check that the corresponding file exists within your web application archive!"); |
19 | } |
20 | |
21 | // If already included or response already committed, perform include, else forward. |
22 | if (useInclude(request, response)) { |
23 | response.setContentType(getContentType()); |
24 | if (logger.isDebugEnabled()) { |
25 | logger.debug("Including resource [" + getUrl() + "] in InternalResourceView '" + getBeanName() + "'"); |
26 | } |
27 | rd.include(request, response); |
28 | } |
29 | |
30 | else { |
31 | // Note: The forwarded resource is supposed to determine the content type itself. |
32 | if (logger.isDebugEnabled()) { |
33 | logger.debug("Forwarding to resource [" + getUrl() + "] in InternalResourceView '" + getBeanName() + "'"); |
34 | } |
35 | rd.forward(request, response); |
36 | } |
37 | } |
总结
Spring Web MVC处理请求的流程
步骤:
1、 首先用户发送请求————>前端控制器,前端控制器根据请求信息(如URL)来决定选择哪一个页面控制器进行处理并把请求委托给它,即以前的控制器的控制逻辑部分;图中的1、2步骤;
2、 页面控制器接收到请求后,进行功能处理,首先需要收集和绑定请求参数到一个对象,这个对象在Spring Web MVC中叫命令对象,并进行验证,然后将命令对象委托给业务对象进行处理;处理完毕后返回一个ModelAndView(模型数据和逻辑视图名);图中的3、4、5步骤;
3、 前端控制器收回控制权,然后根据返回的逻辑视图名,选择相应的视图进行渲染,并把模型数据传入以便视图渲染;图中的步骤6、7;
4、 前端控制器再次收回控制权,将响应返回给用户,图中的步骤8;至此整个结束。
springmvc 的核心架构
步骤:
1、 首先用户发送请求——>DispatcherServlet,前端控制器收到请求后自己不进行处理,而是委托给其他的解析器进行处理,作为统一访问点,进行全局的流程控制;
2、 DispatcherServlet——>HandlerMapping, HandlerMapping将会把请求映射为HandlerExecutionChain对象(包含一个Handler处理器(页面控制器)对象、多个HandlerInterceptor拦截器)对象,通过这种策略模式,很容易添加新的映射策略;
3、 DispatcherServlet——>HandlerAdapter,HandlerAdapter将会把处理器包装为适配器,从而支持多种类型的处理器,即适配器设计模式的应用,从而很容易支持很多类型的处理器;
4、 HandlerAdapter——>处理器功能处理方法的调用,HandlerAdapter将会根据适配的结果调用真正的处理器的功能处理方法,完成功能处理;并返回一个ModelAndView对象(包含模型数据、逻辑视图名);
5、 ModelAndView的逻辑视图名——> ViewResolver, ViewResolver将把逻辑视图名解析为具体的View,通过这种策略模式,很容易更换其他视图技术;
6、 View——>渲染,View会根据传进来的Model模型数据进行渲染,此处的Model实际是一个Map数据结构,因此很容易支持其他视图技术;
7、返回控制权给DispatcherServlet,由DispatcherServlet返回响应给用户,到此一个流程结束。