曹工說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元素,比較簡單,比如上一篇提到的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
。
具體的解析過程,和上一講裡的
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 從這個
我們可以仔細看看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原始碼,有問題歡迎一起交流