Spring IOC体系结构(二)

4、AbstractBeanDefinitionReader读取Bean定义资源:

BeanDefinitionReader的结构如下:

在其抽象父类AbstractBeanDefinitionReader中定义了载入过程:

//重载方法,调用下面的loadBeanDefinitions(String, Set<Resource>);方法  
   public int loadBeanDefinitions(String location) throws BeanDefinitionStoreException {  
       return loadBeanDefinitions(location, null);  
   }  
   public int loadBeanDefinitions(String location, Set<Resource> actualResources) throws BeanDefinitionStoreException {  
       //获取在IoC容器初始化过程中设置的资源加载器  
       ResourceLoader resourceLoader = getResourceLoader();  
       if (resourceLoader == null) {  
           throw new BeanDefinitionStoreException(  
                   "Cannot import bean definitions from location [" + location + "]: no ResourceLoader available");  
       }  
       if (resourceLoader instanceof ResourcePatternResolver) {  
           try {  
               //将指定位置的Bean定义资源文件解析为Spring IoC容器封装的资源  
               //加载多个指定位置的Bean定义资源文件  
               Resource[] resources = ((ResourcePatternResolver) resourceLoader).getResources(location);  
               //委派调用其子类XmlBeanDefinitionReader的方法,实现加载功能  
               int loadCount = loadBeanDefinitions(resources);  
               if (actualResources != null) {  
                   for (Resource resource : resources) {  
                       actualResources.add(resource);  
                   }  
               }  
               if (logger.isDebugEnabled()) {  
                   logger.debug("Loaded " + loadCount + " bean definitions from location pattern [" + location + "]");  
               }  
               return loadCount;  
           }  
           catch (IOException ex) {  
               throw new BeanDefinitionStoreException(  
                       "Could not resolve bean definition resource pattern [" + location + "]", ex);  
           }  
       }  
       else {  
           //将指定位置的Bean定义资源文件解析为Spring IoC容器封装的资源  
           //加载单个指定位置的Bean定义资源文件  
           Resource resource = resourceLoader.getResource(location);  
           //委派调用其子类XmlBeanDefinitionReader的方法,实现加载功能  
           int loadCount = loadBeanDefinitions(resource);  
           if (actualResources != null) {  
               actualResources.add(resource);  
           }  
           if (logger.isDebugEnabled()) {  
               logger.debug("Loaded " + loadCount + " bean definitions from location [" + location + "]");  
           }  
           return loadCount;  
       }  
   }  
   //重载方法,调用loadBeanDefinitions(String);  
   public int loadBeanDefinitions(String... locations) throws BeanDefinitionStoreException {  
       Assert.notNull(locations, "Location array must not be null");  
       int counter = 0;  
       for (String location : locations) {  
           counter += loadBeanDefinitions(location);  
       }  
       return counter;  
    }

loadBeanDefinitions(Resource...resources)方法和上面分析的3个方法类似,同样也是调用XmlBeanDefinitionReader的loadBeanDefinitions方法。

从对AbstractBeanDefinitionReader的loadBeanDefinitions方法源码分析可以看出该方法做了以下两件事:

首先,调用资源加载器的获取资源方法resourceLoader.getResource(location),获取到要加载的资源。

其次,真正执行加载功能是其子类XmlBeanDefinitionReader的loadBeanDefinitions方法。

Resource resource = resourceLoader.getResource(location); 可知,此时调用的是DefaultResourceLoader中的getSource()方法定位Resource,因为FileSystemXmlApplicationContext本身就是DefaultResourceLoader的实现类,所以此时又回到了FileSystemXmlApplicationContext中来。源码如下:

XmlBeanDefinitionReader通过调用其父类DefaultResourceLoader的getResource方法获取要加载的资源

//获取Resource的具体实现方法  
   public Resource getResource(String location) {  
       Assert.notNull(location, "Location must not be null");  
       //如果是类路径的方式,那需要使用ClassPathResource 来得到bean 文件的资源对象  
       if (location.startsWith(CLASSPATH_URL_PREFIX)) {  
           return new ClassPathResource(location.substring(CLASSPATH_URL_PREFIX.length()), getClassLoader());  
       }  
        try {  
             // 如果是URL 方式,使用UrlResource 作为bean 文件的资源对象  
            URL url = new URL(location);  
            return new UrlResource(url);  
           }  
           catch (MalformedURLException ex) { 
           } 
           //如果既不是classpath标识,又不是URL标识的Resource定位,则调用  
           //容器本身的getResourceByPath方法获取Resource  
           return getResourceByPath(location);  
           
   }

FileSystemXmlApplicationContext容器提供了getResourceByPath方法的实现,就是为了处理既不是classpath标识,又不是URL标识的Resource定位这种情况。

protected Resource getResourceByPath(String path) {    
   if (path != null && path.startsWith("/")) {    
        path = path.substring(1);    
    }  
    //这里使用文件系统资源对象来定义bean 文件
    return new FileSystemResource(path);  
}

这样代码就回到了 FileSystemXmlApplicationContext 中来,他提供了FileSystemResource 来完成从文件系统得到配置文件的资源定义。这样,就可以从文件系统路径上对IOC 配置文件进行加载 - 当然我们可以按照这个逻辑从任何地方加载,在Spring 中我们看到它提供 的各种资源抽象,比如ClassPathResource, URLResource,FileSystemResource 等来供我们使用。

5、XmlBeanDefinitionReader加载Bean定义资源

 Bean定义的Resource得到了继续回到XmlBeanDefinitionReader的loadBeanDefinitions(Resource …)方法看到代表bean文件的资源定义以后的载入过程。

//XmlBeanDefinitionReader加载资源的入口方法  
   public int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException {  
       //将读入的XML资源进行特殊编码处理  
       return loadBeanDefinitions(new EncodedResource(resource));  
   } 
     //这里是载入XML形式Bean定义资源文件方法
   public int loadBeanDefinitions(EncodedResource encodedResource) throws BeanDefinitionStoreException {    
   .......    
       try {    
            //将资源文件转为InputStream的IO流 
           InputStream inputStream = encodedResource.getResource().getInputStream();    
           try {    
              //从InputStream中得到XML的解析源    
               InputSource inputSource = new InputSource(inputStream);    
               if (encodedResource.getEncoding() != null) {    
                   inputSource.setEncoding(encodedResource.getEncoding());    
               }    
               //这里是具体的读取过程    
               return doLoadBeanDefinitions(inputSource, encodedResource.getResource());    
           }finally {    
               //关闭从Resource中得到的IO流    
               inputStream.close();    
           }    
       }    
      .........    
     }    
   //从特定XML文件中实际载入Bean定义资源的方法 
   protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource)    
       throws BeanDefinitionStoreException {    
   try {    
       int validationMode = getValidationModeForResource(resource);    
       //将XML文件转换为DOM对象,解析过程由documentLoader实现    
       Document doc = this.documentLoader.loadDocument(    
               inputSource, this.entityResolver, this.errorHandler, validationMode, this.namespaceAware);    
       //这里是启动对Bean定义解析的详细过程,该解析过程会用到Spring的Bean配置规则
       return registerBeanDefinitions(doc, resource);    
     }    
     .......    
     }

通过源码分析,载入Bean定义资源文件的最后一步是将Bean定义资源转换为Document对象,该过程由documentLoader实现。

6、DocumentLoader将Bean定义资源转换为Document对象

//使用标准的JAXP将载入的Bean定义资源转换成document对象  
   public Document loadDocument(InputSource inputSource, EntityResolver entityResolver,  
           ErrorHandler errorHandler, int validationMode, boolean namespaceAware) throws Exception {  
       //创建文件解析器工厂  
       DocumentBuilderFactory factory = createDocumentBuilderFactory(validationMode, namespaceAware);  
       if (logger.isDebugEnabled()) {  
           logger.debug("Using JAXP provider [" + factory.getClass().getName() + "]");  
       }  
       //创建文档解析器  
       DocumentBuilder builder = createDocumentBuilder(factory, entityResolver, errorHandler);  
       //解析Spring的Bean定义资源  
       return builder.parse(inputSource);  
   }  
   protected DocumentBuilderFactory createDocumentBuilderFactory(int validationMode, boolean namespaceAware)  
           throws ParserConfigurationException {  
       //创建文档解析工厂  
       DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();  
       factory.setNamespaceAware(namespaceAware);  
       //设置解析XML的校验  
       if (validationMode != XmlValidationModeDetector.VALIDATION_NONE) {  
           factory.setValidating(true);  
           if (validationMode == XmlValidationModeDetector.VALIDATION_XSD) {  
               factory.setNamespaceAware(true);  
               try {  
                   factory.setAttribute(SCHEMA_LANGUAGE_ATTRIBUTE, XSD_SCHEMA_LANGUAGE);  
               }  
               catch (IllegalArgumentException ex) {  
                   ParserConfigurationException pcex = new ParserConfigurationException(  
                           "Unable to validate using XSD: Your JAXP provider [" + factory +  
                           "] does not support XML Schema. Are you running on Java 1.4 with Apache Crimson? " +  
                           "Upgrade to Apache Xerces (or Java 1.5) for full XSD support.");  
                   pcex.initCause(ex);  
                   throw pcex;  
               }  
           }  
       }  
       return factory;  
   }

该解析过程调用JavaEE标准的JAXP标准进行处理。至此Spring IoC容器根据定位的Bean定义资源文件,将其加载读入并转换成为Document对象过程完成。

接下来我们要继续分析Spring IoC容器将载入的Bean定义资源文件转换为Document对象之后,是如何将其解析为Spring IoC管理的Bean对象并将其注册到容器中的。

7、XmlBeanDefinitionReader解析载入的Bean定义资源文件

XmlBeanDefinitionReader类中的doLoadBeanDefinitions方法是从特定XML文件中实际载入Bean定义资源的方法,该方法在载入Bean定义资源之后将其转换为Document对象,接下来调用registerBeanDefinitions启动Spring IoC容器对Bean定义的解析过程,registerBeanDefinitions方法源码如下:

//按照Spring的Bean语义要求将Bean定义资源解析并转换为容器内部数据结构  
   public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException {  
       //得到BeanDefinitionDocumentReader来对xml格式的BeanDefinition解析  
       BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader();  
       //获得容器中注册的Bean数量  
       int countBefore = getRegistry().getBeanDefinitionCount();  
       //解析过程入口,这里使用了委派模式,BeanDefinitionDocumentReader只是个接口,//具体的解析实现过程有实现类DefaultBeanDefinitionDocumentReader完成  
       documentReader.registerBeanDefinitions(doc, createReaderContext(resource));  
       //统计解析的Bean数量  
       return getRegistry().getBeanDefinitionCount() - countBefore;  
   }  
   //创建BeanDefinitionDocumentReader对象,解析Document对象  
   protected BeanDefinitionDocumentReader createBeanDefinitionDocumentReader() {  
       return BeanDefinitionDocumentReader.class.cast(BeanUtils.instantiateClass(this.documentReaderClass));  
      }

Bean定义资源的载入解析分为以下两个过程:

首先,通过调用XML解析器将Bean定义资源文件转换得到Document对象,但是这些Document对象并没有按照Spring的Bean规则进行解析。这一步是载入的过程,其次,在完成通用的XML解析之后,按照Spring的Bean规则对Document对象进行解析。按照Spring的Bean规则对Document对象解析的过程是在接口BeanDefinitionDocumentReader的实现类DefaultBeanDefinitionDocumentReader中实现的。

8、DefaultBeanDefinitionDocumentReader对Bean定义的Document对象解析

BeanDefinitionDocumentReader接口通过registerBeanDefinitions方法调用其实现类DefaultBeanDefinitionDocumentReader对Document对象进行解析,解析的代码如下:

//根据Spring DTD对Bean的定义规则解析Bean定义Document对象  
public void registerBeanDefinitions(Document doc, XmlReaderContext readerContext) {
        //获得XML描述符  		
        this.readerContext = readerContext;
         //获得Document的根元素  
		Element root = doc.getDocumentElement();
        //进行注册
		doRegisterBeanDefinitions(root);
}

protected void doRegisterBeanDefinitions(Element root) {
		...

		BeanDefinitionParserDelegate parent = this.delegate;
        
		this.delegate = createDelegate(this.readerContext, root, parent);
        //在解析Bean定义之前,进行自定义的解析,增强解析过程的可扩展性  
		preProcessXml(root);
        //从Document的根元素开始进行Bean定义的Document对象  
		parseBeanDefinitions(root, this.delegate);
        //在解析Bean定义之后,进行自定义的解析,增加解析过程的可扩展性  
		postProcessXml(root);

		this.delegate = parent;
	}

protected BeanDefinitionParserDelegate createDelegate(
			XmlReaderContext readerContext, Element root, BeanDefinitionParserDelegate parentDelegate) {
        //具体的解析过程由BeanDefinitionParserDelegate实现,                   
        //BeanDefinitionParserDelegate中定义了Spring Bean定义XML文件的各种元素 
        //createHelper已经不再使用,返回一个null 
		BeanDefinitionParserDelegate delegate = createHelper(readerContext, root, parentDelegate);
		if (delegate == null) {
			delegate = new BeanDefinitionParserDelegate(readerContext, this.environment);
             //BeanDefinitionParserDelegate初始化Document根元素  			
            delegate.initDefaults(root, parentDelegate);
		}
		return delegate;
	}

        @Deprecated
	protected BeanDefinitionParserDelegate createHelper(
			XmlReaderContext readerContext, Element root, BeanDefinitionParserDelegate parentDelegate) {

		return null;
	}


//使用Spring的Bean规则从Document的根元素开始进行Bean定义的Document对象
protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) {
		//Bean定义的Document对象使用了Spring默认的XML命名空间 
        if (delegate.isDefaultNamespace(root)) {
            //获取Bean定义的Document对象根元素的所有子节点  
			NodeList nl = root.getChildNodes();
			for (int i = 0; i < nl.getLength(); i++) {
				Node node = nl.item(i);
                //获得Document节点是XML元素节点 
				if (node instanceof Element) {
					Element ele = (Element) node;
                    //Bean定义的Document的元素节点使用的是Spring默认的XML命名空间  
					if (delegate.isDefaultNamespace(ele)) {
                        //使用Spring的Bean规则解析元素节点  
						parseDefaultElement(ele, delegate);
					}
					else {
                        //没有使用Spring默认的XML命名空间,则使用用户自定义的解//析规则解析元素节点  
						delegate.parseCustomElement(ele);
					}
				}
			}
		}
		else {
             //Document的根节点没有使用Spring默认的命名空间,则使用用户自定义的  
            //解析规则解析Document根节点 
			delegate.parseCustomElement(root);
		}
	}

 
//使用Spring的Bean规则解析Document元素节点  
private void parseDefaultElement(Element ele, BeanDefinitionParserDelegate delegate) {
		 //如果元素节点是<Import>导入元素,进行导入解析 
        if (delegate.nodeNameEquals(ele, IMPORT_ELEMENT)) {
			importBeanDefinitionResource(ele);
		}
         //如果元素节点是<Alias>别名元素,进行别名解析  
		else if (delegate.nodeNameEquals(ele, ALIAS_ELEMENT)) {
			processAliasRegistration(ele);
		}
        //元素节点既不是导入元素,也不是别名元素,即普通的<Bean>元素,  
        //按照Spring的Bean规则解析元素 
		else if (delegate.nodeNameEquals(ele, BEAN_ELEMENT)) {
			processBeanDefinition(ele, delegate);
		}
        //如果是beans进行递归调用
		else if (delegate.nodeNameEquals(ele, NESTED_BEANS_ELEMENT)) {
			// recurse
			doRegisterBeanDefinitions(ele);
		}
	}
//解析<Import>导入元素,从给定的导入路径加载Bean定义资源到Spring IoC容器中  
protected void importBeanDefinitionResource(Element ele) {
         //获取给定的导入元素的location属性  
		String location = ele.getAttribute(RESOURCE_ATTRIBUTE);
        //如果导入元素的location属性值为空,则没有导入任何资源,直接返回
		if (!StringUtils.hasText(location)) {
			getReaderContext().error("Resource location must not be empty", ele);
			return;
		}

		// Resolve system properties: e.g. "${user.dir}"
        // 使用系统变量值解析location属性值  
		location = environment.resolveRequiredPlaceholders(location);

		Set<Resource> actualResources = new LinkedHashSet<Resource>(4);

		//标识给定的导入元素的location是否是绝对路径
		boolean absoluteLocation = false;
		try {
			absoluteLocation = ResourcePatternUtils.isUrl(location) || ResourceUtils.toURI(location).isAbsolute();
		}
		catch (URISyntaxException ex) {
			// cannot convert to an URI, considering the location relative
			// unless it is the well-known Spring prefix "classpath*:"
            // 给定的导入元素的location不是绝对路径  
		}

		// Absolute or relative?
		if (absoluteLocation) {
			try {
                //使用资源读入器加载给定路径的Bean定义资源  
				int importCount = getReaderContext().getReader().loadBeanDefinitions(location, actualResources);
				if (logger.isDebugEnabled()) {
					logger.debug("Imported " + importCount + " bean definitions from URL location [" + location + "]");
				}
			}
			catch (BeanDefinitionStoreException ex) {
				getReaderContext().error(
						"Failed to import bean definitions from URL location [" + location + "]", ele, ex);
			}
		}
		else {
			// No URL -> considering resource location as relative to the current file.
			try {
                //给定的导入元素的location是相对路径  
				int importCount;
				Resource relativeResource = getReaderContext().getResource().createRelative(location);
                 //封装的相对路径资源存在  
				if (relativeResource.exists()) {
                    //使用资源读入器加载Bean定义资源  
					importCount = getReaderContext().getReader().loadBeanDefinitions(relativeResource);
					actualResources.add(relativeResource);
				}
                // 封装的相对路径资源不存在  
				else {
                     //获取Spring IoC容器资源读入器的基本路径  
					String baseLocation = getReaderContext().getResource().getURL().toString();
                    //根据Spring IoC容器资源读入器的基本路径加载给定导入  
                    //路径的资源  
					importCount = getReaderContext().getReader().loadBeanDefinitions(
							StringUtils.applyRelativePath(baseLocation, location), actualResources);
				}
				if (logger.isDebugEnabled()) {
					logger.debug("Imported " + importCount + " bean definitions from relative location [" + location + "]");
				}
			}
			catch (IOException ex) {
				getReaderContext().error("Failed to resolve current resource location", ele, ex);
			}
			catch (BeanDefinitionStoreException ex) {
				getReaderContext().error("Failed to import bean definitions from relative location [" + location + "]",
						ele, ex);
			}
		}
		Resource[] actResArray = actualResources.toArray(new Resource[actualResources.size()]);
        //在解析完<Import>元素之后,发送容器导入其他资源处理完成事件 
		getReaderContext().fireImportProcessed(location, actResArray, extractSource(ele));
	}

通过上述Spring IoC容器对载入的Bean定义Document解析可以看出,我们使用Spring时,在Spring配置文件中可以使用<Import>元素来导入IoC容器所需要的其他资源,Spring IoC容器在解析时会首先将指定导入的资源加载进容器中。使用<Ailas>别名时,Spring IoC容器首先将别名元素所定义的别名注册到容器中。

对于既不是<Import>元素,又不是<Alias>元素的元素,即Spring配置文件中普通的<Bean>元素的解析由BeanDefinitionParserDelegate类的parseBeanDefinitionElement方法来实现。

猜你喜欢

转载自blog.csdn.net/liangkun_java/article/details/81455920