spring源码之初始化

   前面我们探讨了struct的源码及其工作原理。本篇开始,我们将来探讨spring的源码及其工作原理。struct2的核心对象是dispacher,在struct2初始化的时候,初始化一个dispacher对象,当有request请求来的时候,调用dispacher的方法来处理和执行request请求。而spring的核心是ioc容器,如果给我们设计,那spring的初始化是不是都要初始化一个容器对象?然后在运行时就从容器中取出对象?就好比于struct2的container容器,在初始化的的把配置文件中配置的对象放入到容器中,然后在处理action请求的时候,通过@inject依赖注入。同样,spring在初始化的时候也会把配置中的对象放入到容器中(spring把容器称为beanFactory或者是applicationContext,下面会介绍这两个的区别),下面就让我们看看spring是不是这样实现的。

    那么spring是从哪里开始的呢?struct2用到了过滤器filter,而spring用到了监听器listener.

1.spring的入口

   web.xml中配置一个监听器listener-ContetLoaderListener.

<!--【代码清单】:在web.xml中配置监听器listener-->
<!-- 配置spring初始化监听器 -->
    <listener>
    <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>


   tomcat启动的时候,会自动运行配置的监听器ContextLoaderListenercontextInitialized()方法(这是因为ContextLoaderListener继承了ServletContextListener的接口,具体原理参考tomcat的运行机制)。那么,在这个方法中,spring到底干了些什么呢?我们来看看这个方法

2.spring初始化主要工作

//【代码清单】:contextInitialized()方法
publicvoid contextInitialized(ServletContextEvent event) {
    //创建一个ContextLoader
    this.contextLoader =createContextLoader();
    //初始化ioc容器
    this.contextLoader.initWebApplicationContext(event.getServletContext());
    }

    在这个方法中主要是实例化了一个contextLoader,然后通过这个contextLoader初始化一个webApplicationContext,这个webApplicationContext俗称就是spring的ioc容器。这里也论证了我们上面的猜想:spring的初始化,就是初始化一个IOC容器,当然,这个过程中,spring会把配置文件中的对象放入到容器,这里也会涉及到解析配置文件。

2.1.创建ContextLoader
①为什么需要ContextLoader?

   springioc容器有很多,不同的应用层面有不同的ioc服务,如接口的有BeanFactoyApplicationContext,继承这两个接口类又有很多,如继承applicationContext的有ConfigurableApplicationContextWebApplicationContext,而实现这些接口的类也有很多,如实现webApplicationContext的有xmlWebApplictationContext,实现ConfigurableApplicationContext的有FileSystemApplicationContext。这些都是针对不用应用层面提供的ioc服务。FileSystemApplicationContext是在文件目录下解析xml 文件,而xmlWebApplictationContext是在xml配置中获取xml路径并解析。

   针对这么多的ioc容器,tomcat启动的时候,到底初始化哪个ioc容器?这就需要一个类来对这些IOC容器进行管理,并决定在tomcat启动的时候,决定加载哪个ioc容器,这个类就是ContextLoader

   实际web项目开发是在web.xml中配置applicationContext.xml文件的路径。因此本文以webapplicationContext作主要解析。

 

<!--代码清单:web.xml中配置 applicationContext.xml路径-->
<context-param>
       <param-name>contextConfigLocation</param-name>
       <param-value>classpath:applicationContext*.xml</param-value>
</context-param>


classpath:表示从类路劲加载配置文件,详细讲解,请看spring的策略管理。

 

②什么是ContextLoader
//【代码清单】:ContextLoader
publicclass ContextLoader {
//初始化IOC容器
  WebApplicationContext initWebApplicationContext();
//获得当前ioc容器
  WebApplicationContext getCurrentWebApplicationContext();
//关闭ioc容器
  void closeWebApplicationContext
}


    ContextLoader主要提供了初始化ioc容器,获取当前ioc容器,和关闭当前ioc容器的几个方法。通俗地说,contextLoader就是一个管理ioc容器的类。我们可以创建自己的ContextLoader来加载自己想要的ioc容器。

③怎么创建ContextLoader?
//【代码清单】:createContextLoader()
protected ContextLoadercreateContextLoader() {
        //实例化一个ContextLoader对象
       return new ContextLoader();
}

   createContextLoader()方法中主要是new了一个ContextLoader实例。

 

2.2.初始化IOC容器

  初始化IOC容器主要是调用了ContextLoader. initWebApplicationContext()方法。

 

//【代码清单】:initWebApplicationContext()
    public WebApplicationContextinitWebApplicationContext
(ServletContext servletContext)
           throwsIllegalStateException, BeansException {
// 因为webApplicationContext最后要保存到request的*.ROOT常量中,所以这里先判断这个这个变量是否为null,为null则抛出异常。此处代码省略。
       try {
           // 1.获取根上下文,
       ApplicationContextparent = loadParentContext(servletContext);
           //2. 创建webApplicationContext,完成初始化后添加到根上下文中
    this.context =createWebApplicationContext(servletContext, parent);
     //3保存到servletContext.ROOT中
    servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context);
     // 4.添加到当前线程中
    currentContextPerThread.put(Thread.currentThread().getContextClassLoader(), this.context);
      // 此处异常信息,代码省略。
           returnthis.context;
       }


1)获取根上下文
//【代码清单】:loadParentContext()

LOCATOR_FACTORY_SELECTOR_PARAM = "locatorFactorySelector";
LOCATOR_FACTORY_KEY_PARAM = "parentContextKey";

protected ApplicationContext loadParentContext(ServletContextservletContext)throws BeansException {
  // 1.从ServletContext获取上面那2个常量值
     servletContext.getInitParameter();
  // 2.如果parentContextKey的值为空,则返回一个空的applicationContext,否则
    if (parentContextKey!= null) {
 //3.先判断locatorFactorySelector是否为null,如果是null,则用默认值classpath*:beanRefContext.xml,如果不为null,则根据locatorFactorySelector实例化一个BeanFactoryLocator
           BeanFactoryLocatorlocator =
ContextSingletonBeanFactoryLocator.getInstance(locatorFactorySelector);
  // 4.然后用BeanFactoryLocator. useBeanFactory()方法实例化一个parentContextRef .ApplicationContext是parentContextRef的一个field.
 parentContextRef.applicationContext=
 new ClassPathXmlApplicationContext(parentContextKey)
this.parentContextRef = locator.useBeanFactory(parentContextKey);
//5. return parentContextRef.applicationContext
parentContext = (ApplicationContext)
this.parentContextRef.getFactory();
       }
       return parentContext;
    }
2)创建webApplicationContext
//【代码清单】:createWebApplicationContext()
    protectedWebApplicationContext createWebApplicationContext(
           ServletContextservletContext, ApplicationContext parent) throws BeansException {
   //1.判断载入的applicationContext类型,如果没有则默认使用继承WebApplicationContext接口的类。
       ClasscontextClass = determineContextClass(servletContext);
//判断contextClass是否是ConfigurableWebApplicationContext.class类型,不是则抛出异常。此处代码省略。ConfigurableWebApplicationContext是一个继承了webApplicationContext和ConfiguteApplicationContext接口的接口。、
 
  //2.根据contextClass实例化一个ConfigurableWebApplicationContext
       ConfigurableWebApplicationContextwac =
              (ConfigurableWebApplicationContext)BeanUtils.instantiateClass(contextClass);
  //3.添加根上下文
       wac.setParent(parent);
  //4.添加servletContext
       wac.setServletContext(servletContext);
  //5.获取web.xml中配置的contextConfigLocation参数
    wac.setConfigLocation(servletContext.getInitParameter(CONFIG_LOCATION_PARAM));
 //6.定制Context,这是一个什么也不做的方法,用于拓展,可以忽略
       customizeContext(servletContext,wac);
 //7.解析applicationContext.xml,完成初始化
       wac.refresh();
       return wac;
    }

①判断载入的根applicationContext类型
//【代码清单】:determineContextClass()
protected Class determineContextClass(ServletContextservletContext) throws ApplicationContextException {
  //1.获取我们在web.xml中配置的contextClass参数,这个参数可以可以决定我们需要载入的applicationContext类型
       StringcontextClassName = servletContext.getInitParameter(CONTEXT_CLASS_PARAM);
       if (contextClassName!= null) {
           try {
              return ClassUtils.forName(contextClassName);
           }
       }
       else {
//2.如果没配置有,则使用继承webApplicationContext接口的Class类
           contextClassName= defaultStrategies.getProperty(WebApplicationContext.class.getName());
           try {
              return ClassUtils.forName(contextClassName,ContextLoader.class.getClassLoader());
           }
       }
    }
②实例化一个实现ConfigurableWebApplicationContext的类
//【代码清单】:instantiateClass(contextClassName)
    publicstatic Object instantiateClass(Class clazz) throws BeanInstantiationException {
       try {
   //重载instantiateClass方法
           return instantiateClass(clazz.getDeclaredConstructor((Class[])null), null);
       }
    }

  instantiateClasscontextClassName)方法中重载了instantiateClass方法

//【代码清单】:instantiateClass(constructor,args)
    publicstatic Object instantiateClass(Constructor ctor, Object[] args) throws BeanInstantiationException {
       try {
           //setAccessible(true)
           ReflectionUtils.makeAccessible(ctor);
          //实例化
           return ctor.newInstance(args);
       }
    }
③添加根上下文
//【代码清单】:setParent()
publicvoid setParent(ApplicationContextparent) {
       this.parent = parent;
    }
④设置servletContext
//【代码清单】:setServletContext()
publicvoid setServletContext(ServletContext servletContext) {
       this.servletContext = servletContext;
    }
⑤获取配置文件信息
CONFIG_LOCATION_PARAM = "contextConfigLocation";
servletContext.getInitParameter(CONFIG_LOCATION_PARAM)

  获的web.xml配置contextConfigLocation的参数


  <!--【代码清单】:web.xml的contextConfigLocation配置参数-->
    <context-param>
       <param-name>contextConfigLocation</param-name>
       <param-value>classpath:applicationContext*.xml</param-value>
    </context-param>

  处理参数

//【代码清单】:处理参数
publicvoid setConfigLocation(String location) {
       //String CONFIG_LOCATION_DELIMITERS = ",; \t\n";
        //“制表符(‘\t’)”、“换行符(‘\n’)”、“回车符(‘\r’)”
       setConfigLocations(StringUtils.tokenizeToStringArray(location,CONFIG_LOCATION_DELIMITERS));
    }

   tokenizeToStringArray()方法是处理“classpath:applicationContext*.xml”这个字符串成一个数组,然后调用setConfigLocations(String[]locations)方法。

//【代码清单】:tokenizeToStringArray()
    publicstatic String[]tokenizeToStringArray(
           Stringstr, String delimiters, boolean trimTokens, boolean ignoreEmptyTokens) {
       StringTokenizerst = new StringTokenizer(str, delimiters);
       Listtokens = new ArrayList();
       while(st.hasMoreTokens()) {
             //分割
           Stringtoken = st.nextToken();
            //前后去掉空格。此处代码省略
             if (!ignoreEmptyTokens || token.length() > 0) {
                 //添加到数组
              tokens.add(token);
           }
       }
       //转字符数组
       returntoStringArray(tokens);
    }

   把配置文件信息,保存到容器中

//【代码清单】:setConfigLocations(String[] locations)
//configLocations只是一个字符数组
private String[] configLocations;
publicvoid setConfigLocations(String[] locations) {
       if (locations != null) {
           this.configLocations = newString[locations.length];
           for (int i = 0; i <locations.length; i++) {
        //循环遍历,赋给configLocations
           this.configLocations[i] =resolvePath(locations[i]).trim();
           }
       }
       else {
           this.configLocations = null;
       }
    }
⑥解析applicationContext.xml,完成初始化

 AbstractRefreshableWebApplicationContext是实现了

ConfigurableWebApplicationContext接口的类,但是

AbstractRefreshableWebApplicationContext 并没有refresh()方法。当我们一层一层找它的父类的时候,在父类AbstractApplicationContext中找到了refresh()这个方法。

//【代码清单】:AbstractApplicationContext.refresh()
    publicvoid refresh() throws BeansException,IllegalStateException {
       synchronized (this.startupShutdownMonitor) {
           // 1.容器启动的预先准备,记录容器启动的时间和标记.
           prepareRefresh();
           //2.创建BeanFactory,如果已有就销毁,没有就创建.此类实现了对BeanDefinition的装载
           ConfigurableListableBeanFactorybeanFactory = obtainFreshBeanFactory();
           //3.配置BeanFactory标准上下文特性,如类装载器、postProcesser等
           prepareBeanFactory(beanFactory);
           try {
              // 4.在bean被装载后,提供一个修改BeanFactory的入口
              postProcessBeanFactory(beanFactory);
              // 5.调用postProceessBeanFactory
              invokeBeanFactoryPostProcessors(beanFactory);
              //6.注册用于拦截bean创建过程中的BeanPostProcessors
              registerBeanPostProcessors(beanFactory);
              // 7.Initialize message source for this context.
              initMessageSource();
              // 8.Initialize event multicaster for this context.
              initApplicationEventMulticaster();
       //9.Initialize other special beans in specific contextsubclasses.
              onRefresh();
              //10. 注册监听器
              registerListeners();
    //11.完成容器的初始化,里面的preInstantiateSingletons完成单例对象的创建
              finishBeanFactoryInitialization(beanFactory);
              // 12.Last step: publish corresponding event.
              finishRefresh();
           }
       }
    }


3.总结

  综上所述,spring的初始化其实就是创建一个configurateWebApplicationContext对象,然后把这个对象放到servletContext和线程中。而创建这个webApplcationContext的过程,其实就是获取配置文件并解析这个配置文件的过程。上面讨论了spring已经从web.xml中获取了applicationContext.xml配置的文件信息,下篇博文,我们将探讨,refresh()这个方法是如何解析配置文件和完成初始化的。