Merge remote-tracking branch 'other_master/master'

pull/16/head
huifer 5 years ago
commit 3424c4e2d5

@ -9,7 +9,7 @@
有被“读过哪些知名的开源项目源码?”这种问题所困扰过吗?加入我们,一起通读互联网公司主流框架及中间件源码,成为强大的“源码猎人”,目前开放的有 Spring 系列框架、Mybatis 框架、Netty 框架及Redis中间件等让我们一起开拓新的领地揭开这些源码的神秘面纱。本项目主要用于记录框架及中间件源码的阅读经验、个人理解及解析希望能够使阅读源码变成一件更简单有趣且有价值的事情抽空更新中...(如果本项目对您有帮助请watch、star、fork 素质三连一波,鼓励一下作者,谢谢)
## spring系列
## Spring系列
### IoC容器
- [BeanDefinition 的资源定位过程](/docs/Spring/IoC/1、BeanDefinition的资源定位过程.md)
- [将 bean 解析封装成 BeanDefinition](/docs/Spring/IoC/2、将bean解析封装成BeanDefinition.md)
@ -21,6 +21,9 @@
- [JDK 动态代理的实现原理解析](/docs/Spring/AOP/JDK动态代理的实现原理解析.md)
### SpringMVC
- [温习一下servlet](/docs/Spring/SpringMVC/温习一下servlet.md)
- [IoC 容器在 Web 环境中的启动](/docs/Spring/SpringMVC/IoC容器在Web环境中的启动.md)
- [SpringMVC 的设计与实现](/docs/Spring/SpringMVC/SpringMVC的设计与实现.md)
### SpringJDBC

@ -47,7 +47,7 @@ public interface Cache {
```
如下图所示Cache接口的实现类有很多但大部分都是装饰器只有PerpetualCache提供了Cache 接口的基本实现。
![avator](/images/mybatis/Cache组件.png)
![avatar](/images/mybatis/Cache组件.png)
### 1.1 PerpetualCache
PerpetualCachePerpetual永恒的持续的在缓存模块中扮演着被装饰的角色其实现比较简单底层使用HashMap记录缓存项也是通过该HashMap对象的方法实现的Cache接口中定义的相应方法。
```java

@ -0,0 +1,341 @@
## 1 Web环境中的SpringMVC
在Web环境中SpringMVC是建立在IoC容器基础上的。了解SpringMVC首先要了解Spring的IoC容器是如何在Web环境中被载人并起作用的。
Spring的IoC是一个独立模块它并不直接在Web容器中发挥作用如果要在Web环境中使用IoC容器需要Spring为IoC设计一个启动过程把IoC容器导入并在Web容器中建立起来。具体说来这个启动过程是和Web容器的启动过程集成在一起的。在这个过程中一方面处理Web容器的启动另一方面通过设计特定的Web容器拦截器将IoC容器载人到Web环境中来并将其初始化。在这个过程建立完成以后IoC容器才能正常工作而SpringMVC是建立在IoC容器的基础上的这样才能建立起MVC框架的运行机制从而响应从Web容器传递的HTTP请求。
下面以Tomcat作为Web容器的例子进行分析。在Tomcat中web.xml是应用的部署描述文件。在web.xml中常常经常能看到与Spring相关的部署描述。
```xml
<servlet>
<servlet-name>sample</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<load-on-startup>6</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>sample</servlet-name>
<url-pattern>/*</url-pattern>
</servlet-mapping>
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/applicationContext.xml</param-value>
</context-param>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
```
web.xml是SpringMVC与Tomcat的接口部分。这个部署描述文件中首先定义了一个Servlet对象它是SpringMVC的DispatcherServlet。这个DispatcherServlet是MVC中很重要的一个类起着分发请求的作用。
同时在部署描述中还为这个DispatcherServlet定义了对应的URL映射以指定这个Servlet需要处理的HTTP请求范围。context-param参数用来指定IoC容器读取Bean的XML文件的路径在这里这个配置文件被定义为WEB-INF/applicationContext.xml。其中可以看到Spring应用的Bean配置。
最后作为Spring MVC的启动类ContextLoaderListener被定义为一个监听器这个监听器是与Web服务器的生命周期相关联的由ContextLoaderListener监听器负责完成 IoC容器在Web环境中的启动工作。
DispatchServlet和ContextLoaderListener提供了在Web容器中对Spring的接口也就是说这些接口与Web容器耦合是通过ServletContext来实现的ServletContext是容器和应用沟通的桥梁从一定程度上讲ServletContext就是servlet规范的体现。这个ServletContext为Spring的IoC容器提供了一个宿主环境在宿主环境中Spring MVC建立起一个IoC容器的体系。这个IoC容器体系是通过ContextLoaderListener的初始化来建立的在建立IoC容器体系后把DispatchServlet作为Spring MVC处理Web请求的转发器建立起来从而完成响应HTTP请求的准备。有了这些基本配置建立在IoC容器基础上的SpringMVC就可以正常地发挥作用了。下面我们看一下loC容器在Web容器中的启动代码实现。
## 2 IoC容器启动的基本过程
IoC容器的启动过程就是建立上下文的过程该上下文是与ServletContext相伴而生的同时也是IoC容器在Web应用环境中的具体表现之一。由ContextLoaderListener启动的上下文为根上下文。在根上下文的基础上还有一个与Web MVC相关的上下文用来保存控制器(DispatcherServlet)需要的MVC对象作为根上下文的子上下文构成一个层次化的上下文体系。在Web容器中启动Spring应用程序时首先建立根上下文然后建立这个上下文体系这个上下文体系的建立是由ContextLoder来完成的其UML时序图如下图所示。
![avatar](/images/springMVC/Web容器启动spring应用程序过程图.png)
在web.xml中已经配置了ContextLoaderListener它是Spring提供的类是为在Web容器中建立IoC容器服务的它实现了ServletContextListener接口这个接口是在Servlet API中定义的提供了与Servlet生命周期结合的回调比如上下文初始化contextInitialized()方法和上下文销毁contextDestroyed()方法。而在Web容器中建立WebApplicationContext的过程是在contextInitialized()方法中完成的。另外ContextLoaderListener还继承了ContextLoader具体的载入IoC容器的过程是由ContextLoader来完成的。
在ContextLoader中完成了两个IoC容器建立的基本过程一个是在Web容器中建立起双亲IoC容器另一个是生成相应的WebApplicationContext并将其初始化。
## 3 Web容器中的上下文设计
先从Web容器中的上下文入手看看Web环境中的上下文设置有哪些特别之处然后再
到ContextLoaderListener中去了解整个容器启动的过程。为了方便在Web环境中使用IoC容器
Spring为Web应用提供了上下文的扩展接口WebApplicationContext来满足启动过程的需要其继承关系如下图所示。
![avatar](/images/springMVC/WebApplicationContext接口的类继承关系.png)
在这个类继承关系中可以从熟悉的XmlWebApplicationContext入手来了解它的接口实现。在接口设计中最后是通过ApplicationContex接口与BeanFactory接口对接的而对于具体的功能实现很多都是封装在其基类AbstractRefreshableWebApplicationContext中完成的。
同样在源代码中也可以分析出类似的继承关系在WebApplicationContext中可以看到相关的常量设计比如ROOT_ WEB_ APPLICATION_CONTEXT_ATTRIBUTE等这个常量是用来索引在ServletContext中存储的根上下文的。这个接口类定义的接口方法比较简单在这个接口中定义了一
个getServletContext方法通过这个方法可以得到当前Web容器的Servlet上下文环境通过
这个方法相当于提供了一个Web容器级别的全局环境。
```java
public interface WebApplicationContext extends ApplicationContext {
/**
* 该常量用于在ServletContext中存取根上下文
*/
String ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE = WebApplicationContext.class.getName() + ".ROOT";
/**
* 对于WebApplicationContext来说需要得到Web容器的ServletContext
*/
ServletContext getServletContext();
}
```
在启动过程中Spring会使用一个默认的WebApplicationContext实现作为IoC容器这个默认使用的IoC容器就是XmlWebApplicationContext它继承了ApplicationContext在ApplicationContext的基础上增加了对Web环境和XML配置定义的处理。在XmlWebApplicationContext的初始化过程中Web容器中的IoC容器被建立起来从而在Web容器中建立起整个Spring应用。与前面博文中分析的IoC容器的初始化一样这个过程也有loadBeanDefinition对BeanDefinition的载入。在Web环境中对定位BeanDefinition的Resource有特别的要求这个要求的处理体现在对getDefaultConfigLocations方法的处理中。这里使用了默认的BeanDefinition的配置路径这个路径在XmlWebApplicationContext中作为一个常量定义好了即/WEB-INF/applicationContext.xml。
```java
public class XmlWebApplicationContext extends AbstractRefreshableWebApplicationContext {
/** 若不指定其它文件spring默认从"/WEB-INF/applicationContext.xml"目录文件 初始化IoC容器 */
public static final String DEFAULT_CONFIG_LOCATION = "/WEB-INF/applicationContext.xml";
/** 默认的配置文件在 /WEB-INF/ 目录下 */
public static final String DEFAULT_CONFIG_LOCATION_PREFIX = "/WEB-INF/";
/** 默认的配置文件后缀名为.xml */
public static final String DEFAULT_CONFIG_LOCATION_SUFFIX = ".xml";
/**
* 此加载过程在 容器refresh()时启动
*/
@Override
protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws BeansException, IOException {
// 使用XmlBeanDefinitionReader对指定的BeanFactory进行解析
XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory);
// 初始化beanDefinitionReader的属性其中设置ResourceLoader是因为
// XmlBeanDefinitionReader是DefaultResource的子类所有这里同样会使用
// DefaultResourceLoader来定位BeanDefinition
beanDefinitionReader.setEnvironment(this.getEnvironment());
beanDefinitionReader.setResourceLoader(this);
beanDefinitionReader.setEntityResolver(new ResourceEntityResolver(this));
// 该方法是一个空实现
initBeanDefinitionReader(beanDefinitionReader);
// 使用初始化完成的beanDefinitionReader来加载BeanDefinitions
loadBeanDefinitions(beanDefinitionReader);
}
protected void initBeanDefinitionReader(XmlBeanDefinitionReader beanDefinitionReader) {
}
/**
* 获取所有的配置文件然后一个一个载入BeanDefinition
*/
protected void loadBeanDefinitions(XmlBeanDefinitionReader reader) throws IOException {
String[] configLocations = getConfigLocations();
if (configLocations != null) {
for (String configLocation : configLocations) {
reader.loadBeanDefinitions(configLocation);
}
}
}
/**
* 获取默认路径"/WEB-INF/***.xml"下的配置文件,
* 或者获取"/WEB-INF/applicationContext.xml"配置文件
*/
@Override
protected String[] getDefaultConfigLocations() {
if (getNamespace() != null) {
return new String[] {DEFAULT_CONFIG_LOCATION_PREFIX + getNamespace() + DEFAULT_CONFIG_LOCATION_SUFFIX};
}
else {
return new String[] {DEFAULT_CONFIG_LOCATION};
}
}
}
```
从上面的代码中可以看到在XmlWebApplicationContext中基本的上下文功能都已经通过类的继承获得这里需要处理的是如何获取Bean定义信息在这里就转化为如何在Web容器环境中获得Bean定义信息。在获得Bean定义信息之后后面的过程基本上就和前面分析的XmlFileSystemBeanFactory一样是通过XmlBeanDefinitionReader来载人Bean定义信息的最终完成整个上下文的初始化过程。
## 4 ContextLoader的设计与实现
对于Spring承载的Web应用而言可以指定在Web应用程序启动时载入IoC容器或者称为WebApplicationContext。这个功能是由ContextLoaderListener来完成的它是在Web容器中配置的监听器会监听Web容器的启动然后载入IoC容器。这个ContextLoaderListener通过使用ContextLoader来完成实际的WebApplicationContext也就是IoC容器的初始化工作。这个ContextLoader就像Spring应用程序在Web容器中的启动器。这个启动过程是在Web容器中发生的所以需要根据Web容器部署的要求来定义ContextLoader相关的配置在概述中已经看到了这里就不重复了。
为了了解IoC容器在Web容器中的启动原理这里对启动器ContextLoaderListener的实现进行分析。**这个监听器是启动根IoC容器并把它载入到Web容器的主要功能模块也是整个Spring Web应用加载IoC的第一个地方**。从加载过程可以看到首先从Servlet事件中得到ServletContext然后可以读取配置在web.xml中的各个相关的属性值接着ContextLoader会实例化WebApplicationContext并完成其载人和初始化过程。这个被初始化的第一个上下文作为根上下文而存在这个根上下文载入后被绑定到Web应用程序的ServletContext上。任何需要访问根上下文的应用程序代码都可以从WebApplicationContextUtils类的静态方法中得到。
下面分析具体的根上下文的载人过程。在ContextLoaderListener中实现的是**ServletContextListener接口这个接口里的函数会结合Web容器的生命周期被调用**。因为ServletContextListener是ServletContext的监听者如果ServletContext发生变化会触发出相应的事件而监听器一直在对这些事件进行监听如果接收到了监听的事件就会做出预先设计好的响应动作。由于ServletContext的变化而触发的监听器的响应具体包括在服务器启动时ServletContext被创建的时候服务器关闭时ServletContext将被销毁的时候等。对应这些事件及Web容器状态的变化在监听器中定义了对应的事件响应的回调方法。比如在服务器启动时ServletContextListener的contextInitialized()方法被调用服务器将要关闭时ServletContextListener的contextDestroyed()方法被调用。了解了Web容器中监听器的工作原理下面看看服务器启动时 ContextLoaderListener的调用完成了什么。在这个初始化回调中创建了ContextLoader同时会利用创建出来的ContextLoader来完成IoC容器的初始化。
```java
public class ContextLoaderListener extends ContextLoader implements ServletContextListener {
private ContextLoader contextLoader;
/**
* 启动web应用的 根上下文
*/
public void contextInitialized(ServletContextEvent event) {
// 由于本类直接继承了ContextLoader所以能直接使用ContextLoader来初始化IoC容器
this.contextLoader = createContextLoader();
if (this.contextLoader == null) {
this.contextLoader = this;
}
// 具体的初始化工作交给ContextLoader完成
this.contextLoader.initWebApplicationContext(event.getServletContext());
}
}
public class ContextLoader {
public static final String CONTEXT_CLASS_PARAM = "contextClass";
public static final String CONTEXT_ID_PARAM = "contextId";
public static final String CONTEXT_INITIALIZER_CLASSES_PARAM = "contextInitializerClasses";
public static final String CONFIG_LOCATION_PARAM = "contextConfigLocation";
public static final String LOCATOR_FACTORY_SELECTOR_PARAM = "locatorFactorySelector";
public static final String LOCATOR_FACTORY_KEY_PARAM = "parentContextKey";
private static final String DEFAULT_STRATEGIES_PATH = "ContextLoader.properties";
private static final Properties defaultStrategies;
static {
// Load default strategy implementations from properties file.
// This is currently strictly internal and not meant to be customized
// by application developers.
try {
ClassPathResource resource = new ClassPathResource(DEFAULT_STRATEGIES_PATH, ContextLoader.class);
defaultStrategies = PropertiesLoaderUtils.loadProperties(resource);
}
catch (IOException ex) {
throw new IllegalStateException("Could not load 'ContextLoader.properties': " + ex.getMessage());
}
}
/**
* 由ContextLoader完成根上下文在Web容器中的创建。这个根上下文是作为Web容器中唯一的实例而存在的
* 根上下文创建成功后 会被存到Web容器的ServletContext中供需要时使用。存取这个根上下文的路径是由
* Spring预先设置好的在WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE中进行了定义
*/
public WebApplicationContext initWebApplicationContext(ServletContext servletContext) {
// 如果ServletContext中已经包含了根上下文则抛出异常
if (servletContext.getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE) != null) {
throw new IllegalStateException(
"Cannot initialize context because there is already a root application context present - " +
"check whether you have multiple ContextLoader* definitions in your web.xml!");
}
Log logger = LogFactory.getLog(ContextLoader.class);
servletContext.log("Initializing Spring root WebApplicationContext");
if (logger.isInfoEnabled()) {
logger.info("Root WebApplicationContext: initialization started");
}
long startTime = System.currentTimeMillis();
try {
if (this.context == null) {
// 这里创建在ServletContext中存储的根上下文
this.context = createWebApplicationContext(servletContext);
}
if (this.context instanceof ConfigurableWebApplicationContext) {
ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) this.context;
if (!cwac.isActive()) {
if (cwac.getParent() == null) {
// 载入根上下文的 双亲上下文
ApplicationContext parent = loadParentContext(servletContext);
cwac.setParent(parent);
}
// 配置 并且初始化IoC容器看到Refresh应该能想到AbstractApplicationContext
// 中的refresh()方法猜到它是前面介绍的IoC容器的初始化入口
configureAndRefreshWebApplicationContext(cwac, servletContext);
}
}
// 将上面创建的WebApplicationContext实例 存到ServletContext中注意同时被存入的常量
// ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE以后的应用都会根据这个属性获取根上下文
servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context);
ClassLoader ccl = Thread.currentThread().getContextClassLoader();
if (ccl == ContextLoader.class.getClassLoader()) {
currentContext = this.context;
}
else if (ccl != null) {
currentContextPerThread.put(ccl, this.context);
}
if (logger.isDebugEnabled()) {
logger.debug("Published root WebApplicationContext as ServletContext attribute with name [" +
WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE + "]");
}
if (logger.isInfoEnabled()) {
long elapsedTime = System.currentTimeMillis() - startTime;
logger.info("Root WebApplicationContext: initialization completed in " + elapsedTime + " ms");
}
return this.context;
}
catch (RuntimeException ex) {
logger.error("Context initialization failed", ex);
servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, ex);
throw ex;
}
catch (Error err) {
logger.error("Context initialization failed", err);
servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, err);
throw err;
}
}
/**
* 创建WebApplicationContext的实例化对象
*/
protected WebApplicationContext createWebApplicationContext(ServletContext sc) {
// 判断使用什么样的类在Web容器中作为IoC容器
Class<?> contextClass = determineContextClass(sc);
if (!ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass)) {
throw new ApplicationContextException("Custom context class [" + contextClass.getName() +
"] is not of type [" + ConfigurableWebApplicationContext.class.getName() + "]");
}
// 直接实例化需要产生的IoC容器
return (ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);
}
/**
* 在确定使用何种IoC容器的过程中可以看到应用可以在部署描述符中指定使用什么样的IoC容器
* 这个指定操作是通过CONTEXT_ CLASS_ PARAM参数的设置完成的。如果没有指定特定的IoC容器
* 将使用默认的IoC容器也就是XmlWebApplicationContext对象作为在Web环境中使用的IoC容器。
*/
protected Class<?> determineContextClass(ServletContext servletContext) {
// 获取servletContext中对CONTEXT_CLASS_PARAMcontextClass参数的配置
String contextClassName = servletContext.getInitParameter(CONTEXT_CLASS_PARAM);
if (contextClassName != null) {
try {
// 获取配置的contextClassName对应的clazz对象
return ClassUtils.forName(contextClassName, ClassUtils.getDefaultClassLoader());
}
catch (ClassNotFoundException ex) {
throw new ApplicationContextException(
"Failed to load custom context class [" + contextClassName + "]", ex);
}
}
else {
// 如果没有配置CONTEXT_CLASS_PARAM则使用默认的ContextClass
contextClassName = defaultStrategies.getProperty(WebApplicationContext.class.getName());
try {
return ClassUtils.forName(contextClassName, ContextLoader.class.getClassLoader());
}
catch (ClassNotFoundException ex) {
throw new ApplicationContextException(
"Failed to load default context class [" + contextClassName + "]", ex);
}
}
}
protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac, ServletContext sc) {
if (ObjectUtils.identityToString(wac).equals(wac.getId())) {
// The application context id is still set to its original default value
// -> assign a more useful id based on available information
String idParam = sc.getInitParameter(CONTEXT_ID_PARAM);
if (idParam != null) {
wac.setId(idParam);
}
else {
// Generate default id...
if (sc.getMajorVersion() == 2 && sc.getMinorVersion() < 5) {
// Servlet <= 2.4: resort to name specified in web.xml, if any.
wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX +
ObjectUtils.getDisplayString(sc.getServletContextName()));
}
else {
wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX +
ObjectUtils.getDisplayString(sc.getContextPath()));
}
}
}
// 设置ServletContext 及配置文件的位置参数
wac.setServletContext(sc);
String initParameter = sc.getInitParameter(CONFIG_LOCATION_PARAM);
if (initParameter != null) {
wac.setConfigLocation(initParameter);
}
customizeContext(sc, wac);
// IoC容器初始化的入口想不起来的把前面IoC容器初始化的博文再读10遍
wac.refresh();
}
}
```
这就是IoC容器在Web容器中的启动过程与 应用中启动IoC容器的方式相类似所不同的是这里需要考虑Web容器的环境特点比如各种参数的设置IoC容器与Web容器ServletContext的结合等。在初始化这个上下文以后该上下文会被存储到SevletContext中这样就建立了一个全局的关于整个应用的上下文。同时在启动Spring MVC时我们还会看到这个上下文被以后的DispatcherServlet在进行自己持有的上下文的初始化时设置为DispatcherServlet自带的上下文的双亲上下文。

@ -0,0 +1,767 @@
## 1 SpringMVC应用场景
在使用SpringMVC时除了要在web.xml中配置ContextLoaderListener外还要对DispatcherServlet进行配置。作为一个Servlet这个DispatcherServlet实现的是Sun的J2EE核心模式中的前端控制器模式(Front Controller) 作为一个前端控制器所有的Web请求都需要通过它来处理进行转发、匹配、数据处理后并转由页面进行展现因此这个DispatcerServlet可以看成是Spring MVC实现中最为核心的部分。
在Spring MVC中对于不同的Web请求的映射需求Spring MVC提供了不同的HandlerMapping的实现可以让应用开发选取不同的映射策略。DispatcherSevlet默认了BeanNameUrlHandlerMapping作为映射策略实现。除了映射策略可以定制外Spring MVC提供了各种Controller的实现来供应用扩展和使用以应对不同的控制器使用场景这些Controller控制器需要实现handleRequest接口方法并返回ModelAndView对象。Spring MVC还提供了各种视图实现比如常用的JSP视图。除此之外Spring MVC还提供了拦截器供应用使用允许应用对Web请求进行拦截以及前置处理和后置处理。
## 2 SpringMVC设计概览
在完成对ContextLoaderListener的初始化以后Web容器开始初始化DispatcherServlet这个初始化的启动与在web.xml中对载入次序的定义有关。DispatcherServlet会建立自己的上下文来持有Spring MVC的Bean对象在建立这个自己持有的IoC容器时会**从ServletContext中得到根上下文**作为DispatcherServlet持有上下文的双亲上下文。有了这个根上下文再对自己持有的上下文进行初始化最后把自己持有的这个上下文保存到ServletContext中供以后检索和使用。
为了解这个过程可以从DispatcherServlet的父类FrameworkServlet的代码入手去探寻DispatcherServlet的启动过程它同时也是SpringMVC的启动过程。ApplicationContext的创建过程和ContextLoader创建根上下文的过程有许多类似的地方。下面来看一下这个DispatcherServlet类的继承关系。
![avatar](/images/springMVC/DispatcherServlet的继承关系.png)
DispatcherServlet通过继承FrameworkServlet和HttpServletBean而继承了HttpServlet通过使用Servlet API来对HTTP请求进行响应成为Spring MVC的前端处理器同时成为MVC模块与Web容器集成的处理前端。
DispatcherServlet的工作大致可以分为两个部分一个是初始化部分由initServletBean()启动通过initWebApplicationContext()方法最终调用DispatcherServlet的initStrategies()方法在这个方法里DispatcherServlet对MVC模块的其他部分进行了初始化比如handlerMapping、ViewResolver等另一个是对HTTP请求进行响应作为一个ServletWeb容器会调用Servlet的doGet()和doPost()方法在经过FrameworkServlet的processRequest()简单处理后会调用DispatcherServlet的doService()方法在这个方法调用中封装了doDispatch()这个doDispatch()是Dispatcher实现MVC模式的主要部分下图为DispatcherServlet的处理过程时序图。
![avatar](/images/springMVC/DispatcherServlet的处理过程.png)
## 3 DispatcherServlet的启动和初始化
前面大致描述了Spring MVC的工作流程下面看一下DispatcherServlet的启动和初始化的代码设计及实现。
作为ServletDispatcherServlet的启动与Servlet的启动过程是相联系的。在Servlet的初始化过程中Servlet的init()方法会被调用以进行初始化DispatcherServlet的基类HttpServletBean实现了该方法。在初始化开始时需要读取配置在ServletContext中的Bean属性参数这些属性参数设置在web.xml的Web容器初始化参数中。使用编程式的方式来设置这些Bean属性在这里可以看到对PropertyValues和BeanWrapper的使用。对于这些和依赖注人相关的类的使用在分析IoC容器的初始化时尤其是在依赖注入实现分析时有过“亲密接触”。只是这里的依赖注人是与Web容器初始化相关的。
接着会执行DispatcherServlet持有的IoC容器的初始化过程在这个初始化过程中一个新的上下文被建立起来这个DispatcherServlet持有的上下文被设置为根上下文的子上下文。一个Web应用中可以容纳多个Servlet存在与此相对应对于应用在Web容器中的上下体系一个根上下文可以作为许多Servlet上下文的双亲上下文。了解IoC工作原理的读者知道在向IoC容器getBean()时IoC容器会首先向其双亲上下文去getBean()也就是说在根上下文中定义的Bean是可以被各个Servlet持有的上下文得到和共享的。DispatcherServlet持有的 上下文被建立起来以后也需要和其他IoC容器一样完成初始化这个初始化也是通过refresh()方法来完成的。最后DispatcherServlet给这个自己持有的上下文命名并把它设置到Web容器的上下文中这个名称和在web.xml中设置的DispatcherServlet的Servlet名称有关从而保证了这个上下文在Web环境上下文体系中的唯一性。
```java
public abstract class HttpServletBean extends HttpServlet implements EnvironmentCapable, EnvironmentAware {
public final void init() throws ServletException {
if (logger.isDebugEnabled()) {
logger.debug("Initializing servlet '" + getServletName() + "'");
}
// 获取Servlet的初始化参数对bean属性进行配置
try {
PropertyValues pvs = new ServletConfigPropertyValues(getServletConfig(), this.requiredProperties);
BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this);
ResourceLoader resourceLoader = new ServletContextResourceLoader(getServletContext());
bw.registerCustomEditor(Resource.class, new ResourceEditor(resourceLoader, getEnvironment()));
initBeanWrapper(bw);
bw.setPropertyValues(pvs, true);
}
catch (BeansException ex) {
logger.error("Failed to set bean properties on servlet '" + getServletName() + "'", ex);
throw ex;
}
// 这个方法会调用子类的实现,进行具体的初始化
initServletBean();
if (logger.isDebugEnabled()) {
logger.debug("Servlet '" + getServletName() + "' configured successfully");
}
}
}
public abstract class FrameworkServlet extends HttpServletBean {
/** 此servlet的WebApplicationContext */
private WebApplicationContext webApplicationContext;
/** 我们是否应该将当前Servlet的上下文webApplicationContext设为ServletContext的属性 */
private boolean publishContext = true;
public FrameworkServlet() {
}
public FrameworkServlet(WebApplicationContext webApplicationContext) {
this.webApplicationContext = webApplicationContext;
}
/**
* 覆盖了父类HttpServletBean的空实现
*/
@Override
protected final void initServletBean() throws ServletException {
getServletContext().log("Initializing Spring FrameworkServlet '" + getServletName() + "'");
if (this.logger.isInfoEnabled()) {
this.logger.info("FrameworkServlet '" + getServletName() + "': initialization started");
}
long startTime = System.currentTimeMillis();
try {
// 初始化上下文
this.webApplicationContext = initWebApplicationContext();
initFrameworkServlet();
}
catch (ServletException ex) {
this.logger.error("Context initialization failed", ex);
throw ex;
}
catch (RuntimeException ex) {
this.logger.error("Context initialization failed", ex);
throw ex;
}
if (this.logger.isInfoEnabled()) {
long elapsedTime = System.currentTimeMillis() - startTime;
this.logger.info("FrameworkServlet '" + getServletName() + "': initialization completed in " +
elapsedTime + " ms");
}
}
/**
* 为这个Servlet初始化一个公共的WebApplicationContext实例
*/
protected WebApplicationContext initWebApplicationContext() {
// 获取 根上下文 作为当前MVC上下文的双亲上下文这个根上下文保存在ServletContext中
WebApplicationContext rootContext =
WebApplicationContextUtils.getWebApplicationContext(getServletContext());
WebApplicationContext wac = null;
if (this.webApplicationContext != null) {
// 可以在本对象被构造时注入一个webApplicationContext实例
wac = this.webApplicationContext;
if (wac instanceof ConfigurableWebApplicationContext) {
ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) wac;
if (!cwac.isActive()) {
// 上下文尚未刷新 -> 提供诸如设置父上下文、设置应用程序上下文id等服务
if (cwac.getParent() == null) {
// 上下文实例在没有显式父实例的情况下被注入 ->
// 将根上下文(如果有的话;可以为空)设置为父上下文
cwac.setParent(rootContext);
}
configureAndRefreshWebApplicationContext(cwac);
}
}
}
if (wac == null) {
// 在本对象被构造时没有注入上下文实例 ->
// 查看是否已在servlet上下文中注册了上下文实例。
// 如果存在一个,则假定父上下文(如果有的话)已经被设置,
// 并且用户已经执行了任何初始化例如设置上下文ID
wac = findWebApplicationContext();
}
if (wac == null) {
// 没有为此servlet定义上下文实例 -> 创建本地实例
wac = createWebApplicationContext(rootContext);
}
if (!this.refreshEventReceived) {
// 上下文 不是支持刷新的ConfigurableApplicationContext或者
// 在构造时注入的上下文已经完成刷新 -> 在此处手动触发onRefresh()方法
onRefresh(wac);
}
if (this.publishContext) {
// 把当前建立的上下文保存到ServletContext中使用的属性名是和当前servlet名相关的
String attrName = getServletContextAttributeName();
getServletContext().setAttribute(attrName, wac);
if (this.logger.isDebugEnabled()) {
this.logger.debug("Published WebApplicationContext of servlet '" + getServletName() +
"' as ServletContext attribute with name [" + attrName + "]");
}
}
return wac;
}
}
```
至此这个MVC的上下文就建立起来了具体取得根上下文的过程在WebApplicationContextUtils中实现。这个根上下文是ContextLoader设置到ServletContext中去的使用的属性是ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTEContextLoader还对这个IoC容器的Bean配置文件进行了设置默认的位置是在/WEB-INF/applicationContext.xml文件中。由于这个根上下文是DispatcherServlet建立的上下文的 双亲上下文所以根上下文中管理的Bean也可以被DispatcherServlet的上下文使用。通过getBean()向IoC容器获取Bean时容器会先到它的双亲IoC容器中获取。
```java
/**
* 这是一个封装了很多静态方法的抽象工具类,所以只能调用其静态方法,
* 不能对其进行实例化
*/
public abstract class WebApplicationContextUtils {
/**
* 使用了WebApplicationContext的ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE属性获取
* ServletContext中的根上下文这个属性代表的根上下文在ContextLoaderListener初始化的
* 过程中被建立
*/
public static WebApplicationContext getWebApplicationContext(ServletContext sc) {
return getWebApplicationContext(sc, WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE);
}
/**
* 查找此web应用程序的自定义WebApplicationContext
*/
public static WebApplicationContext getWebApplicationContext(ServletContext sc, String attrName) {
Assert.notNull(sc, "ServletContext must not be null");
Object attr = sc.getAttribute(attrName);
if (attr == null) {
return null;
}
if (attr instanceof RuntimeException) {
throw (RuntimeException) attr;
}
if (attr instanceof Error) {
throw (Error) attr;
}
if (attr instanceof Exception) {
throw new IllegalStateException((Exception) attr);
}
if (!(attr instanceof WebApplicationContext)) {
throw new IllegalStateException("Context attribute is not of type WebApplicationContext: " + attr);
}
return (WebApplicationContext) attr;
}
)
```
回到FrameworkServlet的实现中来看一下DispatcherServlet的上下文是怎样建立的这个建立过程与前面建立根上下文的过程非常类似。建立DispatcherServlet的上下文需要把根上下文作为参数传递给它。然后使用反射技术来实例化上下文对象并为它设置参数。根据默认的配置这个上下文对象也是XmlWebApplicationContext对象这个类型是在DEFAULT_CONTEXT_CLASS参数中设置好并允许BeanUtilis使用的。在实例化结束后需要为这个上下文对象设置好一些基本的配置这些配置包括它的双亲上下文、Bean配置文件的位置等。完成这些配置以后最后通过调用IoC容器的refresh()方法来完成IoC容器的最终初始化这和前面我们对IoC容器实现原理的分析中所看到的IoC容器初始化的过程是一致的。
```java
public abstract class FrameworkServlet extends HttpServletBean {
/**
* 为此servlet实例化一个WebApplicationContext可以是默认的XmlWebApplicationContext
* 也可以是用户设置的自定义Context上下文
*/
protected WebApplicationContext createWebApplicationContext(WebApplicationContext parent) {
return createWebApplicationContext((ApplicationContext) parent);
}
protected WebApplicationContext createWebApplicationContext(ApplicationContext parent) {
// 默认为XmlWebApplicationContext.class
Class<?> contextClass = getContextClass();
if (this.logger.isDebugEnabled()) {
this.logger.debug("Servlet with name '" + getServletName() +
"' will try to create custom WebApplicationContext context of class '" +
contextClass.getName() + "'" + ", using parent context [" + parent + "]");
}
if (!ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass)) {
throw new ApplicationContextException(
"Fatal initialization error in servlet with name '" + getServletName() +
"': custom WebApplicationContext class [" + contextClass.getName() +
"] is not of type ConfigurableWebApplicationContext");
}
// 实例化需要的上下文对象,并为其设置属性
ConfigurableWebApplicationContext wac =
(ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);
wac.setEnvironment(getEnvironment());
// 这里设置的 双亲上下文就是在ContextLoader中建立的根上下文
wac.setParent(parent);
wac.setConfigLocation(getContextConfigLocation());
// 配置并且刷新wac
configureAndRefreshWebApplicationContext(wac);
return wac;
}
protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac) {
if (ObjectUtils.identityToString(wac).equals(wac.getId())) {
// 应用程序上下文id仍设置为其原始默认值如果该id不为空的话
if (this.contextId != null) {
wac.setId(this.contextId);
}
else {
// 生成默认的id
ServletContext sc = getServletContext();
if (sc.getMajorVersion() == 2 && sc.getMinorVersion() < 5) {
// 当Servlet<=2.4如果有请使用web.xml中指定的名称。
String servletContextName = sc.getServletContextName();
if (servletContextName != null) {
wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX + servletContextName +
"." + getServletName());
}
else {
wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX + getServletName());
}
}
else {
// Servlet 2.5的getContextPath可用
wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX +
ObjectUtils.getDisplayString(sc.getContextPath()) + "/" + getServletName());
}
}
}
// 设置其它配置信息
wac.setServletContext(getServletContext());
wac.setServletConfig(getServletConfig());
wac.setNamespace(getNamespace());
wac.addApplicationListener(new SourceFilteringListener(wac, new ContextRefreshListener()));
// 在刷新上下文的任何情况下都将会调用wac环境的initPropertySources()方法。
// 在此处执行此方法以确保在刷新上下文之前servlet属性源已准备就绪
ConfigurableEnvironment env = wac.getEnvironment();
if (env instanceof ConfigurableWebEnvironment) {
((ConfigurableWebEnvironment)env).initPropertySources(getServletContext(), getServletConfig());
}
postProcessWebApplicationContext(wac);
applyInitializers(wac);
// IoC容器都是通过该方法完成 容器初始化的
wac.refresh();
}
}
```
这时候DispatcherServlet中的IoC容器已经建立起来了这个IoC容器是 根上下文 的子容器。如果要查找一个由DispatcherServlet所持有的IoC容器来管理的Bean系统会首先到 根上下文 中去查找。如果查找不到才会到DispatcherServlet所管理的IoC容器去进行查找这是由IoC容器getBean()的实现来决定的。通过一系列在Web容器中执行的动作在这个上下文体系建立和初始化完毕的基础上Spring MVC就可以发挥其作用了。下面来分析一下Spring MVC的具体实现。
在前面分析DispatchServlet的初始化过程中可以看到DispatchServlet持有一个以自己的Servlet名称命名的IoC容器。这个IoC容器是一个WebApplicationContext对象这个IoC容器建立起来以后意味着DispatcherServlet拥有自己的Bean定义空间这为使用各个独立的XML文件来配置MVC中各个Bean创造了条件。由于在初始化结束以后与Web容器相关的加载过程实际上已经完成了SpringMVC的具体实现和普通的Spring应用程序的实现并没有太大的差别。
在DispatcherServlet的初始化过程中以对HandlerMapping的初始化调用作为触发点了解SpringMVC模块初始化的方法调用关系。这个调用关系最初是由HttpServletBean的init()方法触发的这个HttpServletBean是HttpServlet的子类。接着会在HttpServletBean的子类FrameworkServlet中对IoC容器完成初始化在这个初始化方法中会调用DispatcherServlet的initStrategies()方法该方法包括对各种MVC框架的实现元素比如支持国际化的LocalResolver、支持request映射的HandlerMappings以及视图生成的ViewResolver等。由该方法启动整个Spring MVC框架的初始化。
```java
public class DispatcherServlet extends FrameworkServlet {
/**
* 初始化此servlet使用的策略对象。
* 可以在子类中重写以便初始化进一步的策略对象U8C
*/
protected void initStrategies(ApplicationContext context) {
// 请求解析
initMultipartResolver(context);
// 多语言,国际化
initLocaleResolver(context);
// 主题view层
initThemeResolver(context);
// 解析url和Method的对应关系
initHandlerMappings(context);
// 适配器匹配
initHandlerAdapters(context);
// 异常解析
initHandlerExceptionResolvers(context);
// 视图转发,根据视图名字匹配到一个具体模板
initRequestToViewNameTranslator(context);
// 解析模板中的内容
initViewResolvers(context);
initFlashMapManager(context);
}
}
```
对于具体的初始化过程根据上面的方法名称很容易理解。以HandlerMapping为例来说明这个initHandlerMappings()过程。这里的Mapping关系的作用是为HTTP请求找到相应的Controller控制器从而利用这些控制器Controller去完成设计好的数据处理工作。
HandlerMappings完成对MVC中Controller的定义和配置只不过在Web这个特定的应用环境中这些控制器是与具体的HTTP请求相对应的。在HandlerMapping初始化的过程中把在Bean配置文件中配置好的HandlerMapping从IoC容器中取得。
```java
/**
* 初始化此类使用的HandlerMappings。
* 如果在BeanFactory中没有为此命名空间定义的HandlerMapping bean则默认为BeanNameUrlHandlerMapping
*/
private void initHandlerMappings(ApplicationContext context) {
this.handlerMappings = null;
// 这个detectAllHandlerMappings默认为true表示从所有的IoC容器中获取所有的HandlerMappings
if (this.detectAllHandlerMappings) {
// 查找所有的HandlerMapping从应用上下文context及其双亲上下文中
Map<String, HandlerMapping> matchingBeans = BeanFactoryUtils.beansOfTypeIncludingAncestors(
context, HandlerMapping.class, true, false);
if (!matchingBeans.isEmpty()) {
this.handlerMappings = new ArrayList<HandlerMapping>(
matchingBeans.values());
// 保持HandlerMappings的有序性
OrderComparator.sort(this.handlerMappings);
}
}
else {
try {
// 根据名称从当前的IoC容器中通过getBean()获取HandlerMapping
HandlerMapping hm = context.getBean(HANDLER_MAPPING_BEAN_NAME,
HandlerMapping.class);
this.handlerMappings = Collections.singletonList(hm);
}
catch (NoSuchBeanDefinitionException ex) {
// 忽略稍后将添加默认的HandlerMapping
}
}
// 如果找不到其他映射请通过注册默认的HandlerMapping确保至少有一个HandlerMapping
if (this.handlerMappings == null) {
this.handlerMappings = getDefaultStrategies(context, HandlerMapping.class);
if (logger.isDebugEnabled()) {
logger.debug("No HandlerMappings found in servlet '" + getServletName()
+ "': using default");
}
}
}
```
经过以上读取过程handlerMappings变量就已经获取了在Bean中配置好的映射关系。其他的初始化过程和handlerMappings比较类似都是直接从IoC容器中读入配置所以这里的MVC初始化过程是建立在IoC容器已经初始化完成的基础上的。
## 4 SpringMVC处理分发HTTP请求
### 4.1 HandlerMapping的配置和设计原理
前面分析了DispatcherServlet对Spring MVC框架的初始化过程在此基础上我们再进一步分析HandlerMapping的实现原理看看这个MVC框架中比较关键的控制部分是如何实现的。
在初始化完成时在上下文环境中已定义的所有HandlerMapping都已经被加载了这些加载的handlerMappings被放在一个List中并被排序存储着HTTP请求对应的映射数据。这个List中的每一个元素都对应着一个具体handlerMapping的配置一般每一个handlerMapping
可以持有一系列从URL请求到Controller的映射而Spring MVC提供了一系列的HandlerMapping实现。
![avatar](/images/springMVC/HandlerMapping组件.png)
以SimpleUrlHandlerMapping这个handlerMapping为例来分析HandlerMapping的设计与实现。在SimpleUrlHandlerMapping中定义了一个map来 持有 一系列的映射关系。通过这些在HandlerMapping中定义的映射关系即这些URL请求和控制器的对应关系使Spring MVC
应用可以根据HTTP请求确定一个对应的Controller。具体来说这些映射关系是通过接口HandlerMapping来封装的在HandlerMapping接 口中定义了一个getHandler方法通过这个方法可以获得与HTTP请求对应的HandlerExecutionChain在这个HandlerExecutionChain
封装了具体的Controller对象。
```java
public interface HandlerMapping {
String PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE = HandlerMapping.class.getName() + ".pathWithinHandlerMapping";
String BEST_MATCHING_PATTERN_ATTRIBUTE = HandlerMapping.class.getName() + ".bestMatchingPattern";
String INTROSPECT_TYPE_LEVEL_MAPPING = HandlerMapping.class.getName() + ".introspectTypeLevelMapping";
String URI_TEMPLATE_VARIABLES_ATTRIBUTE = HandlerMapping.class.getName() + ".uriTemplateVariables";
String MATRIX_VARIABLES_ATTRIBUTE = HandlerMapping.class.getName() + ".matrixVariables";
String PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE = HandlerMapping.class.getName() + ".producibleMediaTypes";
/**
* 返回的这个HandlerExecutionChain不但持有handler本身还包括了处理这个HTTP请求的拦截器
*/
HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception;
}
```
这个HandlerExecutionChain的实现看起来比较简洁它持有一个Interceptor链和一个handler对象这个handler对象实际上就是HTTP请求对应的Controller在持有这个handler对象的同时还在HandlerExecutionChain中设置了一个拦截器链通过这个拦截器链中的拦截器,
可以为handler对象提供功能的增强。要完成这些工作需要对拦截器链和handler都进行配置这些配置都是在HandlerExecutionChain的初始化函数中完成的。为了维护这个拦截器链和handlerHandlerExecutionChain还提供了一系列与拦截器链维护相关的操作比如为拦
截器链增加拦截器的addInterceptor()方法。
```java
public class HandlerExecutionChain {
private static final Log logger = LogFactory.getLog(HandlerExecutionChain.class);
private final Object handler;
private HandlerInterceptor[] interceptors;
private List<HandlerInterceptor> interceptorList;
private int interceptorIndex = -1;
public HandlerExecutionChain(Object handler) {
this(handler, null);
}
public HandlerExecutionChain(Object handler, HandlerInterceptor[] interceptors) {
if (handler instanceof HandlerExecutionChain) {
HandlerExecutionChain originalChain = (HandlerExecutionChain) handler;
this.handler = originalChain.getHandler();
this.interceptorList = new ArrayList<HandlerInterceptor>();
CollectionUtils.mergeArrayIntoCollection(originalChain.getInterceptors(), this.interceptorList);
CollectionUtils.mergeArrayIntoCollection(interceptors, this.interceptorList);
}
else {
this.handler = handler;
this.interceptors = interceptors;
}
}
public Object getHandler() {
return this.handler;
}
/**
* 为拦截器链 添加拦截器
*/
public void addInterceptor(HandlerInterceptor interceptor) {
initInterceptorList();
this.interceptorList.add(interceptor);
}
/**
* 批量添加拦截器
*/
public void addInterceptors(HandlerInterceptor[] interceptors) {
if (interceptors != null) {
initInterceptorList();
this.interceptorList.addAll(Arrays.asList(interceptors));
}
}
/**
* 延迟初始化interceptorList和interceptors集合
*/
private void initInterceptorList() {
if (this.interceptorList == null) {
this.interceptorList = new ArrayList<HandlerInterceptor>();
}
if (this.interceptors != null) {
this.interceptorList.addAll(Arrays.asList(this.interceptors));
this.interceptors = null;
}
}
public HandlerInterceptor[] getInterceptors() {
if (this.interceptors == null && this.interceptorList != null) {
this.interceptors = this.interceptorList.toArray(new HandlerInterceptor[this.interceptorList.size()]);
}
return this.interceptors;
}
@Override
public String toString() {
if (this.handler == null) {
return "HandlerExecutionChain with no handler";
}
StringBuilder sb = new StringBuilder();
sb.append("HandlerExecutionChain with handler [").append(this.handler).append("]");
if (!CollectionUtils.isEmpty(this.interceptorList)) {
sb.append(" and ").append(this.interceptorList.size()).append(" interceptor");
if (this.interceptorList.size() > 1) {
sb.append("s");
}
}
return sb.toString();
}
}
```
HandlerExecutionChain中定义的Handler和Interceptor需要在定义HandlerMapping时配置好例如对具体的SimpleURLHandlerMapping要做的就是根据URL映射的方式注册Handler和Interceptor从而维护一个反映这种映射关系的handlerMap。当需要匹配HTTP请求时需要查询这个handlerMap中的信息来得到对应的HandlerExecutionChain。这些信息是什么时候配置好的呢?这里有一个注册过程这个注册过程在容器对Bean进行依赖注入时发生它实际上是通过一个Bean的postProcessor()来完成的。以SimpleHandlerMapping为例需要注意的是这里用到了对容器的回调只有SimpleHandlerMapping是ApplicationContextAware的子类才能启动这个注册过程。这个注册过程完成的是反映URL和Controller之间映射关系的handlerMap的建立。
![avatar](/images/springMVC/SimpleUrlHandlerMapping的继承关系.png)
```java
public class SimpleUrlHandlerMapping extends AbstractUrlHandlerMapping {
@Override
public void initApplicationContext() throws BeansException {
super.initApplicationContext();
registerHandlers(this.urlMap);
}
/**
* 为相应的路径注册URL映射中指定的所有handlers处理程序
*/
protected void registerHandlers(Map<String, Object> urlMap) throws BeansException {
if (urlMap.isEmpty()) {
logger.warn("Neither 'urlMap' nor 'mappings' set on SimpleUrlHandlerMapping");
}
else {
// 这里对bean的配置进行解析然后调用父类的registerHandler()方法进行解析
for (Map.Entry<String, Object> entry : urlMap.entrySet()) {
String url = entry.getKey();
Object handler = entry.getValue();
// 如果url没有斜线就在前面加上斜线
if (!url.startsWith("/")) {
url = "/" + url;
}
// Remove whitespace from handler bean name.
if (handler instanceof String) {
handler = ((String) handler).trim();
}
// 这里调用的是父类的方法
registerHandler(url, handler);
}
}
}
}
```
这个SimpleUrlHandlerMapping注册过程的完成很大一部分需要它的基类来配合这个基类就是AbstractUrlHandlerMapping。在AbstractUrlHandlerMapping的处理过程中如果使用Bean的名称作为映射那么直接从容器中获取这个HTTP映射对应的Bean然后还要对不同的URL配置进行解析处理比如在HTTP请求中配置成“/”和通配符“/*” 的URL以及正常的URL请求完成这个解析处理过程以后
把URL和handler作为键值对放到一个handlerMap中去。
```java
public abstract class AbstractHandlerMapping extends WebApplicationObjectSupport implements HandlerMapping, Ordered {
/**
* 为给定的URL路径注册指定的handler处理程序
*/
protected void registerHandler(String[] urlPaths, String beanName) throws BeansException, IllegalStateException {
Assert.notNull(urlPaths, "URL path array must not be null");
for (String urlPath : urlPaths) {
registerHandler(urlPath, beanName);
}
}
/**
* 为给定的URL路径注册指定的handler处理程序
*/
protected void registerHandler(String urlPath, Object handler) throws BeansException, IllegalStateException {
Assert.notNull(urlPath, "URL path must not be null");
Assert.notNull(handler, "Handler object must not be null");
Object resolvedHandler = handler;
// 如果使用bean名称进行映射就直接从IoC容器中获取该bean名称对应的handler
if (!this.lazyInitHandlers && handler instanceof String) {
String handlerName = (String) handler;
if (getApplicationContext().isSingleton(handlerName)) {
resolvedHandler = getApplicationContext().getBean(handlerName);
}
}
Object mappedHandler = this.handlerMap.get(urlPath);
if (mappedHandler != null) {
if (mappedHandler != resolvedHandler) {
throw new IllegalStateException(
"Cannot map " + getHandlerDescription(handler) + " to URL path [" + urlPath +
"]: There is already " + getHandlerDescription(mappedHandler) + " mapped.");
}
}
else {
// 处理URL是"/"的映射,把这个"/"映射的controller设置到rootHandler中
if (urlPath.equals("/")) {
if (logger.isInfoEnabled()) {
logger.info("Root mapping to " + getHandlerDescription(handler));
}
setRootHandler(resolvedHandler);
}
// 处理URL是"/"的映射,把这个"/"映射的controller设置到defaultHandler中
else if (urlPath.equals("/*")) {
if (logger.isInfoEnabled()) {
logger.info("Default mapping to " + getHandlerDescription(handler));
}
setDefaultHandler(resolvedHandler);
}
// 处理正常的URL映射此handlerMap的key和value分别代表URL和映射的Controller
else {
this.handlerMap.put(urlPath, resolvedHandler);
if (logger.isInfoEnabled()) {
logger.info("Mapped URL path [" + urlPath + "] onto " + getHandlerDescription(handler));
}
}
}
}
/**
* 为此handler映射设置根handler即要为根路径"/"注册的handler
* <p>Default is {@code null}, indicating no root handler.
*/
public void setRootHandler(Object rootHandler) {
this.rootHandler = rootHandler;
}
public Object getRootHandler() {
return this.rootHandler;
}
/**
* 设置此handler映射的默认handler。如果未找到特定映射则将返回此handler
*/
public void setDefaultHandler(Object defaultHandler) {
this.defaultHandler = defaultHandler;
}
public Object getDefaultHandler() {
return this.defaultHandler;
}
}
```
这里的handlerMap是一个HashMap其中保存了URL请求和Controller的映射关系这个handlerMap是在AbstractUrlHandlerMapping中定义的 Map<String, object> handlerMap = new LinkedHashMap<String, object>() 这个配置好URL请求和handler映射数据的handlerMap为Spring MVC响应HTTP请求准备好了基本的映射数据根据这个handlerMap以及设置于其中的映射数据可以方便地由
URL请求得到它所对应的handler。有了这些准备工作Spring MVC就可以等待HTTP请求的到来了。
### 4.2 使用HandlerMapping完成请求的映射处理
继续通过SimpleUrlHandlerMapping的实现来分析HandlerMapping的接口方法getHandler()该方法会根据初始化时得到的映射关系来生成DispatcherServlet需要的HandlerExecutionChain也就是说这个getHandler()方法是实际使用HandlerMapping完成请求的映射处理的地方。在前面的HandlerExecutionChain的执行过程中首先在AbstractHandlerMapping中启动getHandler的调用。
```java
public abstract class AbstractHandlerMapping extends WebApplicationObjectSupport implements HandlerMapping, Ordered {
/**
* 查找给定请求的handler如果找不到特定的handler则返回到defaultHandler
*/
public final HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
// 模板方法模式
Object handler = getHandlerInternal(request);
// 如果找不到特定的handler则取defaultHandler
if (handler == null) {
handler = getDefaultHandler();
}
// defaultHandler也没有则返回null
if (handler == null) {
return null;
}
// 如果该handler是String类型的说明它是一个beanname
// 根据该beanname从IoC容器中获取真正的handler对象
if (handler instanceof String) {
String handlerName = (String) handler;
handler = getApplicationContext().getBean(handlerName);
}
// 这里把handler添加到到HandlerExecutionChain中
return getHandlerExecutionChain(handler, request);
}
}
```
取得handler的具体过程在getHandlerInternal()方法中实现这个方法接受HTTP请求作为参数它的实现在AbstractHandlerMapping的子类AbstractUrlHandlerMapping中这个实现过程包括从HTTP请求中得到URL并根据URL到urlMapping中获得handler。
```java
public abstract class AbstractUrlHandlerMapping extends AbstractHandlerMapping {
/**
* 查找给定请求的URL路径 对应的handler
*/
@Override
protected Object getHandlerInternal(HttpServletRequest request) throws Exception {
// 从request中获取请求的URL路径
String lookupPath = getUrlPathHelper().getLookupPathForRequest(request);
// 将得到的URL路径与handler进行匹配得到对应的handler如果没有对应的handler
// 则返回null这样默认的handler会被使用
Object handler = lookupHandler(lookupPath, request);
if (handler == null) {
// We need to care for the default handler directly, since we need to
// expose the PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE for it as well.
Object rawHandler = null;
if ("/".equals(lookupPath)) {
rawHandler = getRootHandler();
}
// 使用默认的handler
if (rawHandler == null) {
rawHandler = getDefaultHandler();
}
if (rawHandler != null) {
// Bean name or resolved handler?
if (rawHandler instanceof String) {
String handlerName = (String) rawHandler;
rawHandler = getApplicationContext().getBean(handlerName);
}
validateHandler(rawHandler, request);
handler = buildPathExposingHandler(rawHandler, lookupPath, lookupPath, null);
}
}
if (handler != null && logger.isDebugEnabled()) {
logger.debug("Mapping [" + lookupPath + "] to " + handler);
}
else if (handler == null && logger.isTraceEnabled()) {
logger.trace("No handler mapping found for [" + lookupPath + "]");
}
return handler;
}
/**
* 查找给定URL路径的handler实例
*/
protected Object lookupHandler(String urlPath, HttpServletRequest request) throws Exception {
// 直接匹配
Object handler = this.handlerMap.get(urlPath);
if (handler != null) {
// Bean name or resolved handler?
if (handler instanceof String) {
String handlerName = (String) handler;
handler = getApplicationContext().getBean(handlerName);
}
validateHandler(handler, request);
return buildPathExposingHandler(handler, urlPath, urlPath, null);
}
// 正则匹配
List<String> matchingPatterns = new ArrayList<String>();
for (String registeredPattern : this.handlerMap.keySet()) {
if (getPathMatcher().match(registeredPattern, urlPath)) {
matchingPatterns.add(registeredPattern);
}
}
String bestPatternMatch = null;
Comparator<String> patternComparator = getPathMatcher().getPatternComparator(urlPath);
if (!matchingPatterns.isEmpty()) {
Collections.sort(matchingPatterns, patternComparator);
if (logger.isDebugEnabled()) {
logger.debug("Matching patterns for request [" + urlPath + "] are " + matchingPatterns);
}
bestPatternMatch = matchingPatterns.get(0);
}
if (bestPatternMatch != null) {
handler = this.handlerMap.get(bestPatternMatch);
// Bean name or resolved handler?
if (handler instanceof String) {
String handlerName = (String) handler;
handler = getApplicationContext().getBean(handlerName);
}
validateHandler(handler, request);
String pathWithinMapping = getPathMatcher().extractPathWithinPattern(bestPatternMatch, urlPath);
// There might be multiple 'best patterns', let's make sure we have the correct URI template variables
// for all of them
Map<String, String> uriTemplateVariables = new LinkedHashMap<String, String>();
for (String matchingPattern : matchingPatterns) {
if (patternComparator.compare(bestPatternMatch, matchingPattern) == 0) {
Map<String, String> vars = getPathMatcher().extractUriTemplateVariables(matchingPattern, urlPath);
Map<String, String> decodedVars = getUrlPathHelper().decodePathVariables(request, vars);
uriTemplateVariables.putAll(decodedVars);
}
}
if (logger.isDebugEnabled()) {
logger.debug("URI Template variables for request [" + urlPath + "] are " + uriTemplateVariables);
}
return buildPathExposingHandler(handler, bestPatternMatch, pathWithinMapping, uriTemplateVariables);
}
// No handler found...
return null;
}
}
```
经过这一系列对HTTP请求进行解析和匹配handler的过程得到了与请求对应的handler处理器。在返回的handler中已经完成了在HandlerExecutionChain中进行封装的工作为handler对HTTP请求的响应做好了准备。
### 4.3 DispatcherServlet对HTTP请求的分发处理

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 66 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 41 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Loading…
Cancel
Save