spring源码之解析配置文件原理

    上篇博文提到,spring利用监听器实现在tomcat启动的时候实现初始化。在初始化的过程中,实例化一个webapplicationContext对象,然后利用这个对象的refresh()方法解析配置文件并完成初始化.这篇博文就详细来探讨下这个refresh()方法.

//代码清单:refresh()
    publicvoid refresh() throws BeansException,IllegalStateException {
       synchronized (this.startupShutdownMonitor) {
           // 1.容器启动的预先准备,记录容器启动的时间和标记.
           prepareRefresh();
           //2.创建BeanFactory,如果已有就销毁,没有就创建.此类实现了对BeanDefinition的装载
           ConfigurableListableBeanFactory beanFactory = 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.
                    }
    }

一.prepareRefresh

	protected void prepareRefresh() {
	       //记录容器启动的时间.
		this.startupDate = System.currentTimeMillis();
                //记录标记.
		synchronized (this.activeMonitor) {
			this.active = true;	}

		if (logger.isInfoEnabled()) {
			logger.info("Refreshing " + this);
		}
	}

   这个方法主要是记录容器启动的时间和一些日志信息,对系统并没什么影响。


二.obtainFreshBeanFactory

   这个方法就是本篇博文讨论的重点-解析配置文件applicationContext.xml

1.解析配置文件步骤

1.1.ioc容器创建步骤

①创建一个beanFactory(application)

beanFactory中添加一个读取配置信息的reader(DefaultBeanDefinitionDocumentReader)

③创建ioc配置文件抽象资源(Resouce

④读取配置信息reader.load()

 

    这个步骤不难理解。spring的核心是ioc容器,把配置文件中的对象放入容器中,那我们首先得有一个容器吧?所以这个步骤通俗地理解为:先创建一个盛放对象的容器(BeanFactory),然后创建一个解析器(reader),再获取配置文件信息(resouce),最后用解析器解析配置文件,把解析出来的对象放到这个容器中。

1.2.简单的代码实现

//1.先创建一个IOC容器
DefaultListabeBeanFactory factory=new DefaultListableBeanFactory();
//2.获取一个解析器
XmlBeanDefinitionReader reader=new XmlBeanDefinitionReader(factory);
//3.获取资源文件
ClassPathResouce res=new ClassPathResource("beans.xml");
//4.解析配置文件
reader.loadBeanDefinitions(res);

     正如struct2一样,解析配置文件,会把配置文件中的信息封装到不同的对象中。spring把不同的配置信息封装到beanDefinitions接口的不同实现类中。因为配置文件可能会配置在不同的地方,比如在web.xml,或者是要在file system中,又或者是在classpath目录下,针对这种情况,spring设计了一个resouce接口,来统一了这些路径。只要你实现了相应的resouce类,就能加载相应路径下的配置文件。具体请参考后面有关spring的策略模式的讲解。SpringBeanFacotry是一个类工厂,使用它来创建各种类型的Bean,最主要的方法就是getBean(String beanName),该方法从容器中返回特定名称的Bean。

      下面我们就来看看spring是如何实现上述的这些步骤的。


2.解析配置文件具体实现

【代码清单】:obtainFreshBeanFactory()
    protected ConfigurableListableBeanFactory obtainFreshBeanFactory() {
         //创建一个IOC容器beanFactory,并解析配置文件
       refreshBeanFactory();
       ConfigurableListableBeanFactory beanFactory = getBeanFactory();
       return beanFactory;
    }


2.1.创建一个IOC容器beanFactory

//【代码清单】:refreshBeanFactory()
protected final void refreshBeanFactory()throws BeansException {
        //判断beanFactory是否已存在
       if (hasBeanFactory()){
              //存在就销毁beans
           destroyBeans();
             //关闭工厂
           closeBeanFactory();
       }
       try {
           //不存在,就创建beanFactory, DefaultListableBeanFactory是beanFactory的实现类
           DefaultListableBeanFactorybeanFactory = createBeanFactory();
           customizeBeanFactory(beanFactory);
         //创建一个reader,默认是XmlBeanDefinitionReader,并解析,把对每个标签的解析结果都封装到相应的beanDefinition对象,然后把这个对象注册到DefaultListableBeanFactory
           loadBeanDefinitions(beanFactory);
           synchronized (this.beanFactoryMonitor) {
              this.beanFactory = beanFactory;
           }
       }
    }

 

   创建beanFactory,其实就是new 一个实现类。

//【代码清单】:createBeanFactory()
protected DefaultListableBeanFactory createBeanFactory() {
         //DefaultListableBeanFactory是beanFactory的实现类
		return new DefaultListableBeanFactory(getInternalParentBeanFactory());
	}


2.2.实例化一个解析器

//【代码清单】:loadBeanDefinitions(beanFactory)
    protectedvoid loadBeanDefinitions(DefaultListableBeanFactorybeanFactory) throws IOException {
       // 实例化一个reader
       XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory);
    //配置loader环境,也就是ioc配置文件抽象资源,ResourceLoder定位bean资源
       beanDefinitionReader.setResourceLoader(this);
       beanDefinitionReader.setEntityResolver(newResourceEntityResolver(this));
       // 空方法
       initBeanDefinitionReader(beanDefinitionReader);
      //载入bean信息,并对其进行处理
       loadBeanDefinitions(beanDefinitionReader);
    }

    这里创建了一个解析器xmlBeanDefinitonReader。看它的名字,就知道它的作用了。BeanDefinition是封装配置信息的对象,reader是读取,xml是配置文件,因此这个对象就是applictionContext.xml的解析器。下面的工作就是获取配置文件了。

2.3.获取配置文件resouce

//【代码清单】:loadBeanDefinitions(beanDefinitionReader)
protected void loadBeanDefinitions(XmlBeanDefinitionReader reader) throws BeansException, IOException {
               //获取web.xml中的contextConfigLocation的配置信息
		String[] configLocations = getConfigLocations();
		//循环遍历解析
		if (configLocations != null) {
			for (int i = 0; i < configLocations.length; i++) {
				reader.loadBeanDefinitions(configLocations[i]);
			}
		}
	}

    这个configLoactions是在监听器listener中就已经设置好的了

wac.setConfigLocation(servletContext.getInitParameter(CONFIG_LOCATION_PARAM));

 这里用get()方法,获取这一配置信息。因为,在web.xml中可以配置多个applicationContext.xml,所以这里循环遍历来解析。

<!-- 指定spring的配置文件,默认从web根目录寻找配置文件,classpath从类路径寻找,tomacat启动的时候,实例化spring容器 -->
	<context-param>
		<param-name>contextConfigLocation</param-name>
		<param-value>classpath:applicationContext*.xml</param-value>
	</context-param>

上面说到,spring设计了一个resouce接口来管理配置文件。所以下面还得根据这个路径,来获得一个resouce对象。

//【代码清单】:reader.loadBeanDefinitions(String)
    publicint loadBeanDefinitions(Stringlocation, Set actualResources) throws BeanDefinitionStoreException {
    //得到当前的ResourceLoader,默认使用DefaultResourceLoader
       ResourceLoader resourceLoader = getResourceLoader();
    //如果没有ResourceLoader则抛出异常,此处代码省略
       if (resourceLoader instanceof ResourcePatternResolver) {
// 这里处理我们在定义位置时使用的各种pattern,需要ResourcePatternResolver.
           try {
              Resource[] resources = ((ResourcePatternResolver) resourceLoader).getResources(location);
              //载入资源
              int loadCount =loadBeanDefinitions(resources);
              if (actualResources!= null) {
                  for (int i = 0; i <resources.length; i++) {
                     actualResources.add(resources[i]);
                  }
              }
              return loadCount;
           }
       }
       else {
           // 这里通过ResourceLoader来完成位置定位,一个位置定义转化为Resouce接口后可以供XmlBeanDefinitionReader来使用了
           Resource resource = resourceLoader.getResource(location);
         //这里就是解析配置开始了!!!
           int loadCount =loadBeanDefinitions(resource);
           if (actualResources!= null) {
              actualResources.add(resource);
           }
           return loadCount;
       }
    }

     这里是策略模式中,强制使用了ResouceLoader的实现类。根据web.xml中配置的字符串,来实现不同的resouce:如果是classpath开头,则实现classpathResouce,如果不是话,就尝试UrlResouce,再不然就是filesystemResouce

//【代码清单】:DefaultResourceLoader.getResource(location)
    public Resource getResource(String location) {
       //如果路径以classPath:开头,即类路径
       if(location.startsWith(CLASSPATH_URL_PREFIX)) {
           return new ClassPathResource(location.substring(CLASSPATH_URL_PREFIX.length()), getClassLoader());
       }
       else {
           try {
              // 如果是URL方式,使用UrlResource作为bean资源对象
              URLurl = new URL(location);
              return new UrlResource(url);
           }
           catch(MalformedURLException ex) {
           //如果都不是,那我们只能委托给子类由子类来决定使用什么样的资源对象了。如FiltSystemXmlApplication提供FileSystemResouce来完成从文件系统得到配置文件资源定义
              return getResourceByPath(location);
           }
       }
    }


2.4.解析抽象资源

//【代码清单】:loadBeanDefinitions(resource)
    public int loadBeanDefinitions(EncodedResourceencodedResource) throws BeanDefinitionStoreException {
       try {
            //这里通过Resource得到InputStream的IO流
           InputStream inputStream = encodedResource.getResource().getInputStream();
           try {
            //从InputStream中得到XML的解析源
              InputSourceinput Source = new InputSource(inputStream);
              if(encodedResource.getEncoding() != null) {
              inputSource.setEncoding(encodedResource.getEncoding());
              }
             //具体的解析和注册过程
              return  doLoadBeanDefinitions(inputSource, encodedResource.getResource());
           }
           finally {
              //关闭从Resouce中得到的IO流
              inputStream.close();
           }
       }
    }

    当得到一个resouce对象之后,通过重载loadBeanDefinitions(Resouce resouce)开始解析配置文件.通过resouce对象,获得配置文件袋io流,然后通过io流获得xml的解析源.获得xml的解析源之后,就能获得xml的document对象了

protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource)
			throws BeanDefinitionStoreException {
	try {
	int validationMode = getValidationModeForResource(resource);
	//通过xml源获得document对象
	Document doc = this.documentLoader.loadDocument(inputSource, getEntityResolver(), this.errorHandler, validationMode, isNamespaceAware());
		return registerBeanDefinitions(doc, resource);
	}
	//异常信息
	
	}

    获得了document对象,接下来就是我们熟悉的dom解析了,下回合再详解。


三、总结

   这篇博文主要介绍了spring解析配置文件的主要流程:先创建一个容器(beanFactory),然后创建一个解析器(reader),获取配置文件(resouce)后,通过这个解析器解析这个配置文件(document)。