Spring MVC源码分析 之 Root WebApplicationContext 容器

1、在开始看具体的源码实现之前,我们先一起来看看现在“陌生”的 web.xml 配置。代码如下:
    在排列顺序上一点要按照<context-param>、<filter><filter-mapping>、<listener>、<servlet><servlet-mapping>
    这样的顺序进行配置不然会报错

<web-app>
  <display-name>Archetype Created Web Application</display-name>

  <context-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>classpath:applicationContext.xml</param-value>
  </context-param>
  <!--会初始化一个Root Spring WebApplicationContext,配置文件即为name为contextConfigLocation的<context-param>的值-->
  <listener>
    <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
  </listener>
  <!--是一个HttpServlet的对象,出了拦截*.do的请求外,也会初始化一个属于它的Spring WebApplicationContext容器,
  并以上面的root容器作为父容器,为什么要这样呢?因为有可能会创建多个类似此处的容器,同时以root容器作为父容器,
  当然一般不会如此-->
  <servlet>
    <servlet-name>dispatcherServlet</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <init-param>
      <param-name>contextConfigLocation</param-name>
      <param-value>classpath:springmvc.xml</param-value>
    </init-param>
    <load-on-startup>1</load-on-startup>
  </servlet>
  <servlet-mapping>
    <servlet-name>dispatcherServlet</servlet-name>
    <url-pattern>*.do</url-pattern>
  </servlet-mapping>
</web-app>

2、ContextLooaderListener extends ContextLoader implements ServletContextListener

2.1构造方法
    因为父类ContextLoader有两个构造方法所以必须定义如下两个
    public ContextLoaderListener() {
    }
    传入一个WebApplicationContext,这样就不需要再创建一个新的了
    public ContextLoaderListener(WebApplicationContext context) {
        super(context);
    }

2.2 contextInitialized、contextDestroyed 方法
    实现ServletContextListener的两个方法,实际上是分别调用ContextLoader的initWebApplicationContext,closeWebApplicationContext进行
    初始化WebAoolicationContext和关闭其的动作
    @Override
    public void contextInitialized(ServletContextEvent event) {
        // 初始化 WebApplicationContext
        initWebApplicationContext(event.getServletContext());
    }

    @Override
    public void contextDestroyed(ServletContextEvent event) {
        closeWebApplicationContext(event.getServletContext());
        ContextCleanupListener.cleanupAttributes(event.getServletContext());
    }
    

3、ContextLoader
   真正实现初始化和销毁WebApplicationContext容器的逻辑的类,即2.2提到的initWebApplicationContext和closeWebApplicationContext
3.1、重要属性
    //这个配置文件中存在的键值对如下
    //org.springframework.web.context.WebApplicationContext=org.springframework.web.context.support.XmlWebApplicationContext
    //这意味着如果不在<context-param>中指定WebApplicationContext类型,就是用这边的XmlWebApplicationContext,下面会分析到,见3.3
    private static final String DEFAULT_STRATEGIES_PATH = "ContextLoader.properties";
    //默认的配置 Properties 对象
    private static final Properties defaultStrategies;

    static {
        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());
        }
    }
    //Root WebApplicationContext 对象
    private WebApplicationContext context;
    //两个构造方法,其中一个传入了applicationContext对象
    public ContextLoader() {
    }

    public ContextLoader(WebApplicationContext context) {
        this.context = context;
    }
    
3.2、initWebApplicationContext

    public WebApplicationContext initWebApplicationContext(ServletContext servletContext) {
        // <1> 若已经存在 ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE 对应的 WebApplicationContext 对象,则抛出 IllegalStateException 异常。
        // 例如,在 web.xml 中存在多个 ContextLoader 。
        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!");
        }

        // <2> 打印日志
        servletContext.log("Initializing Spring root WebApplicationContext");
        Log logger = LogFactory.getLog(ContextLoader.class);
        if (logger.isInfoEnabled()) {
            logger.info("Root WebApplicationContext: initialization started");
        }

        // 记录开始时间
        long startTime = System.currentTimeMillis();

        try {
            // 如果属性中的WebApplicationContext为null,则进行创建初始化
            if (this.context == null) {
                // 初始化 context ,即创建 context 对象,见3.3
                this.context = createWebApplicationContext(servletContext);
            }
            // 如果是 ConfigurableWebApplicationContext 的子类,如果未刷新,则进行配置和刷新
            if (this.context instanceof ConfigurableWebApplicationContext) {
                ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) this.context;
                if (!cwac.isActive()) { // 如果此applicationContext没有执行刷新(激活),也就是未执行refresh()方法,继续向下;
                    if (cwac.getParent() == null) { // 若无父容器,则进行加载和设置。
                        //loadParentContext()方法返回一个null,修饰符为protected,需要子类去实现
                        ApplicationContext parent = loadParentContext(servletContext);
                        cwac.setParent(parent);
                    }
                    // 配置 WebApplicationContext 对象,并进行刷新,见3.4
                    configureAndRefreshWebApplicationContext(cwac, servletContext);
                }
            }
            // 记录在 servletContext 中,因此在此方法开头会进行判断,只能存在一个Root WebApplicationContext容器,如果定义了多个ContextLoader,就会报错
            servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context);

            // 记录到 currentContext 或 currentContextPerThread 中,这两个变量会在销毁的时候用到
            ClassLoader ccl = Thread.currentThread().getContextClassLoader();
            if (ccl == ContextLoader.class.getClassLoader()) {
                currentContext = this.context;
            } else if (ccl != null) {
                currentContextPerThread.put(ccl, this.context);
            }

            // 打印日志
            if (logger.isInfoEnabled()) {
                long elapsedTime = System.currentTimeMillis() - startTime;
                logger.info("Root WebApplicationContext initialized in " + elapsedTime + " ms");
            }

            // 返回 context
            return this.context;
        } catch (RuntimeException | Error ex) {
            // 当发生异常,记录异常到 WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE 中,不再重新初始化。
            logger.error("Context initialization failed", ex);
            servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, ex);
            throw ex;
        }
    }

3.3、createWebApplicationContext
    // ContextLoader.java
    protected WebApplicationContext createWebApplicationContext(ServletContext sc) {
        // <1> 获得 context 的类
        Class<?> contextClass = determineContextClass(sc);
        // <2> 判断 context 的类,是否符合 ConfigurableWebApplicationContext 的类型
        if (!ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass)) {
            throw new ApplicationContextException("Custom context class [" + contextClass.getName() +
                    "] is not of type [" + ConfigurableWebApplicationContext.class.getName() + "]");
        }
        // <3> 创建 context 的类的对象
        return (ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);
    }
    
    protected Class<?> determineContextClass(ServletContext servletContext) {
        //从ServletContext中读取属性名为contextClass的值,用于初始化,即<context-param><param-name>contextClass</param-name><param-value>·······类名········</param-value></context-param>
        String contextClassName = servletContext.getInitParameter("contextClass");
        //如果不为空的话就加载我们在web.xml中设置的类
        if (contextClassName != null) {
            try {
                return ClassUtils.forName(contextClassName, ClassUtils.getDefaultClassLoader());
            } catch (ClassNotFoundException var4) {
                throw new ApplicationContextException("Failed to load custom context class [" + contextClassName + "]", var4);
            }
        } else {
            //如果我们没有配置的话,就从defaultStrategies获取org.springframework.web.context.support.XmlWebApplicationContext进行加载,初始化
            //在3.1处提到了
            contextClassName = defaultStrategies.getProperty(WebApplicationContext.class.getName());

            try {
                return ClassUtils.forName(contextClassName, ContextLoader.class.getClassLoader());
            } catch (ClassNotFoundException var5) {
                throw new ApplicationContextException("Failed to load default context class [" + contextClassName + "]", var5);
            }
        }
    }

3.4 configureAndRefreshWebApplicationContext

    public static final String CONTEXT_ID_PARAM = "contextId";

    public static final String CONFIG_LOCATION_PARAM = "contextConfigLocation";

    protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac, ServletContext sc) {
        //  如果 wac 使用了默认编号,则重新设置 id 属性
        if (ObjectUtils.identityToString(wac).equals(wac.getId())) {
            // 情况一,使用 contextId 属性,在web.xml中配置<context-param>
            String idParam = sc.getInitParameter(CONTEXT_ID_PARAM);
            if (idParam != null) {
                wac.setId(idParam);
            // 情况二,自动生成
            } else {
                wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX +
                        ObjectUtils.getDisplayString(sc.getContextPath()));
            }
        }

        //设置 context 的 ServletContext 属性
        wac.setServletContext(sc);
        // 设置 context 的配置文件地址
        String configLocationParam = sc.getInitParameter(CONFIG_LOCATION_PARAM);
        if (configLocationParam != null) {
            wac.setConfigLocation(configLocationParam);
        }
        ConfigurableEnvironment env = wac.getEnvironment();
        if (env instanceof ConfigurableWebEnvironment) {
            ((ConfigurableWebEnvironment) env).initPropertySources(sc, null);
        }

        //执行自定义初始化 context
        customizeContext(sc, wac);

        // 刷新 context ,执行初始化
        wac.refresh();
    }

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值