标准 专业
多元 极客

Spring上古实验室(1)——Spring容器(1)

作为一个Java程序员,大家都知道,如果一个项目集成了Spring,那么,项目中必然会有一个applicationContext.xml,那么这个applicationContext.xml是干什么的呢?你会回答说,写一些需要的配置。

如果你能听懂这些,接下来的内容我们还能聊得下去。

作为一个Web项目,首先我们肯定会有一个web.xml,这个web.xml是做什么的呢?是用于容器解析并配置上下文环境的,比如说Tomcat用Host容器解析web.xml,我们来看一下集成Spring部分的代码。

<!-- 加载spring的配置文件 -->
<context-param>
     <param-name>contextConfigLocation</param-name>
        <param-value>
            classpath:applicationContext.xml;
        </param-value>
    </context-param>
    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
         </listener>
</context-param>

很常见的样子,但是我们经常对此一笑而过,不留半分思考,那么今天我们就来看看ContextLoaderListener类:

public class ContextLoaderListener extends ContextLoader implements ServletContextListener {
     public ContextLoaderListener() {
     }
     public ContextLoaderListener(WebApplicationContext context) {
          super(context);
     }
     @Override
     public void contextInitialized(ServletContextEvent event) {
          initWebApplicationContext(event.getServletContext());
     }
     @Override
     public void contextDestroyed(ServletContextEvent event) {
          closeWebApplicationContext(event.getServletContext());
          ContextCleanupListener.cleanupAttributes(event.getServletContext());
     }
}

你可能说,我有点头绪了,这个contextInitialized可能要搞事啊。的确是,容器启动后,会调用ContextLoaderListener#contextInitialized(),来进行相关操作。但是,并不是说,我们随便写一个类,放到web.xml,容器就会调用,仔细看,这个类是重写的,是来自ServletContextListener接口,这也就说,如果我们想写一个web项目启动时的监听器,首先需要实现ServletContextListener接口。

我们接着看contextInitialized()方法,调用了父类ContextLoader#initWebApplicationContext()方法:

public class ContextLoader {
     public WebApplicationContext initWebApplicationContext(ServletContext servletContext) {
          ...
          if (this.context == null) {
               // 创建容器
               this.context = createWebApplicationContext(servletContext);
          }
          if (this.context instanceof ConfigurableWebApplicationContext) {
               ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) this.context;
               if (!cwac.isActive()) {
                    // 容器还没有被初始化
                    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);
               }
          }
          ...
     }
}

如果上下文为空,那么我们将创建一个上下文容器。创建好上下文后,便进行容器的配置与初始化,继续走:

public class ContextLoader {
     protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac, ServletContext sc) {
          if (ObjectUtils.identityToString(wac).equals(wac.getId())) {
               // 获取ID
               String idParam = sc.getInitParameter(CONTEXT_ID_PARAM);
               if (idParam != null) {
                    wac.setId(idParam);
               }
               else {
                    // 生成ID
                    wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX +
                              ObjectUtils.getDisplayString(sc.getContextPath()));
               }
          }
          wac.setServletContext(sc);
          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);
          }
          customizeContext(sc, wac);
          // Spring容器初始化核心:刷新
          wac.refresh();
     }
}

领号,给入场券后,我们终于在最后看到了Spring容器初始化的核心:refresh()

public abstract class AbstractApplicationContext extends DefaultResourceLoader
          implements ConfigurableApplicationContext, DisposableBean {
     @Override
     public void refresh() throws BeansException, IllegalStateException {
          synchronized (this.startupShutdownMonitor) {
               // 容器启动之前的准备工作
               prepareRefresh();
               // 很关键:创建工厂
               ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
               // 配置工厂
               prepareBeanFactory(beanFactory);
               try {
                    // 设置工厂的后处理器
                    postProcessBeanFactory(beanFactory);
                    // 调用工厂的后处理器
                    invokeBeanFactoryPostProcessors(beanFactory);
                    // 注册工厂的后处理器
                    registerBeanPostProcessors(beanFactory);
                    // 初始化上下文中的消息源
                    initMessageSource();
                    // 初始化应用时间广播器
                    initApplicationEventMulticaster();
                    // 默认空逻辑,请自行进行表演
                    onRefresh();
                    // 注册应用监听器
                    registerListeners();
                    // 很关键:完成工厂的初始化
                    finishBeanFactoryInitialization(beanFactory);
                    // 完成初始化,发布刷新事件,监听事件可以进场了
                    finishRefresh();
               }
               ...
          }
     }
}

为了增加篇幅文字,我们继续BB一些。

  • prepareRefresh()。记录下时间,状态等信息。
  • obtainFreshBeanFactory()。核心,很关键,比如我们applicationContext.xml的解析,Bean注册等。
  • prepareBeanFactory(beanFactory。各种设置,提供了标准上下文环境元素。
  • BeanProcessor。这里有三个关于后置处理的方法,再未来的篇幅中,我们会介绍后置处理的实际应用,到时候你会恍然大悟。
  • initMessageSource()。初始化上下文中的消息源,可能你会往什么JMS方向想,但是我感觉你想歪了,目前作者仅是在国际化中使用过MessageSource接口。
  • initApplicationEventMulticaster()。初始化上下文中的事件机制,作者认为这是一个异步发布事件的驱动,如果你可以自力更生,Spring容器满足你的需求。如果你不行,那么Spring会自动为你配置一个SimpleApplicationEventMulticaster事件驱动。
  • onRefresh()。名曰初始化一些特殊的Bean,其实就是用户自定义的Bean。
  • registerListeners()。上面既然有异步发送事件的模型,那么肯定要注册监听者。
  • finishBeanFactoryInitialization(beanFactory)。核心,很关键,关键事件getBean()
  • finishRefresh()。容器初始化结束了,广而告之。

 

赞(0) 投币

评论 抢沙发

慕勋的实验室慕勋的研究院

码字不容易,路过请投币

支付宝扫一扫

微信扫一扫