【深度剖析】Spring1.0源码--4、refreshBeanFactory方法


更详细的解释可以关注b站视频:https://www.bilibili.com/video/BV1ro4y1q7Fi/?spm_id_from=333.999.0.0

前言

主要讲解refreshBeanFactory方法的执行过程,主要分为两步,创建Bean工厂和加载xml。
开始之前带着两个问题:
1、xml如何解析为BeanDefinition?
2、BeanDefinition放在哪里?

protected void refreshBeanFactory() throws ApplicationContextException, BeansException {
		
	InputStream is = getInputStreamForBeanFactory();
	this.xmlBeanFactory = new XmlBeanFactory(getParent()); // 创建bean工厂
	this.xmlBeanFactory.setEntityResolver(new ResourceBaseEntityResolver(this));
	this.xmlBeanFactory.loadBeanDefinitions(is); // 加载和解析xml文件

}

创建Bean工厂

由上面的代码可知使用new直接创建XmlBeanFactory对象,把名字切开则为 xml + BeanFactory,因此可以猜到是用xml配置的Bean工厂,提供了加载和解析xml功能。

再看看构造方法中有个入参,也是BeanFactory接口,这块其实是父子容器,关于父子容器在后面SpringMVC的时候会用到,我们先留到那个时候再说,现在先假设没有父容器。

加载和解析XML

到底是怎么解析XML的,将XML解析成什么?是我们要解决的第一个问题。另外一个是解析后放在哪里?带着这两个问题继续往下看

1、读取XML

首先XML是一个配置文件,因此需要创建输入流,而由getInputStreamForBeanFactory方法调用getResourceAsStream方法创建文件输入流,代码如下。Spring一开始的代码不禁要让人吐槽,看看getResourceAsStream方法,居然在异常的时候创建文件流,一开始没有注意还以为走不下去了,抛异常直接结束了,很容易让人误解。

	protected final InputStream getInputStreamForBeanFactory() throws IOException {
		return getResourceAsStream(this.configLocation);
	}

	public final InputStream getResourceAsStream(String location) throws IOException {
		try {
			// 不是网络资源,因此会抛出异常
			URL url = new URL(location);
			return url.openStream();
		} catch (MalformedURLException ex) {
			// 创建文件流
			InputStream in = getResourceByPath(location);
			return in;
		}
	}
	protected InputStream getResourceByPath(String path) throws IOException {
		return new FileInputStream(path);
	}

2、解析XML

a)解析成DOM树

java提供了解析XML的工具包,所以这里直接调用,然后把XML解析成DOM树就可以,创建一个生成DOM树的构造器,再把xml文件流传入解析就生成了DOM树,我们现在是讲spring源码,要记住我们的原则,把握主干,只要了解解析后的DOM树大概长什么样子即可。
假设XML如下:

<beans>

  <bean id="demoDao" class="DemoDaoDefaultImpl"/>
    
  <bean id="demoService" class="DemoService">
    <property name="dao"><ref bean="demoDao"/></property>
    <property name="name">张三</property>
  </bean>

</beans>

解析成DOM树为
在这里插入图片描述

具体源码如下,做了删减

public void loadBeanDefinitions(InputStream is) throws BeansException {
			// 使用jdk提供的工具类解析为DOM树
			DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
			factory.setValidating(true);
			DocumentBuilder db = factory.newDocumentBuilder();
			db.setErrorHandler(new BeansErrorHandler());
			db.setEntityResolver(this.entityResolver != null ? this.entityResolver : new BeansDtdResolver());
			Document doc = db.parse(is);
			// 解析dom树的bean节点,并转换为BeanDefinition
			loadBeanDefinitions(doc);
		}


	}

b)BeanDefinition

有了DOM树,根节点是beans,下一步就是获取所有的子节点bean,遍历bean标签的节点构建bean定义信息,那是怎么解析呢?java是面向对象的语言,离不开对象,解析后的信息要找个对象保存起来,在Spring中主要是BeanDefinition,而其中最主要的是RootBeanDefinition,主要看它有哪些属性。因为xml的节点转成java的对象,而其子节点和属性会变成java对象的属性

由于RootBeanDefinition继承自AbstractBeanDefinition ,因此我们先来看AbstractBeanDefinition,主要有两个属性,分别是singleton和pvs,分别表示是否单例和属性值,对应到xml的标签就是bean的singleton属性和property子标签。

public abstract class AbstractBeanDefinition {

	/** Is this a singleton bean? */
	private boolean singleton;
	
	/** Property map */
	private PropertyValues pvs;
}

RootBeanDefinition类包含了clazz、initMethodName和destroyMethodName,其中最重要的是clazz,表示要创建的类,对应到xml就是bean标签的class属性。我们来看看关系图

public class RootBeanDefinition extends AbstractBeanDefinition {

	/** Class of the wrapped object */
	private Class clazz;
	
	private String initMethodName;

	private String destroyMethodName;
}

解析的具体源码如下,详细代码可以自行查看源码:

public void loadBeanDefinitions(Document doc) throws BeansException {
		Element root = doc.getDocumentElement();
		NodeList nl = root.getElementsByTagName(BEAN_ELEMENT); // 获取所有的bean标签
		for (int i = 0; i < nl.getLength(); i++) {  // 遍历bean标签
			Node n = nl.item(i);
			loadBeanDefinition((Element) n); // 解析bean标签属性和子标签,并转换成BeanDefinition
		}
	}
	
private void loadBeanDefinition(Element el) throws BeansException {
		...

		PropertyValues pvs = getPropertyValueSubElements(el);// 解析bean标签下所有property子标签
		beanDefinition = parseBeanDefinition(el, id, pvs); // 创建BeanDefinition
		registerBeanDefinition(id, beanDefinition); // 将BeanDefinition注册到beanDefinitionMap

		// 后面主要是注册为别名,为了快速梳理流程可以选择性的看
		...
	}

c)注册BeanDefinition

注册的逻辑非常简单,就一行代码,其实就是将其放入bean工厂的beanDefinitionMap里面。

	public final void registerBeanDefinition(String beanName, AbstractBeanDefinition beanDefinition) {
		this.beanDefinitionMap.put(beanName, beanDefinition);
	}

总结

refreshBeanFactory方法主要分为3个环节:
1、创建XmlBeanFactory
2、读取xml解析成BD,获取bean节点属性和bean节点的properties,转成BeanDefinition(相当于bean工厂的原料)
3、最后将其BeanDefinition放入beanDefinitionMap。

上一期:3、工厂方法模式
下一期:5、invokeContextConfigurers方法

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值