寫在前面的話
相關背景及資源:
曹工說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 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命名空間里的。
context:property-placeholder
用法
<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
,就是提供一個抽象類,提取一些context:property-placeholder和context:property-override對應的解析器中公共的方法。
可以簡單一看:
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的后置處理器
context:property-override
用法
這個元素,一般比較少用,但今天查了一下,我覺得這個還比較有意思,而且很奇妙地和當前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包內的配置,是不是和我們這個元素的作用比較像呢?
等價用法
如果不使用:context:property-override,也可以像下面這樣使用:
<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());
}
類實現也很簡單,和前面的context:property-placeholder一樣,都繼承了同一個基類: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
我簡單看了兩眼,還挺有意思,歡迎大家和我一起學習。