写在前面的话

相关背景及资源:

曹工说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命名空间的元素。

源码我放在:

https://gitee.com/ckl111/spring-boot-first-version-learn/tree/master/all-demo-in-spring-learning/spring-xml-demo/src/main/java/org/springframework/utilnamespace

欢迎大家和我一起学习spring/spring boot源码,有问题欢迎一起交流!

曹工说Spring Boot源码(8)-- Spring解析xml文件,到底从中得到了什么(util命名空间)的更多相关文章

  1. 曹工说Spring Boot源码(2)-- Bean Definition到底是什么,咱们对着接口,逐个方法讲解

    写在前面的话 相关背景及资源: 曹工说Spring Boot源码系列开讲了(1)-- Bean Definition到底是什么,附spring思维导图分享 工程代码地址 思维导图地址 工程结构图: 正 ...

  2. Spring mybatis源码篇章-Mybatis的XML文件加载

    通过阅读源码对实现机制进行了解有利于陶冶情操,承接前文Spring mybatis源码篇章-Mybatis主文件加载 前话 前文主要讲解了Mybatis的主文件加载方式,本文则分析不使用主文件加载方式 ...

  3. 曹工说Spring Boot源码系列开讲了(1)-- Bean Definition到底是什么,附spring思维导图分享

    写在前面的话&&About me 网上写spring的文章多如牛毛,为什么还要写呢,因为,很简单,那是人家写的:网上都鼓励你不要造轮子,为什么你还要造呢,因为,那不是你造的. 我不是要 ...

  4. 曹工说Spring Boot源码(5)-- 怎么从properties文件读取bean

    写在前面的话 相关背景及资源: 曹工说Spring Boot源码(1)-- Bean Definition到底是什么,附spring思维导图分享 曹工说Spring Boot源码(2)-- Bean ...

  5. 曹工说Spring Boot源码(6)-- Spring怎么从xml文件里解析bean的

    写在前面的话 相关背景及资源: 曹工说Spring Boot源码(1)-- Bean Definition到底是什么,附spring思维导图分享 曹工说Spring Boot源码(2)-- Bean ...

  6. 曹工说Spring Boot源码(7)-- Spring解析xml文件,到底从中得到了什么(上)

    写在前面的话 相关背景及资源: 曹工说Spring Boot源码(1)-- Bean Definition到底是什么,附spring思维导图分享 曹工说Spring Boot源码(2)-- Bean ...

  7. 曹工说Spring Boot源码(9)-- Spring解析xml文件,到底从中得到了什么(context命名空间上)

    写在前面的话 相关背景及资源: 曹工说Spring Boot源码(1)-- Bean Definition到底是什么,附spring思维导图分享 曹工说Spring Boot源码(2)-- Bean ...

  8. # 曹工说Spring Boot源码(10)-- Spring解析xml文件,到底从中得到了什么(context:annotation-config 解析)

    写在前面的话 相关背景及资源: 曹工说Spring Boot源码(1)-- Bean Definition到底是什么,附spring思维导图分享 曹工说Spring Boot源码(2)-- Bean ...

  9. 曹工说Spring Boot源码(11)-- context:component-scan,你真的会用吗(这次来说说它的奇技淫巧)

    写在前面的话 相关背景及资源: 曹工说Spring Boot源码(1)-- Bean Definition到底是什么,附spring思维导图分享 曹工说Spring Boot源码(2)-- Bean ...

随机推荐

  1. H3C 命令行编辑功能

  2. H3C 传输层

  3. call,apply,bind详解

    为什么要改变this指向? 我们知道bind,call,apply的作用都是用来改变this指向的,那为什么要改变this指向呢?请看下面的例子: var name="lucy"; ...

  4. 深入java面向对象三:抽象类和接口(转载)

    文章系转载,地址: http://blog.csdn.net/xw13106209/article/details/6923556 1.概述     一个软件设计的好坏,我想很大程度上取决于它的整体架 ...

  5. mysql 第三课 jdbc基础操作

    jdbc连接可以大致分为5步: 1.注册驱动 2.获取连接 3.编写语句 4.执行语句 5.关闭连接 其中可以设置参数等等. 1.我们先建一个项目目录: 其中com.etc.dao为数据访问对象 co ...

  6. Java 参数的值传递和引用传递

    在Java中,方法的参数的传递分为值传递(基本数据)和引用传递(引用数据:对象.字符串),这是最容易接受的.如果你能知道有这两种情况存在,那么,在遇到调用方法时,你可以避免很多问题的产生.但是,仔细查 ...

  7. CSS 实现单行及多行文字省略

    单行文字省略 很多时候不确定字数限制,但换行可能影响整体设计,这个时候常用就是文字省略加全文字提示了 .dom{ text-overflow: ellipsis; overflow: hidden; ...

  8. 【codeforces 764A】Taymyr is calling you

    time limit per test1 second memory limit per test256 megabytes inputstandard input outputstandard ou ...

  9. Linux 内核 启动时间

    为见到 PCI 如何工作的, 我们从系统启动开始, 因为那是设备被配置的时候. 当一个 PCI 设备上电时, 硬件保持非激活. 换句话说, 设备只响应配置交易. 在上电时, 设备没有内存并且没有 I/ ...

  10. Linux USB 的 Urbs

    linux 内核中的 USB 代码和所有的 USB 设备通讯使用称为 urb 的东西( USB request block). 这个请求块用 struct urb 结构描述并且可在 include/l ...