更详细的解释可以关注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。