- 前言
- 第一部分 核心实现
- 第 1 章 Spring 整体架构和环境搭建
- 第 2 章 容器的基本实现
- 第 3 章 默认标签的解析
- 第 4 章 自定义标签的解析
- 第 5 章 bean 的加载
- 第 6 章 容器的功能扩展
- 第 7 章 AOP
- 第二部分 企业应用
- 第 8 章 数据库连接 JDBC
- 第 9 章 整合 MyBatis
- 第 10 章 事务
- 第 11 章 SpringMVC
- 第 12 章 远程服务
- 第 13 章 Spring 消息
2.5.1 配置文件封装
Spring 的配置文件读取是通过 ClassPathResource 进行封装的,如 new ClassPathResource ("beanFactoryTest.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 {
InputStream getInputStream() throws IOException;
}
public interface Resource extends InputStreamSource {
boolean exists();
boolean isReadable();
boolean isOpen();
URL getURL() throws IOException;
URI getURI() throws IOException;
File getFile() throws IOException;
long lastModified() throws IOException;
Resource createRelative(String relativePath) throws IOException;
String getFilename();
String getDescription();
}
InputStreamSource 封装任何能返回 InputStream 的类,比如 File、Classpath 下的资源和 Byte Array 等。它只有一个方法定义:getInputStream(),该方法返回一个新的 InputStream 对象。
Resource 接口抽象了所有 Spring 内部使用到的底层资源:File、URL、Classpath 等。首先,它定义了 3 个判断当前资源状态的方法:存在性(exists)、可读性(isReadable)、是否处于打开状态(isOpen)。另外,Resource 接口还提供了不同资源到 URL、URI、File 类型的转换,以及获取 lastModified 属性、文件名(不带路径信息的文件名,getFilename())的方法。为了便于操作,Resource 还提供了基于当前资源创建一个相对资源的方法:createRelative()。在错误处理中需要详细地打印出错的资源文件,因而 Resource 还提供了 getDescription() 方法用于在错误处理中的打印信息。
对不同来源的资源文件都有相应的 Resource 实现:文件(FileSystemResource)、Classpath 资源(ClassPathResource)、URL 资源(UrlResource)、InputStream 资源(InputStreamResource)、Byte 数组(ByteArrayResource)等。相关类图如 2-8 所示。
图 2-8 资源文件处理相关类图
在日常的开发工作中,资源文件的加载也是经常用到的,可以直接使用 Spring 提供的类,比如在希望加载文件时可以使用以下代码:
Resource resource=new ClassPathResource(“beanFactoryTest.xml”);
InputStream inputStream=resource.getInputStream();
得到 inputStream 后,我们就可以按照以前的开发方式进行实现了,并且我们已经可以利用 Resource 及其子类为我们提供好的诸多特性。
有了 Resource 接口便可以对所有资源文件进行统一处理。至于实现,其实是非常简单的,以 getInputStream 为例,ClassPathResource 中的实现方式便是通过 class 或者 classLoader 提供的底层方法进行调用,而对于 FileSystemResource 的实现其实更简单,直接使用 FileInputStream 对文件进行实例化。
ClassPathResource.java
if (this.clazz != null) {
is = this.clazz.getResourceAsStream(this.path);
}else {
is = this.classLoader.getResourceAsStream(this.path);
}
FileSystemResource.java
public InputStream getInputStream() throws IOException {
return new FileInputStream(this.file);
}
当通过 Resource 相关类完成了对配置文件进行封装后配置文件的读取工作就全权交给 XmlBeanDefinitionReader 来处理了。
了解了 Spring 中将配置文件封装为 Resource 类型的实例方法后,我们就可以继续探寻 XmlBeanFactory 的初始化过程了,XmlBeanFactory 的初始化有若干办法,Spring 中提供了很多的构造函数,在这里分析的是使用 Resource 实例作为构造函数参数的办法,代码如下:
XmlBeanFactory.java
public XmlBeanFactory(Resource resource) throws BeansException {
//调用 XmlBeanFactory(Resource,BeanFactory)构造方法,
this(resource, null);
}
构造函数内部再次调用内部构造函数:
//parentBeanFactory 为父类 BeanFactory 用于 factory 合并,可以为空
public XmlBeanFactory(Resource resource, BeanFactory parentBeanFactory) throws
BeansException {
super(parentBeanFactory);
this.reader.loadBeanDefinitions(resource);
}
上面函数中的代码 this.reader.loadBeanDefinitions(resource) 才是资源加载的真正实现,也是我们分析的重点之一。我们可以看到时序图中提到的 XmlBeanDefinitionReader 加载数据就是在这里完成的,但是在 XmlBeanDefinitionReader 加载数据前还有一个调用父类构造函数初始化的过程:super(parentBeanFactory),跟踪代码到父类 AbstractAutowireCapableBeanFactory 的构造函数中:
AbstractAutowireCapableBeanFactory.java
public AbstractAutowireCapableBeanFactory() {
super();
ignoreDependencyInterface(BeanNameAware.class);
ignoreDependencyInterface(BeanFactoryAware.class);
ignoreDependencyInterface(BeanClassLoaderAware.class);
}
这里有必要提及一下 ignoreDependencyInterface 方法。ignoreDependencyInterface 的主要功能是忽略给定接口的自动装配功能,那么,这样做的目的是什么呢?会产生什么样的效果呢?
举例来说,当 A 中有属性 B,那么当 Spring 在获取 A 的 Bean 的时候如果其属性 B 还没有初始化,那么 Spring 会自动初始化 B,这也是 Spring 中提供的一个重要特性。但是,某些情况下,B 不会被初始化,其中的一种情况就是 B 实现了 BeanNameAware 接口。Spring 中是这样介绍的:自动装配时忽略给定的依赖接口,典型应用是通过其他方式解析 Application 上下文注册依赖,类似于 BeanFactory 通过 BeanFactoryAware 进行注入或者 ApplicationContext 通过 ApplicationContextAware 进行注入。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论