容器的基础XmlBeanFactory
- 在上一遍博客中我们大概了解了DefaultListableBeanFactory和XmlBeanDefinitionReader的作用。
- DefaultListableBeanFactory的作用主要是Spring的注册,加载bean的默认实现。
- XmlBeanDefinitionReader的作用主要是对配置文件(xml文件)的加载解析。
现在我们具体说下XMLBeanFactory,XmlBeanFactory其实就是继承了DefaultListableBeanFactory,并且自己实现了XML的读取器XmlBeanDefinitionReader,实现个性化的BeanDefinitionReader的读取。
首先我们分析下面的代码的实现来讲解:
BeanFactory bf = new XmlBeanFactory(new ClassPathResource("XXXX.xml"));
这句代码的意思很好理解,就是将我们配置的.xml文件进行加载,得到容器工厂,然后可以根据配置的< bean>标签id,获取到容器中的对象。该代码的具体实现可以参考下面的时序图:
从上面的时序图我们可以看到,我们首先会调用new ClassPathResource得到resource资源文件的实例对象,然后我们就可以根据这个对象进行XmlBeanFactory的初始化。
1.1 配置文件的封装
配置文件的封装指的就是new ClassPathResource(“XXXX.xml”)这一步的过程。这一步具体是怎么操作的,返回的Resource的对象呢?
在java中,将不同的资源抽象成URL,通过注册不同的handler来处理不同来源的资源的读取逻辑,一般的handler的类型使用不同的前缀来识别,如:file,http,jar等,但是URL没有默认定义相对的ClassPath或ServletContext等资源的handler。
所以Resource接口就是起到了这个作用,Spring对其内部使用的资源实现了自己的抽象结构:Resource接口封装底层的资源
下面的代码是Resource的源码,可以看到Resource接口抽象了所有的Spring内部使用到的底层资源:File,URL,Classpath
public interface InputStreamSource {
InputStream getInputStream() throws IOException;
}
/**
*Resource继承了InputStreamSource类
**/
public interface Resource extends InputStreamSource {
// 判断资源是否存在
boolean exists();
// 判断资源是否可读
boolean isReadable();
// 判断资源是否处于打开状态
boolean isOpen();
// 类型转换,不同的资源到URL/URI/FILE类型的转换
URL getURL() throws IOException;
URI getURI() throws IOException;
File getFile() throws IOException;
long contentLength() throws IOException;
long lastModified() throws IOException;
// 基于当前资源创建一个相对资源的方法
Resource createRelative(String var1) throws IOException;
String getFilename();
// 用来在错误处理中打印信息
String getDescription();
}
- 对不同来源的资源文件都有相应的Resource实现:
文件(FileSystemResource),Classpath资源(ClassPathResource),URL资源(UrlResource),InputStream资源(InputStreamResource),Byte数组(ByteArrayResource)等
我们平常在写代码的时候如果需要加载资源文件直接用Spring提供的类
Resource resource = new ClassPathResource("beanTest.xml");
InputStream inputStream = resource.getInputStream();
实现Resource接口的类其实也很简单,举几个例子,看下其getInputStream方法的实现:
- ClassPathResource
if (this.clazz != null){
is = this.clazz.getResourceAsStream(this.path);
}
else{
is = this.classLoader.getResourceAsStream(this.path);
}
- FileSystemResource
public InputStream getInputStream() throws IOException{
return new FileInputStream(this.file);
}
我们在得到了InputStream对象后就可以通过new XmlBeanFactory(InputStream )来初始化XmlBeanFactory
这里注意一下,XmlBeanFactory现在已将是一个过期方法了
public class XmlBeanFactory extends DefaultListableBeanFactory {
private final XmlBeanDefinitionReader reader;
public XmlBeanFactory(Resource resource) throws BeansException {
this(resource, (BeanFactory)null);
}
public XmlBeanFactory(Resource resource, BeanFactory parentBeanFactory) throws BeansException {
super(parentBeanFactory);
// 重要,这里就是加载配置文件的入口,重新定义了XmlBeanDefinitionReader 调用loadBeanDefinitions方法
this.reader = new XmlBeanDefinitionReader(this);
this.reader.loadBeanDefinitions(resource);
}
}
这里注意一下在生成reader对象之前还调用父类的构造函数。跟踪代码可以跟踪到类AbstractAutowireCapableBeanFactory中
public AbstractAutowireCapableBeanFactory() {
super();
this.ignoreDependencyInterface(BeanNameAware.class);
this.ignoreDependencyInterface(BeanFactoryAware.class);
this.ignoreDependencyInterface(BeanClassLoaderAware.class);
}
这里我们看下ignoreDependencyInterface这个方法。
ignoreDependencyInterface该方法的作用是忽略指定接口的自动装配功能。
自动装配功能:
若A中有属性B,A加载的时候发现B没有加载,则会先加载B。
- 若忽略自动装配功能,则表示若类实现了BeanNameAware,BeanFactoryAware,BeanClassLoaderAware则不会对该类进行自动装配
1.2 加载Bean
在上一部分中我们已经拿到了具体的Resource实例,并且已经清楚了Resource的实现,在拿到该对象后我们也可以看看
this.reader = new XmlBeanDefinitionReader(this);
this.reader.loadBeanDefinitions(resource);
这个代码是整个资源加载的切入点,首先先看时序图:
上面的时序图(就是loadBeanDefinitions(resource))的过程,我们可以总结下
- 封装资源文件。当进入XmlBeanDefinitionReader后首先对参数Resource使用EncodeResource类进行封装
- 获取输入流。从Resource中获取对应的InputStream并且构造InputSource
- 通过构造的InputSource实例和Resource实例继续调用函数doLoadBeanDefinition
我们来看下loadBeanDefinitions源码
public int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException {
return this.loadBeanDefinitions(new EncodedResource(resource));
}
EncodedResource的作用是对资源文件进行编码处理的
public Reader getReader() throws IOException {
if (this.charset != null) {
return new InputStreamReader(this.resource.getInputStream(), this.charset);
} else {
return this.encoding != null ? new InputStreamReader(this.resource.getInputStream(), this.encoding) : new InputStreamReader(this.resource.getInputStream());
}
}
上面构造了一个有编码的InputStreamReader,构造完成后回到loadBeanDefinitions方法,该方法的内部才是真正的数据准备阶段。
public int loadBeanDefinitions(EncodedResource encodedResource) throws BeanDefinitionStoreException :
InputStream inputStream = encodedResource.getResource().getInputStream();
try {
InputSource inputSource = new InputSource(inputStream);
if (encodedResource.getEncoding() != null) {
inputSource.setEncoding(encodedResource.getEncoding());
}
var5 = this.doLoadBeanDefinitions(inputSource, encodedResource.getResource());
这里只取了部分的重要的代码,从传来的封装的resource参数中获取inputStream,通过SAX获取XML文件的方式准备InputSource对象,最后将准备的数据通过参数传入真正的核心处理部分doLoadBeanDefinitions
protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource) throws BeanDefinitionStoreException {
try {
int validationMode = getValidationModeForResource(resource);
Document doc = this.doLoadDocument(inputSource, resource);
return this.registerBeanDefinitions(doc, resource);
上面的代码其实只完成了三件事情
- 获取对XML文件的验证
- 加载XML文件,并且得到对应的Document
- 根据返回的Document对象注册bean的信息
下面的章节会详细分析这三步的具体操作。