问题:
读取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);
错误信息:
看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 {...}
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标识, 以后的版本可能会被去掉。