使用InputStreamResource构造XmlBeanFactory出错原因

问题:
读取spring配置文件时, 如果使用InputStreamResource构造XmlBeanFactory时出错。

InputStream inputStream = null;
		try {
			File file = new File("D:/java/.../applicationContext.xml");
			inputStream = new FileInputStream(file);
		} catch (FileNotFoundException e) {
			System.out.println("File Not Found!");
			e.printStackTrace();
		}
		Resource resource = new InputStreamResource(inputStream);
		BeanFactory beanFactory = new XmlBeanFactory(resource);
错误信息:
Exception in thread "main" org.springframework.beans.factory.BeanDefinitionStoreException: Passed-in Resource [resource loaded through InputStream] contains an open stream: cannot determine validation mode automatically. Either pass in a Resource that is able to create fresh streams, or explicitly specify the validationMode on your XmlBeanDefinitionReader instance.at org.springframework.beans.factory.xml.XmlBeanDefinitionReader.detectValidationMode(XmlBeanDefinitionReader.java:418)运行环境:jdk1.6 ,  eclipse3.4 ,  spring 2.o

原因分析: 

    看spring2.0源码中的 InputStreamResource类中的 isOpen() 方法:

/**
	 * This implementation always returns true.
	 */
	public boolean isOpen() {
		return true;
	}

这个方法永远返回true。

    再看这个方法:

/**
	 * Detects which kind of validation to perform on the XML file identified
	 * by the supplied {@link Resource}. If the
	 * file has a DOCTYPE definition then DTD validation is used
	 * otherwise XSD validation is assumed.
	 */
	protected int detectValidationMode(Resource resource) {
		if (resource.isOpen()) {
			throw new BeanDefinitionStoreException(
					"Passed-in Resource [" + resource + "] contains an open stream: " +
					"cannot determine validation mode automatically. Either pass in a Resource " +
					"that is able to create fresh streams, or explicitly specify the validationMode " +
					"on your XmlBeanDefinitionReader instance.");
		}
          ... ...
    }

    由于resource.isOpen()永远返回true,所以这里会抛出异常。我的理解是:InputStreamResource是直指向输入流InputStream的,在构造InputStreamResource时,我们要加载的资源第一次被打开后, 即InputStreamResource的getInputStream()方法调用后,  不允许再次读取资源对应的InputStream。下面是getInputStream()方法,可以看到,InputStreamResource用私有的属性 read来标记输入流是否被读取过, 若读取过一次, 再次读取会抛异常的。

/**
	 * This implementation throws IllegalStateException if attempting to
	 * read the underlying stream multiple times.
	 */
	public InputStream getInputStream() throws IOException, IllegalStateException {
		if (this.read) {
			throw new IllegalStateException("InputStream has already been read - " +
					"do not use InputStreamResource if a stream needs to be read multiple times");
		}
		this.read = true;
		return this.inputStream;
	}

    然而从spring2.0开始,为了支持xml schema,需要再次读取资源以检查配置文件是否遵循了DTD 和 xml schema, 又由于当前被打开的资源不能被多次读取, 以致异常抛出。从下面spring源码中关于InputStreamResource类的说明中可以看到,作者强烈建议不要使用InputStreamResource, 而是尽量使用替代者ByteArrayResource、ClassPathResource、FileSystemResource、UrlResource等来加载xml等资源
     InputStreamResource类是为给定的InputStream而准备的Resource接口的实现。它只有在没有其它合适的Resource接口实现类可用时才使用。而且,只要有可能就尽量使用ByteArrayResource或者其它基于文件的Resource实现。与其它Resource实现不同的是,这是个已经打开资源的描述符(因此isOpen()函数返回 true)。如果你需要在其它位置保持这个资源的描述符或者多次读取一个流,请不要使用它。

/**
   23    * {@link Resource} implementation for a given InputStream. Should only
   24    * be used if no specific Resource implementation is applicable.
   25    * In particular, prefer {@link ByteArrayResource} or any of the
   26    * file-based Resource implementations where possible.
   27    *
   28    * 

In contrast to other Resource implementations, this is a descriptor 29 * for an already opened resource - therefore returning "true" from 30 * isOpen(). Do not use it if you need to keep the resource 31 * descriptor somewhere, or if you need to read a stream multiple times. 32 * 33 * @author Juergen Hoeller 34 * @since 28.12.2003 35 * @see ByteArrayResource 36 * @see ClassPathResource 37 * @see FileSystemResource 38 * @see UrlResource 39 */ 40 public class InputStreamResource extends AbstractResource {...}


引一段springsource站网友的解释, 供参考: 
     This is caused by Spring 2.0's new support for XML schema: We need to sneak into the file to find out whether it's based on our DTD or our schema now. Simply assuming the DTD as default in that case could cause strange exceptions in the XML parser that would only arise with such an InputStream specified.

     The recommended solution is the same as the general resource loading recommendation of the past two years:Do not use InputStreamResource unless explicitly necessary.

总结:

    1.  我觉得要清晰理解该问题的原因,需要先了解spring2.0资源resource接口和相关的实现类,InputStream的用法(mark和reset方法),spring2.0对基于xml schema的XML语法的支持,如何处理配置资源;spring1.x 和 spring2.x 的变更有哪些等,由于我自己对这些也不怎么熟悉,本文都是自己的片面理解,仅供参考。

    2.  获取底层资源时,优先使用ClassPathResource、FileSystemResource、ServletContextResource等

    3. 在项目中使用时,优先使用ApplicationContext,ApplicationContext 拥有XmlBeanFactory的所有功能,XmlBeanFactory已经是一个不建议使用的东西, 源码中已经被打上了@Deprecated标识, 以后的版本可能会被去掉。

你可能感兴趣的:(JAVA)