相關背景及資源:html
曹工說Spring Boot源碼(1)-- Bean Definition究竟是什麼,附spring思惟導圖分享java
曹工說Spring Boot源碼(2)-- Bean Definition究竟是什麼,我們對着接口,逐個方法講解git
曹工說Spring Boot源碼(3)-- 手動註冊Bean Definition不比遊戲好玩嗎,咱們來試一下spring
曹工說Spring Boot源碼(4)-- 我是怎麼自定義ApplicationContext,從json文件讀取bean definition的?json
曹工說Spring Boot源碼(5)-- 怎麼從properties文件讀取beanapp
曹工說Spring Boot源碼(6)-- Spring怎麼從xml文件裏解析bean的ide
曹工說Spring Boot源碼(7)-- Spring解析xml文件,到底從中獲得了什麼(上)spring-boot
工程結構圖:測試
先給你們看看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元素,一路掃過去
用法以下:
#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
用法以下:
<?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命名空間的元素。
源碼我放在:
歡迎你們和我一塊兒學習spring/spring boot源碼,有問題歡迎一塊兒交流!