Spring源码分析——Bean 实例化策略

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>

从上面的配置文件看到:

  1. 定义了两个 bean,名称分别是helloServicehello
  2. helloService bean 中定义了三个属性,以及对应的 value 值;hellobean 中定义了一个属性

我们需要做什么:

  1. 从本地加载这个 XML 文件,拿到 Resource 资源
  2. 获取 Resource 资源的输入流,将其作为 XML 转换成 XML 文档对象
  3. 解析 XML 文档对象,根据固定标签名称(<bean><property>拿到具体的属性内容)
  4. 将上面获取的信息写入到 BeanDefinition 对象,并注册到 BeanDefinition 容器中

BeanDefinitionRegistry

在分析 BeanFactory 创建 Bean 过程中,我们知道:

Spring源码分析——Bean 实例化策略
image-20230413163240313

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

Spring源码分析——Bean 实例化策略
img

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

BeanDefinitionReader

Spring源码分析——Bean 实例化策略
img

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 做了简单实现:

  1. 从 ResourceLoader 中拿到一个具体的资源加载工厂获取 Resource 对象
  2. 从 Resource 对象中拿到文件输入流 InputStream
  3. 利用 Dom4J 工具从 XML 文件输入流 InputStream 中获取 XML 文档的 Element 对象
  4. 解析 Element 对象,依次循环 XML 文件节点,根据字符串名称匹配判断<bean>标签
  5. 根据<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

(0)
小半的头像小半

相关推荐

发表回复

登录后才能评论
极客之音——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!