Spring 源码学习笔记(二)之Bean标签默认属性的解析

分析调用栈

Spring 通过读取 xml 配置文件注册 bean ,通过工厂可以获取注册的 bean,示例代码:

java 复制代码
XmlBeanFactory beanFactory = new XmlBeanFactory(new ClassPathResource("applicationContext.xml"));
Object main = beanFactory.getBean("main");
System.out.println(main);

配置文件 applicationContext.xml :

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"
  xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

  <bean id="main" class="com.pine.Main" />
</beans>

在上述 XmlBeanFactory 中有一个解析器,通过该解析器解析 xml 配置文件:

BeanDefinition 类型是 bean 标签在 JVM 中的存在形式

进入loadBeanDefinitions方法中,重点是331行开始的这一段:

java 复制代码
try {
    // 获取配置文件的输入流
    InputStream inputStream = encodedResource.getResource().getInputStream();
    try {
        // InputSource 用于 xml 解析,类似工具类
        InputSource inputSource = new InputSource(inputStream);
        if (encodedResource.getEncoding() != null) {
            inputSource.setEncoding(encodedResource.getEncoding());
        }
        // 执行解析逻辑
        return doLoadBeanDefinitions(inputSource, encodedResource.getResource());
    }
    finally {
        inputStream.close();
    }
}

在 Spring 源码中有一个惯例,就是 do... 方法一般是真正执行具体逻辑的方法

继续进入 doLoadBeanDefinitions 方法,可以看到是一个 try 后面很多 catch ,只需要关注 try 里的内容就可以了,catch里无非是一些异常处理。这个方法中需要关注的是 registerBeanDefinitions 方法,继续进入它的重载方法,再来到 doRegisterBeanDefinitions 方法。 这个方法中主要有两个部分,上面的 if 块和下面的 parseBeanDefinitions 方法。 if 块的作用是处理不同的 profile (多环境),parseBeanDefinitions 才是解析标签。 这个方法的注释及方法体如下:

java 复制代码
/**
 * Parse the elements at the root level in the document:
 * "import", "alias", "bean".
 * @param root the DOM root element of the document
 */
protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) {
    if (delegate.isDefaultNamespace(root)) {
        NodeList nl = root.getChildNodes();
        for (int i = 0; i < nl.getLength(); i++) {
            Node node = nl.item(i);
            if (node instanceof Element) {
                Element ele = (Element) node;
                if (delegate.isDefaultNamespace(ele)) {
                    parseDefaultElement(ele, delegate);
                }
                else {
                    delegate.parseCustomElement(ele);
                }
            }
        }
    }
    else {
        delegate.parseCustomElement(root);
    }
}

bean 标签是 Spring 中默认自带的元素,所以先看 parseDefaultElement 方法,再进入 processBeanDefinition 方法,继续来到 parseBeanDefinitionElement 方法。

分析解析方法

至此,终于来到了最终执行解析逻辑的方法! 1)首先是前两行:

java 复制代码
// 解析 id 属性
String id = ele.getAttribute(ID_ATTRIBUTE);
// 解析 name 属性
String nameAttr = ele.getAttribute(NAME_ATTRIBUTE);

很浅显易懂,就是解析 id 和 name 这两个属性。

2)然后是处理别名:

java 复制代码
// 处理别名
List<String> aliases = new ArrayList<>();
if (StringUtils.hasLength(nameAttr)) {
    String[] nameArr = StringUtils.tokenizeToStringArray(nameAttr, MULTI_VALUE_ATTRIBUTE_DELIMITERS);
    aliases.addAll(Arrays.asList(nameArr));
}

3)接着兼容了 bean 标签中没有 id 的情况(这里也说明了为什么 bean 可以不指定 id):

java 复制代码
String beanName = id;
if (!StringUtils.hasText(beanName) && !aliases.isEmpty()) {
    beanName = aliases.remove(0);
    ......
}

4)然后是当前方法的重载方法 parseBeanDefinitionElement, 4.1)这个方法中首先解析了 class 和 parent 属性:

java 复制代码
String className = null;
if (ele.hasAttribute(CLASS_ATTRIBUTE)) {
    className = ele.getAttribute(CLASS_ATTRIBUTE).trim();
}
String parent = null;
if (ele.hasAttribute(PARENT_ATTRIBUTE)) {
    parent = ele.getAttribute(PARENT_ATTRIBUTE);
}

4.2)然后是 lookup-method、replaced-method 等子标签:

java 复制代码
// meta
parseMetaElements(ele, bd);
// lookup-method
parseLookupOverrideSubElements(ele, bd.getMethodOverrides());
// replaced-method
parseReplacedMethodSubElements(ele, bd.getMethodOverrides());

// constructor-arg
parseConstructorArgElements(ele, bd);
// property
parsePropertyElements(ele, bd);
// qualifier
parseQualifierElements(ele, bd);

5)最后是一个 if 块,这个 if 块主要是处理了 id , name 都为空的情况,会根据算法为当前 bean 生成一个名字,这里不细究。

这样,就搞清楚了 xml 配置文件中 bean 标签是如何解析的。

欢迎关注微信公众号co松柏一起学习交流!

相关推荐
leoZ2317 小时前
本地跑大模型实战(七):llama.cpp 性能调优,让推理更快更省
java·人工智能·spring·生成对抗网络·语言模型·自然语言处理·llama
有梦不弃7 小时前
模块化单体架构设计方案:DDD + 六边形架构落地实践
后端·架构
大模型momo9 小时前
Spring AI 实战:多 Agent 协作实战 —— 分工拆解复杂旅游行程任务
人工智能·spring·ai·agent·旅游
江南十四行12 小时前
Spring框架核心(上)——IoC控制反转与DI依赖注入详解
java·后端·spring
IT_陈寒12 小时前
Redis缓存击穿把我坑惨了,原来这样设过期时间才靠谱
前端·人工智能·后端
妙码生花13 小时前
从 PHP 到 AI + Golang,程序员自救转型手记(五十二):管理员权限检查中间件,AI 随意放行预闯大祸
前端·后端·go
创安电气研究所13 小时前
用 Node.js 将变频器运行数据接入 MQTT
后端
董员外13 小时前
RAG 系统进化论(八):Agentic RAG(智能体式 RAG),从回答问题到完成任务
人工智能·后端·设计模式
大勇前进14 小时前
告别 div 满天飞,HTML5 语义化标签真的有必要用吗?
后端