XmlBeanFactory

接着分析XmlBeanFactory容器,上一节已经把他和DefaultListableBeanFactory做了比较,下面就是以他为研究对象进行深入分析.

BeanFactory bf = new XmlBeanFactory(new ClassPathResource("beanFactory.xml")); 

XmlBeanFactory初始化时序图
根据上面的时序图,我们可以知道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
在这里插入图片描述
梳理一下时序图

  1. 封装资源文件,把resource使用EnCodeResource类进行封装
  2. 获取输入流,从Resource中获取InputStream并构造InputSource
  3. 通过构造的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对象.

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值