相關背景及資源: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 Boot源碼(8)-- Spring解析xml文件,到底從中獲得了什麼(util命名空間)學習
工程結構圖:
先給你們看看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元素時,實際上是有共性的。全部這些元素的解析器,都實現了BeanDefinitionParser
。這個接口只有一個方法,做用就是解析元素時,根據元素的配置,來收集beanDefinition
,正所謂:條條大道通羅馬,各類xml配置元素,各類註解配置,就是那些大道,羅馬是什麼?
就是beanDefinition
。
從第一篇到如今,已經第9篇了,咱們還在講bean definition
,其實就是由於,只有深入地理解了它,後面才能更方便地理解spring boot,理解configuration註解,理解enable,理解自動裝配。
好了,切入本篇,本篇要講解的xml元素是context命名空間裏的。
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"> <context:property-placeholder location="classpath*:application.properties"/> <bean class="org.springframework.contextnamespace.TestPropertiesVO"> <property name="name" value="${name}"/> </bean> </beans>
@Data public class TestPropertiesVO { private String name; }
#application.properties name: Phil
測試代碼:
package org.springframework.contextnamespace; import com.alibaba.fastjson.JSONObject; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.context.support.ClassPathXmlApplicationContext; import org.springframework.util.MyFastJson; import java.util.List; import java.util.Map; @Slf4j public class TestPropertyPlaceholder { public static void main(String[] args) { ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext( new String[]{"classpath:context-namespace-test-property-holder.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); // 獲取該bean,打印 Object bean = context.getBean(TestPropertiesVO.class); System.out.println("bean:" + bean); } }
輸出以下:
bean:TestPropertiesVO(name=Phil)
若是咱們修改xml:
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"> //註釋之,看看會怎樣 <!--<context:property-placeholder location="classpath*:application.properties"/>--> <bean class="org.springframework.contextnamespace.TestPropertiesVO"> <property name="name" value="${name}"/> </bean> </beans>
輸出以下:
bean:TestPropertiesVO(name=${name})
能夠看到,這樣子呢,就無法解析到properties中的值了。
<?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:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"> <!--<context:property-placeholder location="classpath*:application.properties"/>--> // 這個配置方式,和上面那個,效果實際上是同樣的;上面那個,是對下邊這種的封裝 <bean id="propertyPlaceholderConfigurer" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer"> <property name="locations"> <list> <value>classpath:application.properties</value> </list> </property> </bean> <bean class="org.springframework.contextnamespace.TestPropertiesVO"> <property name="name" value="${name}"/> </bean> </beans>
咱們切入到org.springframework.context.config.ContextNamespaceHandler
,查找下該元素的解析器。
public class ContextNamespaceHandler extends NamespaceHandlerSupport { public void init() { registerBeanDefinitionParser("property-placeholder", new PropertyPlaceholderBeanDefinitionParser()); registerBeanDefinitionParser("property-override", new PropertyOverrideBeanDefinitionParser()); registerBeanDefinitionParser("annotation-config", new AnnotationConfigBeanDefinitionParser()); registerBeanDefinitionParser("component-scan", new ComponentScanBeanDefinitionParser()); registerBeanDefinitionParser("load-time-weaver", new LoadTimeWeaverBeanDefinitionParser()); registerBeanDefinitionParser("spring-configured", new SpringConfiguredBeanDefinitionParser()); registerBeanDefinitionParser("mbean-export", new MBeanExportBeanDefinitionParser()); registerBeanDefinitionParser("mbean-server", new MBeanServerBeanDefinitionParser()); } }
咱們能夠看到,本元素的解析器是:PropertyPlaceholderBeanDefinitionParser
。
先看看類繼承結構:
你們注意第三層,類名裏,有Single字樣,說明了它是單身狗?不是。說明這個xml元素解析器,最終只獲得一個bean definition。
第四層的AbstractPropertyLoadingBeanDefinitionParser
,就是提供一個抽象類,提取一些
能夠簡單一看:
abstract class AbstractPropertyLoadingBeanDefinitionParser extends AbstractSingleBeanDefinitionParser { @Override protected boolean shouldGenerateId() { return true; } // 獲取一些屬性 @Override protected void doParse(Element element, BeanDefinitionBuilder builder) { String location = element.getAttribute("location"); if (StringUtils.hasLength(location)) { String[] locations = StringUtils.commaDelimitedListToStringArray(location); builder.addPropertyValue("locations", locations); } String propertiesRef = element.getAttribute("properties-ref"); if (StringUtils.hasLength(propertiesRef)) { builder.addPropertyReference("properties", propertiesRef); } String fileEncoding = element.getAttribute("file-encoding"); if (StringUtils.hasLength(fileEncoding)) { builder.addPropertyValue("fileEncoding", fileEncoding); } String order = element.getAttribute("order"); if (StringUtils.hasLength(order)) { builder.addPropertyValue("order", Integer.valueOf(order)); } builder.addPropertyValue("ignoreResourceNotFound", Boolean.valueOf(element.getAttribute("ignore-resource-not-found"))); builder.addPropertyValue("localOverride", Boolean.valueOf(element.getAttribute("local-override"))); builder.setRole(BeanDefinition.ROLE_INFRASTRUCTURE); } }
看了父,不看正主也說不過去,這裏呢,正主是真的簡單:
class PropertyPlaceholderBeanDefinitionParser extends AbstractPropertyLoadingBeanDefinitionParser { private static final String SYSTEM_PROPERTIES_MODE_ATTRIB = "system-properties-mode"; private static final String SYSTEM_PROPERTIES_MODE_DEFAULT = "ENVIRONMENT"; // 這裏獲取bean的class,注意,這裏的class,是否是和前面:等價用法那一節裏,配置的bean的class同樣 // 因此啊,context:property-placeholder和等價用法裏的底層實現,仍是同樣的 @Override protected Class<?> getBeanClass(Element element) { ... return PropertyPlaceholderConfigurer.class; } @Override protected void doParse(Element element, BeanDefinitionBuilder builder) { super.doParse(element, builder); builder.addPropertyValue("ignoreUnresolvablePlaceholders", Boolean.valueOf(element.getAttribute("ignore-unresolvable"))); String systemPropertiesModeName = element.getAttribute(SYSTEM_PROPERTIES_MODE_ATTRIB); if (StringUtils.hasLength(systemPropertiesModeName) && !systemPropertiesModeName.equals(SYSTEM_PROPERTIES_MODE_DEFAULT)) { builder.addPropertyValue("systemPropertiesModeName", "SYSTEM_PROPERTIES_MODE_"+systemPropertiesModeName); } } }
你們能夠看註釋,這裏返回的class,和等價用法裏的
這個class,PropertyPlaceholderConfigurer
,其實仍是比較特別的,咱們看看其類圖:
這裏,咱們發現這個bean class,居然是一個BeanFactoryPostProcessor
。這個接口有什麼做用呢,大概就是,等全部的beanDefinition
都裝載了以後,會調用實現了BeanFactoryPostProcessor
接口的bean,對beanDefinition
進行處理。
若是對這塊感興趣,能夠看博主以前的一篇文章,網上也不少解析,可自行搜索:
曹工雜談:爲何不多須要改Spring源碼,由於擴展點太多了,說說Spring的後置處理器
這個元素,通常比較少用,但今天查了一下,我以爲這個還比較有意思,並且很奇妙地和當前spring boot外部化配置的思想吻合。
它的用途提及來比較晦澀,咱們看例子就知道了:
<?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:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"> <bean id="person" class="org.springframework.contextnamespace.Person" > <property name="name" value="Ram"/> <property name="age" value="20"/> <property name="location" value="Varanasi"/> </bean> </beans>
package org.springframework.contextnamespace; import lombok.Data; @Data public class Person { private String name; private int age; private String location; }
測試代碼:
package org.springframework.contextnamespace; import com.alibaba.fastjson.JSONObject; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.context.support.ClassPathXmlApplicationContext; import org.springframework.util.MyFastJson; import java.util.List; import java.util.Map; /** * desc: * * @author : caokunliang * creat_date: 2019/12/25 0025 * creat_time: 15:50 **/ @Slf4j public class TestPropertyOverride { public static void main(String[] args) { ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext( new String[]{"classpath:context-namespace-test-property-override.xml"},false); context.refresh(); // 獲取bean Object bean = context.getBean(Person.class); System.out.println("bean:" + bean); } }
輸出以下:
bean:Person(name=Ram, age=20, location=Varanasi)
這個應該你們都懂。
接下來,咱們在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" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"> // 配置了這個玩意 <context:property-override location="classpath:beanOverride.properties"/> <bean id="person" class="org.springframework.contextnamespace.Person" > <property name="name" value="Ram"/> <property name="age" value="20"/> <property name="location" value="Varanasi"/> </bean> </beans>
#beanOverride.properties person.age=40 person.location=Delhi
測試程序不變,此次的輸出以下:
bean:Person(name=Ram, age=40, location=Delhi)
也就是說,外部配置文件:beanOverride.properties中的屬性,覆蓋了xml中的bean的屬性。
而如今,spring boot的environment解析變量時,也是外部的配置文件、命令行參數、環境變量等,優先級高於jar包內的配置,是否是和咱們這個元素的做用比較像呢?
若是不使用:
<bean class="org.springframework.beans.factory.config.PropertyOverrideConfigurer"> <property name="location" value="classpath:beanOverride.properties" /> </bean>
從ContextNamespaceHandler
,咱們能夠找到該元素對應的parser:PropertyOverrideBeanDefinitionParser
public void init() { registerBeanDefinitionParser("property-placeholder", new PropertyPlaceholderBeanDefinitionParser()); registerBeanDefinitionParser("property-override", new PropertyOverrideBeanDefinitionParser()); registerBeanDefinitionParser("annotation-config", new AnnotationConfigBeanDefinitionParser()); registerBeanDefinitionParser("component-scan", new ComponentScanBeanDefinitionParser()); registerBeanDefinitionParser("load-time-weaver", new LoadTimeWeaverBeanDefinitionParser()); registerBeanDefinitionParser("spring-configured", new SpringConfiguredBeanDefinitionParser()); registerBeanDefinitionParser("mbean-export", new MBeanExportBeanDefinitionParser()); registerBeanDefinitionParser("mbean-server", new MBeanServerBeanDefinitionParser()); }
類實現也很簡單,和前面的
AbstractPropertyLoadingBeanDefinitionParser
。
簡單看看其實現吧:
class PropertyOverrideBeanDefinitionParser extends AbstractPropertyLoadingBeanDefinitionParser { @Override protected Class getBeanClass(Element element) { return PropertyOverrideConfigurer.class; } @Override protected void doParse(Element element, BeanDefinitionBuilder builder) { super.doParse(element, builder); builder.addPropertyValue("ignoreInvalidKeys", Boolean.valueOf(element.getAttribute("ignore-unresolvable"))); } }
這裏,看看咱們得到的bean class:
和前面討論的同樣,也是一個BeanFactoryPostProcessor
。
又須要回答題目的問題了,從xml文件裏,解析獲得了什麼呢,答案依然是beanDefinition。
不過呢,此次的beanClass,略有不一樣,由於他們是特殊的class,是能夠參與beanDefinition生命週期的class,
由於他們實現了BeanFactoryPostProcessor
。
你們能夠再看看前面util命名空間,那些bean class呢,主要就是FactoryBean
。
本篇源碼位置:
因爲context命名空間都是些大人物,因此本篇主要是先給你們熱身,下一講,咱們講講這裏面的:
annotation-config、component-scan
我簡單看了兩眼,還挺有意思,歡迎你們和我一塊兒學習。