SpringMVC源码5.0:ContextLoaderListener

我打算先看Mvc,目前毕竟需要先研究源码好调试情况。

Spring框架提供了构建Web应用程序的全功能MVC模块,通过策略接口,Spring框架时高度可配置的,而且支持多种视图技术。
SpringMVC分离了控制器,模型对象,分派器以及处理程序对象的角色。此分离让它们更容易进行定制。

Servlet

  • MVC是基于Servlet功能实现的,通过实现Servlet接口,通过实现Servlet接口的DispatcherServlet来封装其核心功能实现,通过将请求分派给处理程序,同时带有可配置的处理程序映射,视图解析,本地语言,主题解析以及上载文件支持。
  • 默认的处程序是非常简单的Controller接口,只有一个方法ModelAndView handleRequest(request,response)。Spring提供了一个控制器层次结构,可以派生子类。如果应用程序需要处理输入表单。那么可以基础AbstractFormController如果需要多页输入处理到一个表单,那么可以基础AbstractWizardFormController。
  • 其MVC解决了:将Web页面的请求传给服务器。根据不同的请求处理不同的逻辑单元。返回处理结果数据并跳转至响应的页面

这里是基于XML配置的不是基于SpringBoot ApplicationProperties

运行

  • 配置web.xml

其使用web.xml初始化配置信息,如index页面,servlet,servlet-mapping,filter,listener,启动加载级别等。但是其实现原理是通过servlet拦截所有URL来达到控制的目的。

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
         version="3.1">
    <!--添加过滤器-->
    <filter>
        <filter-name>characterEncodingFilter</filter-name>
        <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
        <init-param>
            <param-name>encoding</param-name>
            <param-value>UTF-8</param-value>
        </init-param>
        <init-param>
            <param-name>forceEncoding</param-name>
            <param-value>true</param-value>
        </init-param>
    </filter>
    <filter-mapping>
        <filter-name>characterEncodingFilter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>
    <!--配置spring-->
    <servlet>
        <servlet-name>dispatcherServlet</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <init-param>
<!-- 使用ContextLoaderListener配置时,需要告诉它Spring配置文件的位置 -->
            <param-name>contextConfigLocation</param-name>
            <param-value>classpath:applicationContext.xml</param-value>
        </init-param>
    </servlet>
    <servlet-mapping>
        <servlet-name>dispatcherServlet</servlet-name>
        <url-pattern>/</url-pattern>
    </servlet-mapping>
    <!--自定义首页-->
  <!--  <welcome-file-list>
        <welcome-file>/first/page</welcome-file>
    </welcome-file-list>-->
    <!--配置404错误页面-->
    <error-page>
        <error-code>404</error-code>
        <location>/error_pages/404.jsp</location>
    </error-page>

    <!--读取静态文件-->
    <servlet-mapping>
        <servlet-name>default</servlet-name>
        <url-pattern>*.js</url-pattern>
        <url-pattern>*.css</url-pattern>
    </servlet-mapping>


</web-app>

MVC最关键的是要配置两个地方。
contextConfigLocation:Spring的核心就是配置文件,这个参数就是使Web与Spring的配置文件相结合的一个关键配置
DispatcherServlet:包含了SpringMVC的请求逻辑,Spring使用此类拦截Web请求并进行相应的逻辑处理

  • 创建Spring的配置文件applicationContext.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
   xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans  
   http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context 
   http://www.springframework.org/schema/context/spring-context.xsd">
   <context:component-scan base-package="com.concretepage.controller" />
   <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
	  <property name="prefix" value="/WEB-INF/pages/"/>
	  <property name="suffix" value=".jsp"/> 
   </bean>
</beans>

InternalResourceViewResp;ver是一个辅助bean,会在ModelAndView返回的视图名前面加上prefix指定的前缀,再在最后加上suffix指定的后缀,列如:由于XXController返回的ModelAndView中的视图名是testview,故在视图解析器在/WEB-INF/jsp/testview.jsp处查找视图。

  • 创建model
    用于承载数据
  • 创建controller
    控制器用于处理Web请求。
ModelAndView handleRequestInternal(HttpServletRequest arg0,HttpServletResponse arg1) throws Exception{
......
return new ModelAndView();
}

控制器执行方法都必须返回一个ModelAndView。

  • 创建视图文件
    视图文件用于展现请求处理结果

  • 创建Servlet配置文件Spring-servlet

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
   xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans  
   http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context 
   http://www.springframework.org/schema/context/spring-context.xsd">
   <bean id="simpleUrlMapping"
class="org.springframework.web.servlet.handler.SompleUrlHadnlerMapping">
	  <property name="mappings">
		<props>
<prop key="/userlist.htm">userController</prop>
</props>  
</property> 
   </bean>
   <bean id ="userController" class="test.contrlloer.UserController" />
</beans>

因为SpringMVC是基于Servlet的实现,所以在Web启动时,服务器会实现尝试加载对应于Servlet的配置文件,而为了让项目更加模块化,通常将Web部分配置存放于此配置文件中。

一个SpringMVC应用完成

ContextLoaderListener

从功能实现的分析,先从web.xml开始,在web.xml首先配置的就是ContextLoaderListener。
当使用编程方式的时候,可以将Spring配置信息作为参数传入Spring容器中。如

ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml");

但是在Web下,我们需要更多的时与Web环境互相结合,通常就是将路径以context-param的方式注册并使用ContextLoaderListener进行监听读取

ContextLoaderListener作用就是启动Web容器时,自动装配ApplicationContext的配置信息。因为其实现了ServletContextListener此接口。在web.xml配置这个监听器,启动容器时会默认执行它实现的方法,使用ServletContextListener接口。开发者能够在客户端请求提供服务之前向ServletContext中添加任意的对象,此对对象在ServletContext启动的时候被初始化。然后再ServletContext整个运行期间都是可见的。
每个Web应用都有一个ServletContext与之相关,ServletContext对象再应用启动时被创建,在应用关闭的时候被销毁,ServletContext在全局范围内有效,类型于应用中的一个全局变量。
在ServletContextListenrer中的核心逻辑便是初始化WebApplicationContext实例并存放至ServletContext中。

ServletContextListener的使用

  • 创建自定义ServletContextListener
    实现在系统启动时添加自定义的属性,以便在全局范围内可以随时调用,系统启动的时候会调用ServletContextListener实现类的contextInitialized方法。所以在其方法实现初始化逻辑
public class MyDataContextListener implements ServletContextListener{
private ServletContext context =null;
public MyDataContextListener(){
}
public void contextInitialized(ServletContextEvent event){
this.context = event.getServletContext();
context =setAttribute("MyData","this data");
}
public void contextDestroyed(ServletContextEvent even){
this.context =null;
}
}
  • 注册监听器
    在web.xml注册自定义的监听器
<listener>
com.test.MyDataContextListener
</listener>
  • 测试
    Web应用启动时,可以在任何的Servlet or JSP获取初始化的参数
String myData = (String)getServletContext.getAttribute("myData");

Spring中的ContextLoaderListener

分析了ServletContextListener 后再分析ContextLoaderListener的实现。

ServletContext启动后会调用ServletContextListener的contextInitialized方法。

ContextLoaderListener.class

@Override
public void contextInitialized(ServletContextEvent event) {
//初始化WevApplicationContext
   initWebApplicationContext(event.getServletContext());
}

这里的WebApplicationContext:再web应用中。我们会用到它,它继承自ApplicationContext。
在ApplicationContext的基础之上又追加了一些特定于Web操作及属性。类似通过编程方式使用Spring时使用的ClassPathXmlApplicationContext类提供的功能。

ContextLoader.class

public WebApplicationContext initWebApplicationContext(ServletContext servletContext) {
   if (servletContext.getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE) != null) {
//如果web.xml存在多次ContextLoader定义   
   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 {
      // Store context in local instance variable, to guarantee that
      // it is available on ServletContext shutdown.
      if (this.context == null) {
//初始化context
         this.context = createWebApplicationContext(servletContext);
      }
      if (this.context instanceof ConfigurableWebApplicationContext) {
         ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) this.context;
         if (!cwac.isActive()) {
            // The context has not yet been refreshed -> provide services such as
            // setting the parent context, setting the application context id, etc
            if (cwac.getParent() == null) {
               // The context instance was injected without an explicit parent ->
               // determine parent for root web application context, if any.
               ApplicationContext parent = loadParentContext(servletContext);
               cwac.setParent(parent);
            }
            configureAndRefreshWebApplicationContext(cwac, servletContext);
         }
      }
//记录在servletContext中      
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;
   }
}

initWebApplicationContex函数主要时体现了创建WebApplicationContext实例的一个功能架构,步骤如下

  • WebApplicationContext存在性的验证
    配置只允许声明一次ServletContextListener,多次声明会扰乱Spring执行逻辑,所以首先对此验证。
    如果创建WebApplicationContext实例会记录在ServletContext中以方便全局调用,而使用key就是WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE,所以验证方式就是查看ServletContext实例中是否有对应key的属性。
  • 创建WebApplicationContext实例
    如果通过验证,则创建WebApplicationContext实例的工作委托给了createWebApplicationContext函数。
protected WebApplicationContext createWebApplicationContext(ServletContext sc) {
   Class<?> contextClass = determineContextClass(sc);
   if (!ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass)) {
      throw new ApplicationContextException("Custom context class [" + contextClass.getName() +
            "] is not of type [" + ConfigurableWebApplicationContext.class.getName() + "]");
   }
   return (ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);
}

protected Class<?> determineContextClass(ServletContext servletContext) {
   String contextClassName = servletContext.getInitParameter(CONTEXT_CLASS_PARAM);
   if (contextClassName != null) {
      try {
         return ClassUtils.forName(contextClassName, ClassUtils.getDefaultClassLoader());
      }
      catch (ClassNotFoundException ex) {
         throw new ApplicationContextException(
               "Failed to load custom context class [" + contextClassName + "]", ex);
      }
   }
   else {
      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);
      }
   }
}

其中,在ContextLoader类中有这样的静态代码块


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

根据以上静态diamagnetic块内容,可得当前类ContextLoader同样目录下存在属性文件ContextLoader.properties,并根据其中的配置提取将要实现WebApplicationContext接口的实现类,并根据这个实现类通过反射方式进行实例的创建

  • 将实例记录在servletContext中
  • 映射当前的类加载器于创建的实例到全局变量currentContextPerThread中

DispatcherServlet

Spring中,ContextLoaderListener只是辅助功能,用于创建WebApplicationContext类型实例。而真正的逻辑实现其实实在DispatcherServlet中进行的,DispatcherServlet是实现servlet接口的实现类。

servlet

servlet是java编写的程序,其基于HTTP协议,在服务端运行的(如,Tomcat)是按照servlet规范编写的一个java类,主要是处理客户端的请求并将其结果发送到客户端。
servlet生命周期是由servlet的容器控制的。分为三个阶段:初始化,运行和销毁。

  • 初始化阶段
    servlet容器加载servlet类,把servlet类的.class文件中数据读到内存中。
    servlet容器创建一个ServletConfig对象。ServletConfig对象包含了servlet的初始化配置信息。
    servlet容器创建一个servlet对象。
    servlet容器调用servlet对象的init方法进行初始化。

  • 运行阶段
    当servlet容器接收到一个请求时servlet容器会针对这个请求创建servletRequest和ServletResponse对象,然后调用service方法,并把这两个参数传递给service方法。
    service方法通过servletRequest对象获得请求的信息,并处理该请求,再通过servletResponse对象生成这个请求的相应结果,然后销毁servletRequest和servletResponse对象。不管是post or get 最终都由service方法处理。

  • 销毁阶段

当Web应用被终止时,servlet容器会调用servlet对象的destroy方法,然后再销毁servlet对象。同时也会销毁与servlet对象相关联的servletConfig对象。我们可以再destroy方法的实现中释放servlet所占资源,如关闭数据库连接,关闭文件输入输出流等。
servlet框架由两个java包组成,javax.servlet 和 javax.servlet.http。
javax.servlet :定义了所有的servlet类都必须实现或扩展的通用接口和类。
avax.servlet.http:定义了采用HTTP通信协议的HttpServlet类。
servlet被设计成请求驱动,其请求可能包含多个数据项。当Web容器接收到某个servlet请求时,servlet把请求封装成一个HttpServletRequest对象,然后把对象传给servlet的对象的服务方法。
HTTP请求方式包括delete,get,options,post,put,trace。
HttpServlet类中分别提供了相应方法。doDelete(),doGet()…

DispatcherServlet的初始化

上面可得servlet初始化会调用其init方法。那么先查看在DispatcherServlet中是否重写了init方法。
在其父类 HttpServletBean中找到该方法。


@Override
public final void init() throws ServletException {
   if (logger.isDebugEnabled()) {
      logger.debug("Initializing servlet '" + getServletName() + "'");
   }

   // Set bean properties from init parameters.
   //解析init-param并封装至pvs中
PropertyValues pvs = new ServletConfigPropertyValues(getServletConfig(), this.requiredProperties);
   if (!pvs.isEmpty()) {
      try {
//将当前这个servlet类转换为BeanWrapper,使得能以Spring方式对init-param的值进进行注入
         BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this);
         ResourceLoader resourceLoader = new ServletContextResourceLoader(getServletContext());
//注册自定义属性编辑器,一旦遇到Resource类型的属性将会使用ResourceEdit进行解析
         bw.registerCustomEditor(Resource.class, new ResourceEditor(resourceLoader, getEnvironment()));
//空实现,留给子类覆盖
         initBeanWrapper(bw);
       //属性注入  
bw.setPropertyValues(pvs, true);
      }
      catch (BeansException ex) {
         if (logger.isErrorEnabled()) {
            logger.error("Failed to set bean properties on servlet '" + getServletName() + "'", ex);
         }
         throw ex;
      }
   }

   // Let subclasses do whatever initialization they like.留给子类扩展
   initServletBean();

   if (logger.isDebugEnabled()) {
      logger.debug("Servlet '" + getServletName() + "' configured successfully");
   }
}

DIspatcherServlet初始化主要通过当前servlet类型实例转换为BeanWrapper类型实例,以便使用Spring中提供的注入功能进行对应属性的注入,如contextAttribute,contextClass,nameSpace,contextConfigLocation等,都可以在web.xml中以初始化参数的方式配置在servlet声明中。
DispatcherServlet继承自FrameworkServlet,其包含对应的同名属性,Spring会保证这些参数被注入到对应的值中。属性注入包含以下:

  • 封装及验证初始化参数

ServletConfigPropertyValues 除了封装属性外还有对属性验证的功能

public ServletConfigPropertyValues(ServletConfig config, Set<String> requiredProperties)
         throws ServletException {

      Set<String> missingProps = (!CollectionUtils.isEmpty(requiredProperties) ?
            new HashSet<>(requiredProperties) : null);

      Enumeration<String> paramNames = config.getInitParameterNames();
      while (paramNames.hasMoreElements()) {
         String property = paramNames.nextElement();
         Object value = config.getInitParameter(property);
         addPropertyValue(new PropertyValue(property, value));
         if (missingProps != null) {
            missingProps.remove(property);
         }
      }

      // Fail if we are still missing properties.
      if (!CollectionUtils.isEmpty(missingProps)) {
         throw new ServletException(
               "Initialization from ServletConfig for servlet '" + config.getServletName() +
               "' failed; the following required properties were missing: " +
               StringUtils.collectionToDelimitedString(missingProps, ", "));
      }
   }
}

封装属性主要是对初始化参数进行封装,也就是servlet中配置中配置的封装,当然用户可以通过对requiredProperties参数的初始化来强制验证某些属性的必要性,这样在属性封装过程中一旦监测到requiredProperties中的属性没有指定初始值,就会抛出异常

  • 将当前servlet实例转化成BeanWrapper实例

PropertyAccessFactory.forBeanPropertyAccess是Spring中提供的工具方法,主要用于指定实例转化为Spring中可以除了的BeanWrapper实例。

  • 注册相对于Resource的属性编辑器

属性编辑器,容器的基本实现讲解过,这里使用属性编辑器的目的实在对当前实例(DispatcherServlet)属性注入过程中一旦遇到Resource类型的属性就会使用ResourceEditor去解析

  • 属性注入
    BeanWrapper为Spring中的方法,支持SPring的自动注入,最常用的属性无非就是contextAttribute,contextClass,nameSpace,contextConfigLocation等

  • servletBean的初始化

在ContextLoaderListener加载的时候已经创建了WebApplicationContext实例。而在这个函数中最重要的就是对这个实例进行进一步的补充初始化。
继续查看initServletBean(),父类FrameworkServlet覆盖了HttpServletBean中的initServletBean函数

@Override
protected final void initServletBean() throws ServletException {
   getServletContext().log("Initializing Spring FrameworkServlet '" + getServletName() + "'");
   if (logger.isInfoEnabled()) {
      logger.info("FrameworkServlet '" + getServletName() + "': initialization started");
   }
   long startTime = System.currentTimeMillis();

   try {
      this.webApplicationContext = initWebApplicationContext();
   //设计为子类覆盖
      initFrameworkServlet();
   }
   catch (ServletException | RuntimeException ex) {
      logger.error("Context initialization failed", ex);
      throw ex;
   }

   if (logger.isInfoEnabled()) {
      long elapsedTime = System.currentTimeMillis() - startTime;
      logger.info("FrameworkServlet '" + getServletName() + "': initialization completed in " +
            elapsedTime + " ms");
   }
}

上述函数设计了计时器来统计初始化的执行时间,而且提供了一个扩展方法initFrameworkServlet()用于子类的覆盖操作。而作为关键的初始化逻辑委托给了initWebApplicationContext()

WebApplicationContext初始化

创建or刷新WebApplicationContext实例并对servlet功能所使用的变量进行初始化

protected WebApplicationContext initWebApplicationContext() {
   WebApplicationContext rootContext =
         WebApplicationContextUtils.getWebApplicationContext(getServletContext());
   WebApplicationContext wac = null;

   if (this.webApplicationContext != null) {
      // A context instance was injected at construction time -> use it
      wac = this.webApplicationContext;
      if (wac instanceof ConfigurableWebApplicationContext) {
         ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) wac;
         if (!cwac.isActive()) {
            // The context has not yet been refreshed -> provide services such as
            // setting the parent context, setting the application context id, etc
            if (cwac.getParent() == null) {
               // The context instance was injected without an explicit parent -> set
               // the root application context (if any; may be null) as the parent
               cwac.setParent(rootContext);
            }
//刷新上下文环境
            configureAndRefreshWebApplicationContext(cwac);
         }
      }
   }
   if (wac == null) {
      // No context instance was injected at construction time -> see if one
      // has been registered in the servlet context. If one exists, it is assumed
      // that the parent context (if any) has already been set and that the
      // user has performed any initialization such as setting the context id
      //根据contextAttribute属性加载WebApplicationContext
wac = findWebApplicationContext();
   }
   if (wac == null) {
      // No context instance is defined for this servlet -> create a local one
      wac = createWebApplicationContext(rootContext);
   }

   if (!this.refreshEventReceived) {
      // Either the context is not a ConfigurableApplicationContext with refresh
      // support or the context injected at construction time had already been
      // refreshed -> trigger initial onRefresh manually here.
      synchronized (this.onRefreshMonitor) {
         onRefresh(wac);
      }
   }

   if (this.publishContext) {
      // Publish the context as a servlet context attribute.
      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;
}
  • 创建web ApplicationContext实例
  1. 通过构造函数的注入进行初始化
    进入initWebApplicationContext函数判断this.WebApplicationContext!=null便可确定this.webApplicationContext是否通过构造函数来初始化的。因为在Web种包含SpringWeb的核心逻辑的DispatcherServlet只可以被声明一次,在Spring中已经存在验证。
  2. 通过contextAttribute进行初始化
    通过在web.xml文件中配置的servlet参数contextAttribute来查找ServletContext中对应的属性,默认为WebApplicationContext.class.getName()+".ROOT",也就是在ContextLoaderListener加载时会创建WebApplicationContext实例,并将实例以WebApplicationContext.class.getName()+".ROOT"为key放入ServletContext中
protected WebApplicationContext findWebApplicationContext() {
   String attrName = getContextAttribute();
   if (attrName == null) {
      return null;
   }
   WebApplicationContext wac =
         WebApplicationContextUtils.getWebApplicationContext(getServletContext(), attrName);
   if (wac == null) {
      throw new IllegalStateException("No WebApplicationContext found: initializer not registered?");
   }
   return wac;
}
  1. 重新创建WebApplicationContext实例
    如果上面两种都没有找到任何突破,则重新创建新的实例
protected WebApplicationContext createWebApplicationContext(@Nullable ApplicationContext parent) {
//获取servlet的初始化参数contextClass,如果没有配置默认为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");
   }
//通过反射方式实例化contextClass
   ConfigurableWebApplicationContext wac =
         (ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);
//parent为在ContextLoaderListener中创建的实例
//在ContextLoaderListener加载的时候初始化的WebApplicationContext类型实例
   wac.setEnvironment(getEnvironment());
   wac.setParent(parent);

   String configLocation = getContextConfigLocation();
   if (configLocation != null) {
//获取contextConfigLocation属性,配置在servlet初始化参数中  
    wac.setConfigLocation(configLocation);
   }
//初始化Spring环境包括加载配置文件
   configureAndRefreshWebApplicationContext(wac);

   return wac;
}

configureAndRefreshWebApplicationContext

无论时通过构造函数注入还是单独创建,都会调用configureAndRefreshWebApplicationContext方法对已经创建的WebApplicationContext实例进行配置及刷新。

protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac) {
   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
      if (this.contextId != null) {
         wac.setId(this.contextId);
      }
      else {
         // Generate default id...
         wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX +
               ObjectUtils.getDisplayString(getServletContext().getContextPath()) + '/' + getServletName());
      }
   }

   wac.setServletContext(getServletContext());
   wac.setServletConfig(getServletConfig());
   wac.setNamespace(getNamespace());
   wac.addApplicationListener(new SourceFilteringListener(wac, new ContextRefreshListener()));

   // The wac environment's #initPropertySources will be called in any case when the context
   // is refreshed; do it eagerly here to ensure servlet property sources are in place for
   // use in any post-processing or initialization that occurs below prior to #refresh
   ConfigurableEnvironment env = wac.getEnvironment();
   if (env instanceof ConfigurableWebEnvironment) {
      ((ConfigurableWebEnvironment) env).initPropertySources(getServletContext(), getServletConfig());
   }

   postProcessWebApplicationContext(wac);
   applyInitializers(wac);
   //加载配置文件及整合parent到wac
   wac.refresh();
}

无论调用方式如何编号,只有是提供ApplicableContext所提供的功能最后都免不了使用公共父类AbstractApplicationContext提供的refresh()进行配置文件的加载

刷新

onRefresh是FrameworkServlet类中提供的模板方法,在其子类DispatcherServlet中进行了重写,主要用于刷新Spring在Web功能实现中所必须使用的全局变量

@Override
protected void onRefresh(ApplicationContext context) {
   initStrategies(context);
}

/**
 * Initialize the strategy objects that this servlet uses.
 * <p>May be overridden in subclasses in order to initialize further strategy objects.
 */
protected void initStrategies(ApplicationContext context) {
   initMultipartResolver(context);
   initLocaleResolver(context);
   initThemeResolver(context);
   initHandlerMappings(context);
   initHandlerAdapters(context);
   initHandlerExceptionResolvers(context);
   initRequestToViewNameTranslator(context);
   initViewResolvers(context);
   initFlashMapManager(context);
}

其初始化的有9个参数

  • MultipartResolver
    用于处理文件上传,xml配置。
    MultipartResolver就是在 initMultipartResolver(context) 中被加入到DIspatcherServlet中的。因为之前的步骤已经完成了Spring中配置文件的解析,所以在此只要在配置文件注册过都可以通过ApplicationContext 提供的getBean方法来直接获取对应bean,进而初始化MutipartResolver中multipartResolver变量。

  • 初始化LocaleResolver

Spring国际化配置有3种方式

  1. 基于url参数的配置
  2. 基于session的配置
  3. 基于cookie的国际化配置
  • 初始化ThemeResolver

主题,就是一组静态资源

  • 初始化HandlerMappings

当客户端发出Request时,DispatcherServlet会将Request提交给HandlerMapping,然后HandlerMapping根据WebApplicationContext的配置来回传给DispatcherServlet相应的Controller。
在基于SpringMVC的Web应用程序种,我们可为DispatcherServlet提供多个HandlerMapping的过程中,根据我们指定的一系列HandlerMapping的优先级进行排序,然后优先使用优先级在前的HandlerMapping。如果当前的HandlerMapping能够返回可用的Handler,DispatcherServlet则使用当前返回的Handler进行Web请求的处理,不再继续询问其他的HandlerMapping。否则DispatcherServlet将继续按照各个HandlerMapping的优先级进行询问,直到获取一个可用的Handler为止

private void initHandlerMappings(ApplicationContext context) {
   this.handlerMappings = null;

   if (this.detectAllHandlerMappings) {
      // Find all HandlerMappings in the ApplicationContext, including ancestor contexts.
      Map<String, HandlerMapping> matchingBeans =
            BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerMapping.class, true, false);
      if (!matchingBeans.isEmpty()) {
         this.handlerMappings = new ArrayList<>(matchingBeans.values());
         // We keep HandlerMappings in sorted order.
         AnnotationAwareOrderComparator.sort(this.handlerMappings);
      }
   }
   else {
      try {
         HandlerMapping hm = context.getBean(HANDLER_MAPPING_BEAN_NAME, HandlerMapping.class);
         this.handlerMappings = Collections.singletonList(hm);
      }
      catch (NoSuchBeanDefinitionException ex) {
         // Ignore, we'll add a default HandlerMapping later.
      }
   }

   // Ensure we have at least one HandlerMapping, by registering
   // a default HandlerMapping if no other mappings are found.
   if (this.handlerMappings == null) {
      this.handlerMappings = getDefaultStrategies(context, HandlerMapping.class);
      if (logger.isDebugEnabled()) {
         logger.debug("No HandlerMappings found in servlet '" + getServletName() + "': using default");
      }
   }
}

xml配置

<inti-param>
<param-name>detectAllHandlerMappings</param-name>
<param-value>fasle</param-value>
</init-param>

如果期望MVC加载指定的handlermapping时,可修改,如上设置值为fasle

  • 初始化HandlerAdapters

适配器,可以使接口不兼容而无法一起工作的类协同工作。做法就是将类自己包裹在一个已存在的类中。

private void initHandlerAdapters(ApplicationContext context) {
   this.handlerAdapters = null;

   if (this.detectAllHandlerAdapters) {
      // Find all HandlerAdapters in the ApplicationContext, including ancestor contexts.
      Map<String, HandlerAdapter> matchingBeans =
            BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerAdapter.class, true, false);
      if (!matchingBeans.isEmpty()) {
         this.handlerAdapters = new ArrayList<>(matchingBeans.values());
         // We keep HandlerAdapters in sorted order.
         AnnotationAwareOrderComparator.sort(this.handlerAdapters);
      }
   }
   else {
      try {
         HandlerAdapter ha = context.getBean(HANDLER_ADAPTER_BEAN_NAME, HandlerAdapter.class);
         this.handlerAdapters = Collections.singletonList(ha);
      }
      catch (NoSuchBeanDefinitionException ex) {
         // Ignore, we'll add a default HandlerAdapter later.
      }
   }

   // Ensure we have at least some HandlerAdapters, by registering
   // default HandlerAdapters if no other adapters are found.
   if (this.handlerAdapters == null) {
      this.handlerAdapters = getDefaultStrategies(context, HandlerAdapter.class);
      if (logger.isDebugEnabled()) {
         logger.debug("No HandlerAdapters found in servlet '" + getServletName() + "': using default");
      }
   }
}

同样在初始化过程中涉及了一个变量detectAllHandlerAdapters,其作用和detectAllHandlerMappings类型,只不过作用对象为handlerAdapter,可同上一样配置强制系统只加载beanname为handlerAdapter的handlerAdapter

<inti-param>
<param-name>detectAllHandlerMappings</param-name>
<param-value>fasle</param-value>
</init-param>

如果找不到对应的bean,那么系统会尝试加载默认的适配器

protected <T> List<T> getDefaultStrategies(ApplicationContext context, Class<T> strategyInterface)

defaultStrategies,在DispatcherServlet中有一段static代码

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, DispatcherServlet.class);
      defaultStrategies = PropertiesLoaderUtils.loadProperties(resource);
   }
   catch (IOException ex) {
      throw new IllegalStateException("Could not load '" + DEFAULT_STRATEGIES_PATH + "': " + ex.getMessage());
   }
}

在系统加载的时候,defaultStrategies根据当前路径DispatcherServlet.properties来初始化本身,查看DispatcherServlet.properties中对应于HandlerAdapter的属性。

如果开发人员没有在配置文件中定义自己的适配器,那么Spring默认加载配置文件中的3个适配器。
总控制的派遣器servlet通过处理器映射到处理器后,会轮询处理器适配器模块,查找能够处理当前HTTP请求的处理器适配器的实现,处理器适配器模块根据处理器映射返回的处理器类型,eg简单的控制器类型,注解控制器类型or远程调用处理器类型,来选择某个适当的处理器适配器的实现,从而适配当前的HTTP请求。

  1. HTTP请求处理器适配器(HttpRequestHandlerAdapter)
  2. 简单控制器处理器适配器(SimpleControllerHandlerAdapter)
  3. 注解方式处理器适配器(AnnotationMethodHandlerAdapter)

具体的这3个适配器大意可以网上查询,这里不多言了。

  • 初始化HandlerExceptionResolver

基于HandlerExceptionResolver接口的异常处理,使用这种方式只需要实现resolveException方法,该方法返回一个ModelAndView对象,在方法内部对异常类型进行判断,然后尝试生成对应的ModelAndView对象,if(null) 则逐个执行直到返回一个ModelAndView对象

此类必须声明到Spring中去,让Spring管理他。在applicationContext.xml中增加其内容

  • 初始化RequestToViewNameTranslator

当Controller处理器方法没有返回一个View对象or逻辑视图名称,并且该方法中没有直接网response的输出流里, 写数据时,Spring就会采用约定好的方式提供一个逻辑视图名称
通过RequestToViewNameTranslator的getViewName方法实现,其支持的用户定义的属性

prefix
suffix
separator
stripLeadingSlash
stripTrailingSlash
stripExtension
urlDecode

当我们没有手动定义名为viewNameTranslator的Bean的时候,Spring会给我们提供默认的viewNameTranslator。

  • 初始化ViewResolvers

SpringMVC中当Controller将请求处理结果放入到ModelAndView中后,DIspatcherServlet会根据ModelAndVIew选择合适的视图进行渲染。其解决了选择合适的View,创建View对象。

<bean class="org.Springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/WEB-INF/views/">
<property name="suffix" value=".jsp"/>
</bean>
  • 初始化FlashMapManager
    Flashattributes提供给了一个请求存储属性,可供其他请求使用,在使用重定向时候非常必要,post/redirect/get模式。flash attributes在重定向之前暂存(就像存在session中)以便重定向后还有使用,并立即删除。

整体小结

  • web.xml先配置ContextLoaderListener-----获取param----->ServletContextListener
  • ServletContextListener 初始化WebApplicationContext实例并存至ServletContext中
  • ContextLoaderListener为辅助功能,用于创建WebApplicationContext类型实例,真正逻辑实现在DispatcherServlet
  • ContextLoader 静态代码块,读取属性文件,提取将要实现的WebApplicationContext接口实现类
  • 接下来
  • DispatcherServlet ->FrameworkServlet ->HttpServletBean,ApplicationContextAware
    HttpServletBean -> Httpservlet ->重写其init方法
  • inti方法->servletBean初始化->FrameworkServlet->覆盖initServletBean(对WebApplicationContext进一步初始化)—关键初始化–>initWebApplicationContext()
  • initWebApplicationContext()-----寻找or创建对应的WebApplicationContext实例 ------调用onRefresh()进行配置文件加载
  • onRefresh刷新Spring在Web功能实现的全局变量,如果没有手动配置bean--------------------- DispatcherServlet静态代码块初始化本身对应的init**属性
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值