Spring 源码分析——Bean 实例化策略
本篇文章讨论 Spring 源码中 XML 配置文件如何装配 Bean。
对应 Github 源码完整文档:https://github.com/TyCoding/mini-spring/tree/main/docs/ioc/05-xml-bean-definition
引言
前面我们提到了 Spring 如何加载资源文件,在 Bean 初始化阶段,需要获取 BeanDefinition 对象加载 Bean 信息,而 XML 就是配置 Bean 的一个常见的方式。可能在 SpringBoot 阶段我们很少使用这种方式了,不过在 Struct 阶段会经常出现这种定义 Bean 的方式,一个常见的 XML 定义如下:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:util="http://www.springframework.org/schema/util" xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd"> <!-- bean definitions here -->
<bean id="helloService" class="cn.tycoding.spring.beans.factory.HelloService">
<property name="name" value="tycoding"/>
<property name="des" value="this HelloService Bean"/>
<property name="hello" ref="hello" />
</bean>
<bean id="hello" class="cn.tycoding.spring.beans.factory.Hello">
<property name="msg" value="hello tycoding"/>
</bean>
</beans>
从上面的配置文件看到:
-
定义了两个 bean,名称分别是 helloService
和hello
-
helloService
bean 中定义了三个属性,以及对应的 value 值;hello
bean 中定义了一个属性
我们需要做什么:
-
从本地加载这个 XML 文件,拿到 Resource 资源 -
获取 Resource 资源的输入流,将其作为 XML 转换成 XML 文档对象 -
解析 XML 文档对象,根据固定标签名称( <bean>
、<property>
拿到具体的属性内容) -
将上面获取的信息写入到 BeanDefinition 对象,并注册到 BeanDefinition 容器中
BeanDefinitionRegistry
在分析 BeanFactory 创建 Bean 过程中,我们知道:

其中有一个管理 BeanDefintion 容器的接口BeanDefinitionRegistry
,提供注册接口用于将 BeanDefinition 对象注册到 Map 容器中实现管理。那么如何读取 BeanDefinition 对象,我们可以直接从源码目录中分析:

有一个BeanDefinitionReader
,顾名思义是读取 BeanDefinition 对象的。
BeanDefinitionReader

Spring 提供了三种加载装配 BeanDefinition 的方式:
-
XmlBeanDefinitionReader:根据 XML 文件装配 BeanDefinition -
GroovyBeanDefinitionReader:根据 Groovy 语法装配 BeanDefinition -
PropertiesBeanDefinitionReader:根据 properties 文件装配 BeanDefinition
这里我们只讨论 XmlBeanDefinitionReader 实现。
伪代码
BeanDefinitionReader接口:
public interface BeanDefinitionReader {
// 获取BeanDefinition对象注册的接口
BeanDefinitionRegistry getRegistry();
// 获取资源加载策略
ResourceLoader getResourceLoader();
// 从指定位置加载BeanDefinition
void loadBeanDefinitions(String location) throws BeansException;
}
AbstractBeanDefinitionReader是 BeanDefinitionReader 接口抽象实现,定义基础抽象方法,子类基于此类扩展实现。
public abstract class AbstractBeanDefinitionReader implements BeanDefinitionReader {
private final BeanDefinitionRegistry registry;
private ResourceLoader resourceLoader;
public AbstractBeanDefinitionReader(BeanDefinitionRegistry registry) {
this(registry, new DefaultResourceLoader());
}
public AbstractBeanDefinitionReader(BeanDefinitionRegistry registry, ResourceLoader resourceLoader) {
this.registry = registry;
this.resourceLoader = resourceLoader;
}
@Override
public BeanDefinitionRegistry getRegistry() {
return registry;
}
@Override
public ResourceLoader getResourceLoader() {
return resourceLoader;
}
@Override
public void loadBeanDefinitions(String[] locations) throws BeansException {
for (String location : locations) {
loadBeanDefinitions(location);
}
}
public void setResourceLoader(ResourceLoader resourceLoader) {
this.resourceLoader = resourceLoader;
}
}
XmlBeanDefinitionReader是 AbstractBeanDefinitionReader 的具体实现,提供解析 XML 的逻辑,伪代码如下:
public class XmlBeanDefinitionReader extends AbstractBeanDefinitionReader {
public static final String BEAN_ELEMENT = "bean";
public static final String PROPERTY_ELEMENT = "property";
public static final String ID_ATTRIBUTE = "id";
public static final String NAME_ATTRIBUTE = "name";
public static final String CLASS_ATTRIBUTE = "class";
public static final String VALUE_ATTRIBUTE = "value";
public static final String REF_ATTRIBUTE = "ref";
public static final String INIT_METHOD_ATTRIBUTE = "init-method";
public static final String DESTROY_METHOD_ATTRIBUTE = "destroy-method";
public static final String SCOPE_ATTRIBUTE = "scope";
public static final String BASE_PACKAGE_ATTRIBUTE = "base-package";
public static final String COMPONENT_SCAN_ELEMENT = "component-scan";
public XmlBeanDefinitionReader(BeanDefinitionRegistry registry) {
super(registry);
}
public XmlBeanDefinitionReader(BeanDefinitionRegistry registry, ResourceLoader resourceLoader) {
super(registry, resourceLoader);
}
@Override
public void loadBeanDefinitions(String location) throws BeansException {
ResourceLoader resourceLoader = getResourceLoader();
Resource resource = resourceLoader.getResource(location);
loadBeanDefinitions(resource);
}
@Override
public void loadBeanDefinitions(Resource resource) throws BeansException {
try {
InputStream inputStream = resource.getInputStream();
try {
doLoadBeanDefinitions(inputStream);
} finally {
inputStream.close();
}
} catch (IOException | DocumentException e) {
throw new BeansException("IOException parsing XML document from " + resource, e);
}
}
/**
* 在Spring源码中有单独的doLoadDocument()解析Document文档,Spring使用了自己封装的解析工具类
* 在本项目中我们直接使用Dom4j的工具类解析XML文档
*
* @param inputStream 资源文件输入流
* @throws DocumentException 解析异常
*/
protected void doLoadBeanDefinitions(InputStream inputStream) throws DocumentException {
SAXReader reader = new SAXReader();
Document document = reader.read(inputStream);
Element root = document.getRootElement();
// 拿到根节点下所有<bean></bean>节点
List<Element> beanElems = root.elements(BEAN_ELEMENT);
for (Element bean : beanElems) {
// 获取<bean>标签携带的属性
String id = bean.attributeValue(ID_ATTRIBUTE);
String beanName = bean.attributeValue(NAME_ATTRIBUTE);
String className = bean.attributeValue(CLASS_ATTRIBUTE);
String initMethod = bean.attributeValue(INIT_METHOD_ATTRIBUTE);
String destroyMethod = bean.attributeValue(DESTROY_METHOD_ATTRIBUTE);
String beanScope = bean.attributeValue(SCOPE_ATTRIBUTE);
Class<?> clazz;
try {
clazz = Class.forName(className);
} catch (ClassNotFoundException e) {
throw new BeansException("Cannot find class [" + className + "]");
}
// 处理id和name,id优于name
beanName = StrUtil.isNotEmpty(id) ? id : beanName;
if (StrUtil.isEmpty(beanName)) {
// 如果仍是空,取类名首字母大写作为Bean名称
beanName = StrUtil.lowerFirst(clazz.getSimpleName());
}
// 封装BeanDefinition
BeanDefinition beanDefinition = new BeanDefinition(clazz);
beanDefinition.setInitMethod(initMethod);
beanDefinition.setDestroyMethod(destroyMethod);
beanDefinition.setScope(beanScope);
// 获取<bean>标签下的<property>标签集合
List<Element> propertyElems = bean.elements(PROPERTY_ELEMENT);
for (Element property : propertyElems) {
String propertyAttrName = property.attributeValue(NAME_ATTRIBUTE);
String propertyAttrValue = property.attributeValue(VALUE_ATTRIBUTE);
String propertyAttrRef = property.attributeValue(REF_ATTRIBUTE);
if (StrUtil.isEmpty(propertyAttrName)) {
throw new BeansException("Property name attribute cannot be empty");
}
// 判断是否存在ref bean引用
Object value = propertyAttrValue;
if (StrUtil.isNotEmpty(propertyAttrRef)) {
// ref指向了其他bean名称
value = new BeanReference(propertyAttrRef);
}
PropertyValue propertyValue = new PropertyValue(propertyAttrName, value);
beanDefinition.getPropertyValues().addPropertyValue(propertyValue);
}
if (getRegistry().containsBeanDefinition(beanName)) {
// bean已存在
throw new BeansException("Duplicate beanName[" + beanName + "] is not allowed");
}
// 注册BeanDefinition
getRegistry().registryBeanDefinition(beanName, beanDefinition);
}
}
}
如上代码摘录:首先 Spring 项目https://github.com/TyCoding/mini-spring ;
我们对 XmlBeanDefinitionReader 做了简单实现:
-
从 ResourceLoader 中拿到一个具体的资源加载工厂获取 Resource 对象 -
从 Resource 对象中拿到文件输入流 InputStream -
利用 Dom4J 工具从 XML 文件输入流 InputStream 中获取 XML 文档的 Element 对象 -
解析 Element 对象,依次循环 XML 文件节点,根据字符串名称匹配判断 <bean>
标签 -
根据 <bean>
标签内容创建 BeanDefinition 对象,最终注册到 BeanDefinitionRegistry 对象
注: Spring 源码中对于此部分的 XML 文件解析很复杂,并没有依赖 Dom4j 外部插件,这里我们只是做了简单的实现。
Spring 源码专栏
此专栏将从 Spring 源码角度整体分析 Spring 设计思路以及常见的面试题。
配套作者的手写 Spring 的项目:https://github.com/TyCoding/mini-spring 。该项目中包含各个阶段的开发文档,有关 Spring 源码更详细的分析测试文档请查阅:https://github.com/TyCoding/mini-spring/tree/main/docs
联系我
-
个人博客:http://tycoding.cn/ -
GitHub:https://github.com/tycoding -
微信公众号:程序员涂陌 -
QQ 交流群:866685601
原文始发于微信公众号(程序员涂陌):Spring源码分析——Bean 实例化策略
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/145608.html