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松柏一起学习交流!

相关推荐
Piper蛋窝3 小时前
深入 Go 语言垃圾回收:从原理到内建类型 Slice、Map 的陷阱以及为何需要 strings.Builder
后端·go
Bug退退退1234 小时前
RabbitMQ 高级特性之死信队列
java·分布式·spring·rabbitmq
六毛的毛6 小时前
Springboot开发常见注解一览
java·spring boot·后端
AntBlack6 小时前
拖了五个月 ,不当韭菜体验版算是正式发布了
前端·后端·python
31535669136 小时前
一个简单的脚本,让pdf开启夜间模式
前端·后端
uzong6 小时前
curl案例讲解
后端
一只叫煤球的猫7 小时前
真实事故复盘:Redis分布式锁居然失效了?公司十年老程序员踩的坑
java·redis·后端
大鸡腿同学8 小时前
身弱武修法:玄之又玄,奇妙之门
后端
轻语呢喃9 小时前
JavaScript :字符串模板——优雅编程的基石
前端·javascript·后端
MikeWe10 小时前
Paddle张量操作全解析:从基础创建到高级应用
后端