Spring(二):容器的实现——配置文件封装与加载Bean过程

前面认识了两大核心类

  • DefaultListableBeanFactory
  • XmlBeanDefinitionReader

对整体架构有了一定的认识,对容器功能有了大致的认识,下面就来看看容器的基础

XmlBeanFactory

XmlBeanFactory是容器的基础,继承了DefaultListableBeanFactory,并且自身组装了XmlBeanDefinitionReader,可以进行解析XML配置文件
在这里插入图片描述

拓展:BeanFactory其实就是我们的IOC容器,而器上层接口为ApplicationContext,也就是Spring的上下文

所以,我们单纯使用XmlBeanFactory就可以进行读取配置文件、获取和创建Bean了

BeanFactory bf = new XmlBeanFactory(new ClassPathResource("xxx.xml"));
bf.getBean("beanName");

可以看到,首先使用ClassPathResource去进行加载配置文件,然后使用ClassPathResource去实例化XmlBeanFactory

配置文件封装

从上面的那一句代码我们就可以看到,配置文件的封装主要在下面这句代码里面!!!

new ClassPathResource("xxx.xml");

那么究竟这个ClassPathResource完成了什么功能

ClassPathResource实现的就是加载配置文件的功能,并且Spring实现了自己的资源加载策略,不妨可以试想一下,如果没有自己的资源加载策略,对于各种形式的文件,比如file、jar、xml等形式,需要怎样进行解析加载呢?所以,Spring干脆对其内部使用到的资源实现了自己的抽象结构

而这个资源加载策略、抽象结构的关键在于两个接口

  • InputStreamSource
  • Resource
InputStreamSource

在这里插入图片描述

可以看到这个接口十分简单,里面只定义了一个获取InputStream流的方法,用于获取文件的InputStream

Resource

在这里插入图片描述
在这里插入图片描述
从源码上就可以看到Resource接口继承了InputStreamSource接口,所以具体的配置文件抽象结构就是Resource接口

相当于Resource接口抽象了所有Spring内部将会使用到的底层资源,一个Resource对应就是一个资源,是File类型的、还是URL类型的、是否存在、是否可读、最近的修改时间等,都可以从这个抽象接口中获取,从这个接口就可以获取配置文件的资源

所以,针对不同资源类型的(File、Url、ClassPath等),对应都会有一个Resource实现类!

在这里插入图片描述
可以看到这个Resource家族是很大的!

下面列举几个常用的

  • FileSystemResource:获取File资源
  • ClassPathResource:获取ClassPath资源
  • UrlResource:获取URL资源
  • InputStreamResource:获取InputStream资源
  • ByteArrayResource:获取Byte数组资源

有了Resource接口,我们就可以对所有资源文件进行统一处理了,每个资源文件就是一个Resource

Resource相关实现类对配置文件进行封装了之后,后面XmlBeanFactory对文件的读取工作就全部交给了XmlBeanDefinitionReader来处理了
在这里插入图片描述
在这里插入图片描述
可以看到,构造方法就是将传进来的Resource交由XmlBeanDefinitionReader去进行处理,调用的是loadBeanDefinitions方法,也就是说,this.reader.loadBeanDefinitionReader(Resource)才是资源加载的真正实现!

所以,从配置文件的读取,要从这里开始

这里要注意的一点是,XmlBeanFactory还调用了super(ParentBeanFactory方法),而XmlBeanFactory的父类是DefaultListableBeanFactory
在这里插入图片描述
可以看到,DefaultListableBeanFactory又直接super,其父类是AbstractAutowireCapableBeanFactory
在这里插入图片描述

可以看到,进来先调用AbstractAutowireCapableBeanFactory的无参构造方法

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

无参构造中值得注意的是ignoreDependencyInterface方法,该方法的主要作用是忽略给定接口的自动装配功能,也就是说实现了这些接口的类都不会再有自动注入功能,其底层实现是将忽略的接口存进名为ignoreDependencyInterfaces的HashSet集合中

举个栗子,假如一个实体A中的成员属性是实体B,当实体A进行自动注入的时候,实体B也会被自动实例化,这也是Spring中提供的一个重要特性,但有些时候,我们不希望成员实体被自动注入那么我们可以让成员实体B去实现BeanNameAware接口、BeanFactoryAware接口、BeanClassLoaderAware接口,但前提是,实体A必须要有B的setter方法,也就是外部注入方法是不支持的!(这也可能是官方推荐setter方法注入原因之一)

对于aware这类接口,其实还有一个重要的作用是让bean对容器有意识,一般来说,bean是根本不知道Spring的IOC容器的,所以Bean是无法获取Spring的IOC容器服务的,为了让Bean对Spring的IOC容器有意识,所以我们可以让实体去实现上面的那些aware接口,这样在容器里面的这些Bean会意识到IOC容器,并且可以获取相对应的IOC容器服务,比如实现BeanNameAware接口,可以让Bean获取自己在IOC容器里面的Name

但这里还不能理解,为什么要给这些接口忽略外部注入功能???

这里我猜测,可能是想不让Bean被外部有意识的Bean污染(setter注入),本来Bean就是一个无意识的东西,假如Bean还能注入外部有意识的Bean,那么就会越来越多的Bean对Spring的IOC容器有意识,违反了初衷

加载Bean

现在已经对配置文件进行封装了,那么下面就可以来加载Bean了

加载Bean的时候,其实是在读取配置文件里面完成的,也就是下面这句代码

在这里插入图片描述

这一整套流程是比较复杂的

EncodedResource

首先是封装资源文件后,对文件Resource进行EncodedResource封装

在这里插入图片描述
这个类是对文件的编码进行处理的
在这里插入图片描述
可以看到其构造器,其实起到装配作用而已,比如装配了文件Resource,定义了编码encoding和字符集charset,其主要作用体现在getReader方法上

不过先科普一下字符集和编码的关系,编码是依赖于字符集的,比如UTF-8编码就依赖于Unicode字符集,一般来说一个字符集对应一种编码

	public Reader getReader() throws IOException {
        //判断字符集是否为空,如果不为空,根据字符集处理文件并返回InputStreamReader
        //并且优先判断字符集
		if (this.charset != null) {
			return new InputStreamReader(this.resource.getInputStream(), this.charset);
		}
        //判断编码是否为空,如果字符集不存在,那么就根据编码处理文件并返回InputStreamReader
		else if (this.encoding != null) {
			return new InputStreamReader(this.resource.getInputStream(), this.encoding);
		}
        //如果即没有编码,也没有字符集,默认方式处理文件返回InputStreamReader,默认为UTF-8编码
        //从下面的源码中可以看出来
		else {
			return new InputStreamReader(this.resource.getInputStream());
		}
	}

在这里插入图片描述

loadBeanDefinitions方法

使用了EncodedResource对文件进行编码处理了之后,接下来就调用重载的loadBeanDefinitions,相当于将编码后的Resource交给重载的loadBeanDefinitions方法处理,具体根据配置文件去加载Bean的操作都在这个方法里面

	public int 	loadBeanDefinitions(EncodedResource encodedResource) throws BeanDefinitionStoreException {
		//空判断
        Assert.notNull(encodedResource, "EncodedResource must not be null");
		//开启日志追踪
        if (logger.isTraceEnabled()) {
			logger.trace("Loading XML bean definitions from " + encodedResource);
		}
		//从当前线程的ThreadLocal去获取正在加载的Resource
		Set<EncodedResource> currentResources = this.resourcesCurrentlyBeingLoaded.get();
		//下面这个判断,是用来防止循环加载的!
        //因为currentResource是一个HashSet集合,重复的不会进行添加
        //所以,往currentResources添加失败,就表明当前文件已经在加载了
		if (!currentResources.add(encodedResource)) {
			throw new BeanDefinitionStoreException(
					"Detected cyclic loading of " + encodedResource + " - check your import definitions!");
		}
		//获取文件的InputStream
		try (InputStream inputStream = encodedResource.getResource().getInputStream()) {
			//又封装了一层,封装为了不是Spring下的InputResource
            InputSource inputSource = new InputSource(inputStream);
            //同样进行编码操作
            //所以可以估计,Spring所说的自定义文件加载策略,其实就是基于InputResource的
			if (encodedResource.getEncoding() != null) {
                 //如果文件自身携带编码规则,那就使用自带的编码规则
				inputSource.setEncoding(encodedResource.getEncoding());
			}
            //进入了加载Bean的核心部分
			return doLoadBeanDefinitions(inputSource, encodedResource.getResource());
		}
		catch (IOException ex) {
			throw new BeanDefinitionStoreException(
					"IOException parsing XML document from " + encodedResource.getResource(), ex);
		}
        //最终都会执行的操作
		finally {
            //当文件加载完后,无论失败与否,都要清楚在currentResources的存在
			currentResources.remove(encodedResource);
            //如果currentResources为空了,证明没有配置文件加载了
            //当前线程的ThreadLocal直接清空这个集合
			if (currentResources.isEmpty()) {
				this.resourcesCurrentlyBeingLoaded.remove();
			}
		}
	}

从源码上可以看到,这个重载的loadBeanDefinitions方法有以下功能

  • 判断文件是否为空
  • 使用了ThreadLocal检测、防止配置文件循环加载
  • Spring的自定义资源加载策略,其实就是给InputResource又套多了一层,最后的加载文件还是依赖于InputResource
  • 加载Bean的核心部分在doLoadBeanDefinitions方法
doLoadBeanDefinitions方法

这个方法就是加载Bean的核心了

	protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource)
			throws BeanDefinitionStoreException {

		try {
            //doLoadDocument方法就是加载XML文件并得到对应的Document
			Document doc = doLoadDocument(inputSource, resource);
            //根据返回的Dom去注册bean信息
			int count = registerBeanDefinitions(doc, resource);
			if (logger.isDebugEnabled()) {
				logger.debug("Loaded " + count + " bean definitions from " + resource);
			}
			return count;
		}
		catch (BeanDefinitionStoreException ex) {
			throw ex;
		}
		catch (SAXParseException ex) {
			throw new XmlBeanDefinitionStoreException(resource.getDescription(),
					"Line " + ex.getLineNumber() + " in XML document from " + resource + " is invalid", ex);
		}
		catch (SAXException ex) {
			throw new XmlBeanDefinitionStoreException(resource.getDescription(),
					"XML document from " + resource + " is invalid", ex);
		}
		catch (ParserConfigurationException ex) {
			throw new BeanDefinitionStoreException(resource.getDescription(),
					"Parser configuration exception parsing XML from " + resource, ex);
		}
		catch (IOException ex) {
			throw new BeanDefinitionStoreException(resource.getDescription(),
					"IOException parsing XML document from " + resource, ex);
		}
		catch (Throwable ex) {
			throw new BeanDefinitionStoreException(resource.getDescription(),
					"Unexpected exception parsing XML document from " + resource, ex);
		}
	}

可以看到这个方法只有两个步骤

  1. 加载Xml文件(细节在doLoadDocument方法里面),生成Dom
  2. 将Dom里面的Bean进行注册
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值