【Spring源码深度解析学习系列】容器的基础XmlBeanFactory(二)
一、配置文件封装
Spring的配置文件读取是通过ClassPathResource进行封装的,如new ClassPathResource("test.xml"),那么ClassPathResource完成的功能是什么呢?
在java中,将不同来源的资源抽象成URL,通过注册不同的handler(URLStreamHandler)来处理不同来源的资源的读取逻辑,一般handler的类型使用不同前缀(协议,Protocol)来识别,如:file: http: jar: 等,然而URL没有默认定义相对Classpath或ServletContext等资源的handler,虽然可以注册自己的URLStreamHandler来解析特定的URL前缀(协议),比如“classpath:”,这个需要了解URL的实现机制,而且url也没有提供一些基本的方法,如检查当前资源是否存在、检查当前资源是否可读等方法。因而Spring对其内部使用到的资源实现了自己的抽象结构:Resource接口来封装底层资源:
public interface InputStreamSource { /** * Return an {@link InputStream} for the content of an underlying resource. * <p>It is expected that each call creates a <i>fresh</i> stream. * <p>This requirement is particularly important when you consider an API such * as JavaMail, which needs to be able to read the stream multiple times when * creating mail attachments. For such a use case, it is <i>required</i> * that each {@code getInputStream()} call returns a fresh stream. InputStream getInputStream() throws IOException; }
public interface Resource extends InputStreamSource { /** * Determine whether this resource actually exists in physical form. * <p>This method performs a definitive existence check, whereas the * existence of a {@code Resource} handle only guarantees a valid * descriptor handle. */ boolean exists(); /** * Indicate whether the contents of this resource can be read via * {@link #getInputStream()}. * <p>Will be {@code true} for typical resource descriptors; * note that actual content reading may still fail when attempted. * However, a value of {@code false} is a definitive indication * that the resource content cannot be read. * @see #getInputStream() */ boolean isReadable(); /** * Indicate whether this resource represents a handle with an open stream. * If {@code true}, the InputStream cannot be read multiple times, * and must be read and closed to avoid resource leaks. * <p>Will be {@code false} for typical resource descriptors. */ boolean isOpen(); /** * Return a URL handle for this resource. * @throws IOException if the resource cannot be resolved as URL, * i.e. if the resource is not available as descriptor */ URL getURL() throws IOException; /** * Return a URI handle for this resource. * @throws IOException if the resource cannot be resolved as URI, * i.e. if the resource is not available as descriptor * @since 2.5 */ URI getURI() throws IOException; /** * Return a File handle for this resource. * @throws java.io.FileNotFoundException if the resource cannot be resolved as * absolute file path, i.e. if the resource is not available in a file system * @throws IOException in case of general resolution/reading failures * @see #getInputStream() */ File getFile() throws IOException; /** * Determine the content length for this resource. * @throws IOException if the resource cannot be resolved * (in the file system or as some other known physical resource type) */ long contentLength() throws IOException; /** * Determine the last-modified timestamp for this resource. * @throws IOException if the resource cannot be resolved * (in the file system or as some other known physical resource type) */ long lastModified() throws IOException; /** * Create a resource relative to this resource. * @param relativePath the relative path (relative to this resource) * @return the resource handle for the relative resource * @throws IOException if the relative resource cannot be determined */ Resource createRelative(String relativePath) throws IOException; /** * Determine a filename for this resource, i.e. typically the last * part of the path: for example, "myfile.txt". * <p>Returns {@code null} if this type of resource does not * have a filename. */ String getFilename(); /** * Return a description for this resource, * to be used for error output when working with the resource. * <p>Implementations are also encouraged to return this value * from their {@code toString} method. * @see Object#toString() */ String getDescription(); }
InputStreamSource封装任何能返回InputStream的类,比如File、Classpath下的资源和 Byte Array对象。它只有一个方法定义:getInputStream(),该方法返回一个新的InputStream对象。
Resource接口抽象了所有Spring内部使用到的底层资源:File、URL、Classpath等,首先、它定义了3个判断当前资源状态的方法:存在性(exists)、可读性(isReaddable)、是否处于打开状态(isOpen)另外Resource接口还提供了不同资源到URL、URI、File类型的转换,以及获取lastModified属性、文件名(不带路径信息的文件名,getFilename())的方法。为了便于操作,Resource还提供了基于当前资源创建一个相对资源的犯法:createRelative()。在错误处理中需要详细打印出错的资源文件,因而Resource还提供了getDescription()方法用于在错误处理中的打印信息。
对不同来源的资源文件都有相应的Resource实现:文件(FileSystemResource)、Classpath资源(ClassPathResource)、URL资源(UrlResource)、InputStream资源(InputStreamResource)、Byte数组(ByteArrayResource)等。
加载资源文件的代码:
Resource resource = new ClassPathResource("beanFactoryTest.xml"); InputStream inputStream = resource.getInputStream();
ClassPathResource的实现:
public InputStream getInputStream() throws IOException { InputStream is; if (this.clazz != null) { is = this.clazz.getResourceAsStream(this.path); } else if (this.classLoader != null) { is = this.classLoader.getResourceAsStream(this.path); } else { is = ClassLoader.getSystemResourceAsStream(this.path); } if (is == null) { throw new FileNotFoundException(getDescription() + " cannot be opened because it does not exist"); } return is; }
FileInputStream的实现:
public InputStream getInputStream() throws IOException { return new FileInputStream(this.file); }
当通过Resource相关类完成了对配置文件进行封装后配置文件的读取工作就全权交给XmlBeanDefinitonReader来处理了。
XmlBeanFactory的初始化有若干方法,Spring中提供了很多的构造函数,以下为使用Resource实例作为构造函数参数的办法。
public XmlBeanFactory(Resource resource) throws BeansException { //调用XmlBeanFactory(Resource,BeanFactory)构造方法 this(resource, null); } public XmlBeanFactory(Resource resource, BeanFactory parentBeanFactory) throws BeansException { super(parentBeanFactory); this.reader.loadBeanDefinitions(resource); }
public void ignoreDependencyInterface(Class<?> ifc) { this.ignoredDependencyInterfaces.add(ifc); }
ignoreDependencyInterface的主要功能是忽略给定接口的自动装配功能。这样做的目的是什么呢?举例来说,当A中有属性B,那么当Spring在获取A的Bean的时候如果其属性B还没有初始化,那么Spring会自动初始化B,这也是Spring中提供的一个重要特性。但是某些情况下,B不会被初始化,其中的一种情况就是B实现了BeanNameAware接口,Spring中是这样介绍的:自动装配时忽略给定的依赖接口,典型应用是通过其他方式解析Application上下文注册依赖,类似于BeanFactory通过BeanFactoryAware进行注册或者ApplicationContext通过ApplicationContextAware进行注入。
二、加载Bean