曹工说Spring Boot源码(8)-- Spring解析xml文件,到底从中得到了什么(util命名空间)
写在前面的话
相关背景及资源:
曹工说Spring Boot源码(1)-- Bean Definition到底是什么,附spring思维导图分享
曹工说Spring Boot源码(2)-- Bean Definition到底是什么,咱们对着接口,逐个方法讲解
曹工说Spring Boot源码(3)-- 手动注册Bean Definition不比游戏好玩吗,我们来试一下
曹工说Spring Boot源码(4)-- 我是怎么自定义ApplicationContext,从json文件读取bean definition的?
曹工说Spring Boot源码(5)-- 怎么从properties文件读取bean
曹工说Spring Boot源码(6)-- Spring怎么从xml文件里解析bean的
曹工说Spring Boot源码(7)-- Spring解析xml文件,到底从中得到了什么(上)
工程结构图:

概要
先给大家看看spring支持的xml配置,我列了个表格如下:
| namespace | element |
|---|---|
| util | constant、property-path、list、set、map、properties |
| context | property-placeholder、property-override、annotation-config、component-scan、load-time-weaver、spring-configured、mbean-export、mbean-server |
| beans | import、bean、alias |
| task | annotation-driven、scheduler、scheduled-tasks、executor |
| cache | advice、annotation-driven |
| aop | config、scoped-proxy、aspectj-autoproxy |
我标题的意思是,既然spring支持这么多种xml配置,那解析这些xml的代码,是否是有共性的呢?还是说,就是很随意的,产品经理说要支持这个元素的解析,就写个分支呢?
看过我上讲的同学应该知道,不管是什么元素,不管在哪个namespace下,其对应的解析代码,都是一种类,这种类,叫做:BeanDefinitionParser,这个类的接口如下:
org.springframework.beans.factory.xml.BeanDefinitionParser
public interface BeanDefinitionParser {
/**
* 解析指定额element,注册其返回的BeanDefinition到BeanDefinitionRegistry
* (使用参数ParserContext#getRegistry()得到BeanDefinitionRegistry)
*/
BeanDefinition parse(Element element, ParserContext parserContext);
}
这个接口的实现类,相当多,除了beans命名空间下的xml元素,其他namespace下的xml元素的解析代码都实现了这个接口。
首先是util命名空间下:

其次是context命名空间:

这里面有大家熟悉的。
这里就不一一列举了,所以大家知道了,每个xml元素的解析器,都是实现了BeanDefinitionParser,这个接口的方法,就是交给各个子类去实现:针对指定的xml元素,如何获取到对应的bean definition。
有的xml元素,比较简单,比如上一篇提到的util:constant,只能得到一个bean definition(factory bean);还有的xml元素,则是群攻魔法,比如<context:component-scan>这种,一把就能捞一大波bean definition上来。
本讲,我们会继续从util namespace开始,将比较常见的xml元素,一路扫过去
util:properties
用法如下:
#test.properties
name=xxx system
import lombok.Data;
@Data
public class TestPropertiesBean {
private String appName;
}
spring xml中如下配置:
<util:properties id="properties"
location="classpath:test.properties"/>
<bean class="org.springframework.utilnamespace.TestPropertiesBean">
// 注意,这里的value,#{properties.name},点号前面引用了上面的properties bean的id,点号后面
// 是properties文件里key的名称
<property name="appName" value="#{properties.name}"></property>
</bean>
测试类如下:
@Slf4j
public class TestProperties {
public static void main(String[] args) {
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext(new String[]{"classpath:util-namespace-test-properties.xml"},false);
context.refresh();
List<BeanDefinition> list =
context.getBeanFactory().getBeanDefinitionList();
MyFastJson.printJsonStringForBeanDefinitionList(list);
Object o = context.getBean(TestPropertiesBean.class);
System.out.println(o);
}
}
输出如下:
TestPropertiesBean(appName=xxx system)
原理解析
在UtilNamespaceHandler中,我们看看该元素对应的BeanDefinitionParser是啥:
public class UtilNamespaceHandler extends NamespaceHandlerSupport {
public void init() {
registerBeanDefinitionParser("constant", new ConstantBeanDefinitionParser());
registerBeanDefinitionParser("property-path", new PropertyPathBeanDefinitionParser());
registerBeanDefinitionParser("list", new ListBeanDefinitionParser());
registerBeanDefinitionParser("set", new SetBeanDefinitionParser());
registerBeanDefinitionParser("map", new MapBeanDefinitionParser());
registerBeanDefinitionParser("properties", new PropertiesBeanDefinitionParser());
}
}
ok! 是PropertiesBeanDefinitionParser。

具体的解析过程,和上一讲里的util:constant相似,这里只说不同的:
private static class PropertiesBeanDefinitionParser extends AbstractSimpleBeanDefinitionParser {
// 这里就是指定了bean definition里的bean class
@Override
protected Class getBeanClass(Element element) {
return PropertiesFactoryBean.class;
}
// 一些定制逻辑,无需操心
@Override
protected void doParse(Element element, ParserContext parserContext, BeanDefinitionBuilder builder) {
super.doParse(element, parserContext, builder);
Properties parsedProps = parserContext.getDelegate().parsePropsElement(element);
builder.addPropertyValue("properties", parsedProps);
String scope = element.getAttribute(SCOPE_ATTRIBUTE);
if (StringUtils.hasLength(scope)) {
builder.setScope(scope);
}
}
}
这里其实,主要就是指定了beanClass,其他逻辑都不甚重要。这里的beanClass就是PropertiesFactoryBean,类型是一个工厂bean。
因为我们的主题是,Spring解析xml文件,从中得到了什么,所以我们不会进一步剖析实现,从对上面这个元素的解析来说,就是得到了一个工厂bean
util:list
用法如下:
<?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">
<util:list id="testList" list-class="java.util.ArrayList">
<value>a</value>
<value>b</value>
<value>c</value>
</util:list>
<bean id="testPropertiesBeanA" class="org.springframework.utilnamespace.TestPropertiesBean">
<property name="appName" value="xxx"/>
</bean>
<bean id="testPropertiesBeanB" class="org.springframework.utilnamespace.TestPropertiesBean">
<property name="appName" value="yyy"/>
</bean>
<util:list id="testBeanList" list-class="java.util.ArrayList">
<ref bean="testPropertiesBeanA"/>
<ref bean="testPropertiesBeanB"/>
</util:list>
</beans>
测试代码:
@Slf4j
public class TestUtilListElement {
public static void main(String[] args) {
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext(
new String[]{"classpath:util-namespace-test-list.xml"},false);
context.refresh();
Map<String, Object> map = context.getDefaultListableBeanFactory().getAllSingletonObjectMap();
log.info("singletons:{}", JSONObject.toJSONString(map));
List<BeanDefinition> list =
context.getBeanFactory().getBeanDefinitionList();
MyFastJson.printJsonStringForBeanDefinitionList(list);
Object bean = context.getBean("testList");
System.out.println("bean:" + bean);
bean = context.getBean("testBeanList");
System.out.println("bean:" + bean);
}
}
输出如下:
23:32:06.396 [main] DEBUG o.s.b.f.s.DefaultListableBeanFactory - Returning cached instance of singleton bean 'testList'
bean:[a, b, c]
23:32:06.396 [main] DEBUG o.s.b.f.s.DefaultListableBeanFactory - Returning cached instance of singleton bean 'testBeanList'
bean:[TestPropertiesBean(appName=xxx), TestPropertiesBean(appName=yyy)]
我们看看这两个bean的beanDefinitionParser,
private static class ListBeanDefinitionParser extends AbstractSingleBeanDefinitionParser {
// 这里指定本bean的class,可以看到,这也是一个工厂bean
@Override
protected Class getBeanClass(Element element) {
return ListFactoryBean.class;
}
//解析元素里的属性等
@Override
protected void doParse(Element element, ParserContext parserContext, BeanDefinitionBuilder builder) {
String listClass = element.getAttribute("list-class");
List parsedList = parserContext.getDelegate().parseListElement(element, builder.getRawBeanDefinition());
builder.addPropertyValue("sourceList", parsedList);
if (StringUtils.hasText(listClass)) {
builder.addPropertyValue("targetListClass", listClass);
}
String scope = element.getAttribute(SCOPE_ATTRIBUTE);
if (StringUtils.hasLength(scope)) {
builder.setScope(scope);
}
}
}
回到题目,spring 从这个util:list元素获得了什么,一个工厂bean,和前面一样。
我们可以仔细看看beandefinition,我这里的测试类是用json输出了的:
{
"abstract": false,
"autowireCandidate": true,
"autowireMode": 0,
"beanClassName": "org.springframework.beans.factory.config.ListFactoryBean",
"constructorArgumentValues": {
"argumentCount": 0,
"empty": true,
"genericArgumentValues": [],
"indexedArgumentValues": {}
},
"dependencyCheck": 0,
"enforceDestroyMethod": true,
"enforceInitMethod": true,
"lazyInit": false,
"lenientConstructorResolution": true,
"methodOverrides": {
"empty": true,
"overrides": []
},
"nonPublicAccessAllowed": true,
"primary": false,
"propertyValues": {
"converted": false,
"empty": false,
"propertyValueList": [
{
"converted": false,
"name": "sourceList",
"optional": false,
"value": [
{
"beanName": "testPropertiesBeanA",
"toParent": false
},
{
"beanName": "testPropertiesBeanB",
"toParent": false
}
]
},
{
"converted": false,
"name": "targetListClass",
"optional": false,
"value": "java.util.ArrayList"
}
]
},
"prototype": false,
"qualifiers": [],
"resolvedAutowireMode": 0,
"role": 0,
"scope": "",
"singleton": true,
"synthetic": false
}
从上面可以看出,beanClass是ListFactoryBean,而我们xml里配置的元素,则被解析后,存放到了propertyValues,被作为了这个bean的属性对待。
总结
util命名空间还有几个别的元素,比如map、set,都差不多,spring都把它们解析为了一个工厂bean。
工厂bean和普通bean的差别,会放到后面再说,下一讲,会继续讲解context命名空间的元素。
源码我放在:
欢迎大家和我一起学习spring/spring boot源码,有问题欢迎一起交流!
曹工说Spring Boot源码(8)-- Spring解析xml文件,到底从中得到了什么(util命名空间)的更多相关文章
- 曹工说Spring Boot源码(2)-- Bean Definition到底是什么,咱们对着接口,逐个方法讲解
写在前面的话 相关背景及资源: 曹工说Spring Boot源码系列开讲了(1)-- Bean Definition到底是什么,附spring思维导图分享 工程代码地址 思维导图地址 工程结构图: 正 ...
- Spring mybatis源码篇章-Mybatis的XML文件加载
通过阅读源码对实现机制进行了解有利于陶冶情操,承接前文Spring mybatis源码篇章-Mybatis主文件加载 前话 前文主要讲解了Mybatis的主文件加载方式,本文则分析不使用主文件加载方式 ...
- 曹工说Spring Boot源码系列开讲了(1)-- Bean Definition到底是什么,附spring思维导图分享
写在前面的话&&About me 网上写spring的文章多如牛毛,为什么还要写呢,因为,很简单,那是人家写的:网上都鼓励你不要造轮子,为什么你还要造呢,因为,那不是你造的. 我不是要 ...
- 曹工说Spring Boot源码(5)-- 怎么从properties文件读取bean
写在前面的话 相关背景及资源: 曹工说Spring Boot源码(1)-- Bean Definition到底是什么,附spring思维导图分享 曹工说Spring Boot源码(2)-- Bean ...
- 曹工说Spring Boot源码(6)-- Spring怎么从xml文件里解析bean的
写在前面的话 相关背景及资源: 曹工说Spring Boot源码(1)-- Bean Definition到底是什么,附spring思维导图分享 曹工说Spring Boot源码(2)-- Bean ...
- 曹工说Spring Boot源码(7)-- Spring解析xml文件,到底从中得到了什么(上)
写在前面的话 相关背景及资源: 曹工说Spring Boot源码(1)-- Bean Definition到底是什么,附spring思维导图分享 曹工说Spring Boot源码(2)-- Bean ...
- 曹工说Spring Boot源码(9)-- Spring解析xml文件,到底从中得到了什么(context命名空间上)
写在前面的话 相关背景及资源: 曹工说Spring Boot源码(1)-- Bean Definition到底是什么,附spring思维导图分享 曹工说Spring Boot源码(2)-- Bean ...
- # 曹工说Spring Boot源码(10)-- Spring解析xml文件,到底从中得到了什么(context:annotation-config 解析)
写在前面的话 相关背景及资源: 曹工说Spring Boot源码(1)-- Bean Definition到底是什么,附spring思维导图分享 曹工说Spring Boot源码(2)-- Bean ...
- 曹工说Spring Boot源码(11)-- context:component-scan,你真的会用吗(这次来说说它的奇技淫巧)
写在前面的话 相关背景及资源: 曹工说Spring Boot源码(1)-- Bean Definition到底是什么,附spring思维导图分享 曹工说Spring Boot源码(2)-- Bean ...
随机推荐
- MaxCompute 预付费标准版VS套餐版
MaxCompute 于5月7日正式售卖预付费(包年包月)套餐资源,主打存储密集型套餐,一共三个套餐: 存储密集型160套餐 存储密集型320套餐 存储密集型600套餐 本文主要给大家介绍预付标准版和 ...
- react项目安装及运行
博客地址 :https://www.cnblogs.com/sandraryan/ 安装node ,有就跳过. node.js官网:https://nodejs.org/en/ 终端用node -v ...
- Fragment学习(一) :生命周期
一. 何为Fragment Fragment是activity的界面中的一部分或一种行为.你可以把多个Fragment们组合到一个Activity中来创建一个多面界面,并且你可以在多个Activity ...
- iptables [match] 常用封包匹配参数
参数 -p, --protocol 范例 iptables -A INPUT -p tcp 说明 匹配通讯协议类型是否相符,可以使用 ! 运算符进行反向匹配,例如: -p !tcp 意思是指除 tcp ...
- 深入java面向对象四:Java 内部类种类及使用解析(转)
内部类Inner Class 将相关的类组织在一起,从而降低了命名空间的混乱. 一个内部类可以定义在另一个类里,可以定义在函数里,甚至可以作为一个表达式的一部分. Java中的内部类共分为四种: 静态 ...
- 洛谷P1280 尼克的任务 题解 动态规划/最短路
作者:zifeiy 标签:动态规划.最短路 题目链接:https://www.luogu.org/problem/P1280 题目大意: 有k个任务分布在第1至n这n个时间点,第i个任务的于第 \(P ...
- Python--day32--复习:https和http的区别;黏包;黏包问题的解决方式;
1,https和http的区别: https比较安全,传输的时候先对内容进行加密,收到后再进行解密:它的传输内容不容易拦截,就算拦截下来了,也是加密的,看不懂.但是要买证书,一年要好几万,小公司承担不 ...
- Postman:非专业的并发测试
Postman是开发中常用的接口测试工具,也可以用来进行并发测试. 使用方法如下: 1. 直接输入url 选择GET方法,点击Send. 结果打印一个"test",如下: 2. 使 ...
- dll中全局变量在外部进行引用
在Windows中实际导出全局变量,您必须使用类似于export / import语法的语法,例如: #ifdef COMPILING_THE_DLL #define MY_DLL_EXPORT ex ...
- dotnet core 输出调试信息到 DebugView 软件
本文告诉大家如何在 dotnet core 输出调试信息到 DebugView 软件 在之前告诉小伙伴,如何在 WPF 输出调试信息到 DebugView 软件,请看文章 WPF 调试 获得追踪输出 ...