Spring MVC 上下文(ApplicationContext)初始化入口

时间:2022-01-22 19:48:58

Spring 常用上下文容器有哪些

ApplicationContext

  1. ClassPathXmlApplicationContext

    ApplicationContext context = new ClassPathXmlApplicationContext(applicationContext.xml");
  2. AnnotationConfigApplicationContext

    AbstractApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);

应该来说是很少使用这种方法用于生产开发,常常在学习Spring做demo的时候会使用到。更有可能出现在Spring项目的代码测试,不过呢,单元测试的框架(比如 JUnit)已经提供了简单的方式,也就不建议直接实例化上下文。因为实例化一个上下文还得要做维护,再者现在常用的是基于Web的开发,也就是常用 Spring MVC。如果没有基于 Web 应用的开发,那么很可能就是一个小的程序,类似于提供给第三方使用的 SDK 代码,那么使用 Spring 感觉会太重,最重要是自己要维护一个 ApplicationContext,感觉不是那么方便

Web ApplicationContext

以下两个是针对 Spring MVC 的应用上下文。WebApplicationContext 实例会在应用启动之后由Spring实例化并维护,而平常在学习的时候也往往不会自己去实例化 WebApplicationContext 对象,因为将因为部署到web容器(比如 tomcat),启动之后就可以直接测试了。单元测试有专门的框架处理(比如 JUnit),可以很简单的实现测试。web项目的开发关键点在于让web容器初始化之后提醒Spring ApplicationContext 初始化,例如 tomcat 的 ServletContext 会维护一个 WebApplicationContext。

  1. XmlWebApplicationContext

    @Test
    public void handlerBeanNotFound() throws Exception {
     MockServletContext sc = new MockServletContext("");
     XmlWebApplicationContext root = new XmlWebApplicationContext();
     root.setServletContext(sc);
     root.setConfigLocations(new String[] {"/org/springframework/web/servlet/handler/map1.xml"});
     root.refresh();
     XmlWebApplicationContext wac = new XmlWebApplicationContext();
     wac.setParent(root);
     wac.setServletContext(sc);
     wac.setNamespace("map2err");
     wac.setConfigLocations(new String[] {"/org/springframework/web/servlet/handler/map2err.xml"});
     try {
         wac.refresh();
         fail("Should have thrown NoSuchBeanDefinitionException");
     }
     catch (FatalBeanException ex) {
         NoSuchBeanDefinitionException nestedEx = (NoSuchBeanDefinitionException) ex.getCause();
         assertEquals("mainControlle", nestedEx.getBeanName());
     }
    }
  2. AnnotationConfigWebApplicationContext:没找到合适的示例代码

ApplicationContext 入口

这种方式需要自己维护 ApplicationContext 实例,也就是开发使用的时候 new ApplicationContext,入口自己控制

WebApplicationContext 入口

web.xml 配置和 全注解 配置启动会有一些差别。

web.xml 配置

web.xml 中关于Spring的配置项,也非常常见。

<?xml version="1.0" encoding="UTF-8"?>
<web-app version="3.0" xmlns="http://java.sun.com/xml/ns/javaee"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd">

    <!-- 上下文参数,在监听器中被使用,实际就是key-value,key=contextConfigLocation 写死 -->
    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>
            classpath:applicationContext.xml
        </param-value>
    </context-param>

    <!-- 监听器配置,初始化 WebApplicationContext -->
    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>

    <!-- 前端控制器配置 -->
    <servlet>
        <servlet-name>dispatcher</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>classpath:applicationContext-mvc.xml</param-value>
        </init-param>
        <load-on-startup>1</load-on-startup>
    </servlet>

    <servlet-mapping>
        <servlet-name>dispatcher</servlet-name>
        <url-pattern>/</url-pattern>
    </servlet-mapping>

</web-app>

WebApplicationContext 的初始化调用链路:ContextLoaderListener.contextInitialized-->ContextLoader.initWebApplicationContext-->ContextLoader.createWebApplicationContext-->ContextLoader.determineContextClass-->ContextLoader.determineContextClass。

determineContextClass 源码如下:

protected Class<?> determineContextClass(ServletContext servletContext) {
    // 自定义的 ApplicationContext
    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 {
        // 缺省为 XmlWebApplicationContext
        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);
        }
    }
}

默认 WebApplicationContext

根据上面的 web.xml 配置是没有指定 ApplicationContext 的实现的,所以会执行:contextClassName = defaultStrategies.getProperty(WebApplicationContext.class.getName());。defaultStrategies 的实现如下:

/**
* Name of the class path resource (relative to the ContextLoader class)
* that defines ContextLoader's default strategy names.
*/
private static final String DEFAULT_STRATEGIES_PATH = "ContextLoader.properties";
private static final Properties defaultStrategies;

static {
    // 读取文件 ContextLoader.properties 中的配置
    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.properties 中的配置完成,里边的内容如下:

org.springframework.web.context.WebApplicationContext=org.springframework.web.context.support.XmlWebApplicationContext

所以呢,通过 web.xml 配置Spring MVC默认的上下文是: XmlWebApplicationContext

指定 WebApplicationContext

如果在 web.xml 中配置 contextClass 属性,例如下面的方式,摘自 *:How to register Spring @Configuration annotated class instead of applicationContext.xml file in web.xml?

<web-app>
  <!-- Configure ContextLoaderListener to use AnnotationConfigWebApplicationContext
       instead of the default XmlWebApplicationContext -->
  <context-param>
      <param-name>contextClass</param-name>
      <param-value>
          org.springframework.web.context.support.AnnotationConfigWebApplicationContext
      </param-value>
  </context-param>

  <!-- Configuration locations must consist of one or more comma- or space-delimited
       fully-qualified @Configuration classes. Fully-qualified packages may also be
       specified for component-scanning -->
  <context-param>
      <param-name>contextConfigLocation</param-name>
      <param-value>com.acme.AppConfig</param-value>
  </context-param>

  <!-- Bootstrap the root application context as usual using ContextLoaderListener -->
  <listener>
      <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
  </listener>

  <!-- Declare a Spring MVC DispatcherServlet as usual -->
  <servlet>
      <servlet-name>dispatcher</servlet-name>
      <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
      <!-- Configure DispatcherServlet to use AnnotationConfigWebApplicationContext
           instead of the default XmlWebApplicationContext -->
      <init-param>
          <param-name>contextClass</param-name>
          <param-value>
              org.springframework.web.context.support.AnnotationConfigWebApplicationContext
          </param-value>
      </init-param>
      <!-- Again, config locations must consist of one or more comma- or space-delimited
           and fully-qualified @Configuration classes -->
      <init-param>
          <param-name>contextConfigLocation</param-name>
          <param-value>com.acme.web.MvcConfig</param-value>
      </init-param>
  </servlet>

  <!-- map all requests for /app/* to the dispatcher servlet -->
  <servlet-mapping>
      <servlet-name>dispatcher</servlet-name>
      <url-pattern>/app/*</url-pattern>
  </servlet-mapping>
</web-app>

感觉很神奇,但是也很麻烦的样子。我是不建议混合 web.xml 配置启动和全注解启动,乱且不好看懂。

全注解配置

全注解方式一般是实现WebApplicationInitializer或者通过继承AbstractAnnotationConfigDispatcherServletInitializerAbstractAnnotationConfigDispatcherServletInitializerWebApplicationInitializer的实现类。想知道全注解配置下tomcat如何Spring IOC怎样被加载,可以阅读篇文章Spring揭秘--寻找遗失的web.xml

全注解方式配置常用类似如下的代码:

import org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer;

/**
 * @author imssbora
 */
public class MyWebAppInitializer extends 
   AbstractAnnotationConfigDispatcherServletInitializer{

   @Override
   protected Class<?>[] getRootConfigClasses() {
      return new Class[]{RootConfig.class};
   }

   @Override
   protected Class<?>[] getServletConfigClasses() {
      return new Class[]{WebConfig.class};
   }

   @Override
   protected String[] getServletMappings() {
      return new String[]{"/"};
   }
}

启动路径:SpringServletContainerInitializer.onStartup-->AbstractContextLoaderInitializer.onStartup-->AbstractContextLoaderInitializer.registerContextLoaderListener-->AbstractAnnotationConfigDispatcherServletInitializer.createRootApplicationContext。

createRootApplicationContext 源码如下:

protected WebApplicationContext createRootApplicationContext() {
    Class<?>[] configClasses = this.getRootConfigClasses();
    if (!ObjectUtils.isEmpty(configClasses)) {
        AnnotationConfigWebApplicationContext rootAppContext = new AnnotationConfigWebApplicationContext();
        rootAppContext.register(configClasses);
        return rootAppContext;
    } else {
        return null;
    }
}

可以看出实例化了 AnnotationConfigWebApplicationContext 对象。

参考

关于web.xml配置启动,Spring 的加载流程网络上资料很多,所以有可能会有很多重复的,选择一遍排版不错,写得相对完整的,编写时间比较新的:Spring MVC 启动过程源码分析

排版精美,写得很棒的文章:Spring揭秘--寻找遗失的web.xml

从 * 上找到的资料:How to register Spring @Configuration annotated class instead of applicationContext.xml file in web.xml?

我的博客即将搬运同步至腾讯云+社区,邀请大家一同入驻:https://cloud.tencent.com/developer/support-plan?invite_code=38db6z9qc328s

时间:2018.9.3 16:20