diff --git a/README.md b/README.md index 077d221..2c7fa03 100644 --- a/README.md +++ b/README.md @@ -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 diff --git a/docs/Mybatis/基础支持层/4、缓存模块.md b/docs/Mybatis/基础支持层/4、缓存模块.md index 12e1e73..b5375b9 100644 --- a/docs/Mybatis/基础支持层/4、缓存模块.md +++ b/docs/Mybatis/基础支持层/4、缓存模块.md @@ -47,7 +47,7 @@ public interface Cache { ``` 如下图所示,Cache接口的实现类有很多,但大部分都是装饰器,只有PerpetualCache提供了Cache 接口的基本实现。 -![avator](/images/mybatis/Cache组件.png) +![avatar](/images/mybatis/Cache组件.png) ### 1.1 PerpetualCache PerpetualCache(Perpetual:永恒的,持续的)在缓存模块中扮演着被装饰的角色,其实现比较简单,底层使用HashMap记录缓存项,也是通过该HashMap对象的方法实现的Cache接口中定义的相应方法。 ```java diff --git a/docs/Spring/SpringMVC/IoC容器在Web环境中的启动.md b/docs/Spring/SpringMVC/IoC容器在Web环境中的启动.md new file mode 100644 index 0000000..afc89e4 --- /dev/null +++ b/docs/Spring/SpringMVC/IoC容器在Web环境中的启动.md @@ -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 + + sample + org.springframework.web.servlet.DispatcherServlet + 6 + + + sample + /* + + + contextConfigLocation + /WEB-INF/applicationContext.xml + + + org.springframework.web.context.ContextLoaderListener + +``` +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_PARAM(contextClass)参数的配置 + 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自带的上下文的双亲上下文。 \ No newline at end of file diff --git a/docs/Spring/SpringMVC/SpringMVC的设计与实现.md b/docs/Spring/SpringMVC/SpringMVC的设计与实现.md new file mode 100644 index 0000000..9febb8c --- /dev/null +++ b/docs/Spring/SpringMVC/SpringMVC的设计与实现.md @@ -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请求进行响应,作为一个Servlet,Web容器会调用Servlet的doGet()和doPost()方法,在经过FrameworkServlet的processRequest()简单处理后,会调用DispatcherServlet的doService()方法,在这个方法调用中封装了doDispatch(),这个doDispatch()是Dispatcher实现MVC模式的主要部分,下图为DispatcherServlet的处理过程时序图。 + +![avatar](/images/springMVC/DispatcherServlet的处理过程.png) + +## 3 DispatcherServlet的启动和初始化 +前面大致描述了Spring MVC的工作流程,下面看一下DispatcherServlet的启动和初始化的代码设计及实现。 + +作为Servlet,DispatcherServlet的启动与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_ATTRIBUTE,ContextLoader还对这个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 matchingBeans = BeanFactoryUtils.beansOfTypeIncludingAncestors( + context, HandlerMapping.class, true, false); + if (!matchingBeans.isEmpty()) { + this.handlerMappings = new ArrayList( + 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的初始化函数中完成的。为了维护这个拦截器链和handler,HandlerExecutionChain还提供了一系列与拦截器链维护相关的操作,比如,为拦 +截器链增加拦截器的addInterceptor()方法。 +```java +public class HandlerExecutionChain { + + private static final Log logger = LogFactory.getLog(HandlerExecutionChain.class); + + private final Object handler; + + private HandlerInterceptor[] interceptors; + + private List 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(); + 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(); + } + 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 urlMap) throws BeansException { + if (urlMap.isEmpty()) { + logger.warn("Neither 'urlMap' nor 'mappings' set on SimpleUrlHandlerMapping"); + } + else { + // 这里对bean的配置进行解析,然后调用父类的registerHandler()方法进行解析 + for (Map.Entry 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 + *

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 handlerMap = new LinkedHashMap() ),这个配置好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 matchingPatterns = new ArrayList(); + for (String registeredPattern : this.handlerMap.keySet()) { + if (getPathMatcher().match(registeredPattern, urlPath)) { + matchingPatterns.add(registeredPattern); + } + } + String bestPatternMatch = null; + Comparator 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 uriTemplateVariables = new LinkedHashMap(); + for (String matchingPattern : matchingPatterns) { + if (patternComparator.compare(bestPatternMatch, matchingPattern) == 0) { + Map vars = getPathMatcher().extractUriTemplateVariables(matchingPattern, urlPath); + Map 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请求的分发处理 + + + + diff --git a/docs/Netty/占位文件.stkj b/docs/Spring/SpringMVC/温习一下servlet.md similarity index 100% rename from docs/Netty/占位文件.stkj rename to docs/Spring/SpringMVC/温习一下servlet.md diff --git a/docs/学习心得/编码规范/异常日志.md b/docs/学习心得/编码规范/异常日志.md new file mode 100644 index 0000000..e69de29 diff --git a/docs/学习心得/编码规范/数据库规范.md b/docs/学习心得/编码规范/数据库规范.md new file mode 100644 index 0000000..e69de29 diff --git a/docs/学习心得/编码规范/编程规约.md b/docs/学习心得/编码规范/编程规约.md new file mode 100644 index 0000000..e69de29 diff --git a/images/springMVC/DispatcherServlet的处理过程.png b/images/springMVC/DispatcherServlet的处理过程.png new file mode 100644 index 0000000..40c227d Binary files /dev/null and b/images/springMVC/DispatcherServlet的处理过程.png differ diff --git a/images/springMVC/DispatcherServlet的继承关系.png b/images/springMVC/DispatcherServlet的继承关系.png new file mode 100644 index 0000000..7b99c8b Binary files /dev/null and b/images/springMVC/DispatcherServlet的继承关系.png differ diff --git a/images/springMVC/HandlerMapping组件.png b/images/springMVC/HandlerMapping组件.png new file mode 100644 index 0000000..48038ca Binary files /dev/null and b/images/springMVC/HandlerMapping组件.png differ diff --git a/images/springMVC/SimpleUrlHandlerMapping的继承关系.png b/images/springMVC/SimpleUrlHandlerMapping的继承关系.png new file mode 100644 index 0000000..8c5ce0a Binary files /dev/null and b/images/springMVC/SimpleUrlHandlerMapping的继承关系.png differ diff --git a/images/springMVC/WebApplicationContext接口的类继承关系.png b/images/springMVC/WebApplicationContext接口的类继承关系.png new file mode 100644 index 0000000..ad12de6 Binary files /dev/null and b/images/springMVC/WebApplicationContext接口的类继承关系.png differ diff --git a/images/springMVC/Web容器启动spring应用程序过程图.png b/images/springMVC/Web容器启动spring应用程序过程图.png new file mode 100644 index 0000000..4777ddf Binary files /dev/null and b/images/springMVC/Web容器启动spring应用程序过程图.png differ