接着分析XmlBeanFactory容器,上一节已经把他和DefaultListableBeanFactory做了比较,下面就是以他为研究对象进行深入分析.
BeanFactory bf = new XmlBeanFactory(new ClassPathResource("beanFactory.xml"));
根据上面的时序图,我们可以知道XmlBeanFactory初始化的整个过程.
下面就分别分析各个步骤.
配置文件的封装
通过代码可以知道,配置文件是通过ClassPathResource进行封装的,在java中,将不同来源的资源抽象成URL,通过注册不同的handler来处理不同来源的资源的读取逻辑.所以一般handler的类型使用不同前缀协议来识别,如: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();
// 是否可读
default boolean isReadable() {
return exists();
}
// 是否处于打开状态
default boolean isOpen() {
return false;
}
//是不是文件
default boolean isFile() {
return false;
}
// 把对应的资源转换成URL、URI和File
URL getURL() throws IOException;
URI getURI() throws IOException;
File getFile() throws IOException;
// ReadableByteChannel,这个类属于Java的NIO中的管道
default ReadableByteChannel readableChannel() throws IOException {
return Channels.newChannel(getInputStream());
}
// 文件内容的长度
long contentLength() throws IOException;
// 最后一次修改的时间戳
long lastModified() throws IOException;
// 创建相对路径的资源
Resource createRelative(String relativePath) throws IOException;
// 获取文件的名字(不带路径的)
String getFilename();
// 在错误处理中打印信息
String getDescription();
}
资源文件的加载在日常开发中是经常用到的,可以直接使用Spring提供的类.
比如:
Resource resource = new ClassPathResource("beanFactory.xml");
InputStream inputStream = resource.getInputStream();
并且可以使用Resource及其子类提供的诸多特性.
有了Resource接口便可以对所有的资源文件进行统一处理.以getInputStream为例,ClassPathResource中的实现方式便是通过class或classLoader提供的底层方法进行调用,而对于FileSystemResource的实现是直接使用FileInputStream对文件进行实例化.
ClassPathResource.java
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;
}
FileSystemResource.java
public InputStream getInputStream() throws IOException {
try {
return Files.newInputStream(this.filePath);
}
catch (NoSuchFileException ex) {
throw new FileNotFoundException(ex.getMessage());
}
}
有了Resource资源之后,根据时序图,可以探索XmlBeanFactory的初始化过程了.
XmlBeanFactory.java
public XmlBeanFactory(Resource resource) throws BeansException {
// 调用XmlBeanFactory(Resource,BeanFactory)构造方法
this(resource, null);
}
public XmlBeanFactory(Resource resource, BeanFactory parentBeanFactory) throws BeansException {
// parentBeanFactory为父类BeanFactory用于factory合并,可以为空
super(parentBeanFactory);
// 资源加载的真正实现
this.reader.loadBeanDefinitions(resource);
}
顺便看一下,父类的构造函数中做了什么?
public AbstractAutowireCapableBeanFactory() {
super();
ignoreDependencyInterface(BeanNameAware.class);
ignoreDependencyInterface(BeanFactoryAware.class);
ignoreDependencyInterface(BeanClassLoaderAware.class);
}
ignoreDependencyInterface
:主要功能是忽略给定接口的自动装配功能.
我们都知道Spring在获取A的时候,如果其属性B还没有初始化,就会自动初始化B.但是某些情况下,B不会初始化,其中一种情况就是B实现了上面三个接口中的一个或多个.即自动装配的时候忽略给定的依赖接口,典型应用是通过其他方式解析Application上下文注册依赖,类似于BeanFactory通过BeanFactoryAware进行注入或者ApplicationContext通过ApplicationContextAware进行注入.
加载Bean
梳理一下时序图
- 封装资源文件,把resource使用EnCodeResource类进行封装
- 获取输入流,从Resource中获取InputStream并构造InputSource
- 通过构造的InputSource实例和Resource实例继续调用函数doLoadBeanDefinitions.
public int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException {
return loadBeanDefinitions(new EncodedResource(resource));
}
其中 EncodedResource对资源进行了编码.主要考虑到Resource可能存在编码要求的情况,其次,通过SAX读取XML文件的方式来准备InputSource对象.
public int loadBeanDefinitions(EncodedResource encodedResource) throws BeanDefinitionStoreException {
// 通过属性来记录已经加载的资源
// 防止重复、循环加载resource资源
Set<EncodedResource> currentResources = this.resourcesCurrentlyBeingLoaded.get();
if (!currentResources.add(encodedResource)) {
throw new BeanDefinitionStoreException(
"Detected cyclic loading of " + encodedResource + " - check your import definitions!");
}
// 从encodedResource中获取已经封装的Resource对象
// 并再次从Resource中获取其中的inputStream
try (InputStream inputStream = encodedResource.getResource().getInputStream()) {
//
InputSource inputSource = new InputSource(inputStream);
if (encodedResource.getEncoding() != null) {
inputSource.setEncoding(encodedResource.getEncoding());
}
// 真正进入到逻辑核心部分
return doLoadBeanDefinitions(inputSource, encodedResource.getResource());
}
catch (IOException ex) {
throw new BeanDefinitionStoreException(
"IOException parsing XML document from " + encodedResource.getResource(), ex);
}
finally {
// 移除
currentResources.remove(encodedResource);
if (currentResources.isEmpty()) {
this.resourcesCurrentlyBeingLoaded.remove();
}
}
}
那么进入到逻辑核心部分,我已经把捕获异常的代码去掉了
protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource)
throws BeanDefinitionStoreException {
try {
// 加载XML文件,并得到对应的document
Document doc = doLoadDocument(inputSource, resource);
// 根据返回的document注册Bean信息
// count是此次注册的beanDefinition的个数
int count = registerBeanDefinitions(doc, resource);
return count;
}
catch (Throwable ex) {
throw new BeanDefinitionStoreException(resource.getDescription(),
"Unexpected exception parsing XML document from " + resource, ex);
}
}
其中加载XML文件之前还会做一件事:
protected Document doLoadDocument(InputSource inputSource, Resource resource) throws Exception {
return this.documentLoader.loadDocument(inputSource, getEntityResolver(), this.errorHandler,
// 获取对XML文件的验证模式
getValidationModeForResource(resource), isNamespaceAware());
}
总结:
1、首先Spring通过Resource接口及其实现类对配置文件进行了封装,方便对配置文件的操作.在
2、对封装好的resource进行操作,在操作之前,注意会调用父类的一个构造器,该构造器会把实现了指定接口的类的初始化延迟.
3、对resource进行操作,首先会把resource封装成enCodeResource,然后通过enCodeResource获取对应的inputStream,再通过inputStream得到inputSource,进入到核心部分.
4、在核心的加载beanDefinition的部分又分成了两大步:
4.1、加载XML,并得到对应的document
4.2、进一步解析document,并注册beanDefinition,把注册的beanDefinition的个数返回.
5、至此XmlBeanFactory的构造函数执行结束,得到一个BeanFactory对象.