Spring原始碼系列 — BeanDefinition擴充套件點
前言
前文介紹了Spring Bean的生命週期,也算是XML IOC系列的完結。但是Spring的博大精深,還有很多盲點需要摸索。整合前面的系列文章,從Resource到BeanDefinition,再到容器擴充套件點,最後到Bean創鍵,這個過程中無處不存在Spring預留的擴充套件口。
本篇文章介紹Spring的另一種擴充套件點:BeanDefinition擴充套件點,該擴充套件點是為處理BeanDefinition而設計。本文主要從以下幾點分析:
- BeanDefinition擴充套件點的幾種方式
- BeanDefinition擴充套件點實戰
- BeanDefinition擴充套件點的原理
BeanDefinition擴充套件點的幾種方式
Spring中針對向上下文中新增BeanDefinition、修改上下文中的BeanDefinition可謂是提供了豐富的擴充套件點。既有針對XML配置的,又有針對註解配置的Bean,甚至還有自定義XML標籤的。這裡總結了,共有以下幾種方式:
- BeanDefinitionRegistryPostProcessor方式
- BeanFactoryPostProcessor方式
- ImportBeanDefinitionRegistrar方式
- BeanDefinitionParser方式
BeanDefinitionRegistryPostProcessor方式
從命名上也可以看出一些端倪,BeanDefinitionRegistryPostProcessor是BeanDefinition註冊後置處理器,它本身是BeanFactoryPostProcessor的擴充套件,允許在BeanFactoryPostProcessor處理前向上下文中註冊更多的BeanDefinition。
BeanFactoryPostProcessor方式
BeanFactoryPostProcessor是容器的擴充套件點,用於更進一步處理上下文中的BeanDefinition,如果對其還不甚瞭解,請移步至我的另一篇文章Spring原始碼系列 — 容器Extend Point(一)
ImportBeanDefinitionRegistrar方式
ImportBeanDefinitionRegistrar也是BeanDefinition註冊器,用於向上下文註冊更多的BeanDefinition。不過它是被應用在註解處理BeanDefinition的場景中,即自定義註解,然後利用ImportBeanDefinitionRegistrar其實現向上下文中註冊自定義註解標註的Bean定義。
BeanDefinitionParser方式
BeanDefinitionParser是BeanDefinition解析器,它是Spring提供為擴充套件解析XML配置的Bean而設計。它不僅能夠解析XML向上下文中註冊更多BeanDefiniion,同時還支援自定義XML Tag。
BeanDefinition擴充套件點實戰
上節整理了Spring中提供處理BeanDefinition的幾種擴充套件方式,為了更好的理解和應用這些擴充套件點,本節將從實戰的角度再度理解這些擴充套件方式。
Notes:
關於BeanFactoryPostProcessor的擴充套件實戰本節不再做說明,在前文的容器擴張點中已經詳細介紹其原理,並利用PropertySourcesPlaceholderConfigurer案例進行了分析。這裡不再贅述。
基於BeanDefinitionRegistryPostProcessor擴充套件
首先定義BeanDefinitionRegistryPostProcessor實現類MyBdRegistryPostProcessor,實現其postProcessBeanDefinitionRegistry介面:
/**
* 用於演示BeanDefinitionRegistryPostProcessor擴充套件點
*
* @author huaijin
*/
public class MyBdRegistryPostProcessor implements BeanDefinitionRegistryPostProcessor {
@Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
try {
// 建立自定義的BeanDefinition
String bdClassName = MyBeanUsedBdRegistryPostProcessor.class.getName();
AbstractBeanDefinition bd = BeanDefinitionReaderUtils
.createBeanDefinition(null, bdClassName, ClassUtils.getDefaultClassLoader());
// 設定BeanDefinition屬性:單例、非惰性
bd.setScope(AbstractAutowireCapableBeanFactory.SCOPE_SINGLETON);
bd.setLazyInit(false);
// 設定Bean的屬性值
MutablePropertyValues mutablePropertyValues = new MutablePropertyValues();
PropertyValue propertyValue = new PropertyValue("name", "myBeanUsedBdRegistryPostProcessor");
mutablePropertyValues.addPropertyValue(propertyValue);
// 將Bean的屬性值新增到BeanDefinition中
bd.setPropertyValues(mutablePropertyValues);
// 註冊該自定義的BeanDefinition,BeanName使用myBeanUsedBdRegistryPostProcessor
registry.registerBeanDefinition("myBeanUsedBdRegistryPostProcessor", bd);
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
}
}
然後編寫啟動類,BeanDefinitionRegistryPostProcessorDemo,載入XML配置,從上下文中獲取myBeanUsedBdRegistryPostProcessor名稱的Bean,並執行其printMyName方法:
public class BeanDefinitionRegistryPostProcessorDemo {
public static void main(String[] args) {
// 載入配置
ClassPathXmlApplicationContext context =
new ClassPathXmlApplicationContext(
"applicationContext-extendpoint/beans.xml");
// get bean
MyBeanUsedBdRegistryPostProcessor myBean = context.getBean(
"myBeanUsedBdRegistryPostProcessor", MyBeanUsedBdRegistryPostProcessor.class);
// 執行方法
myBean.printMyName();
}
}
beans.xml的配置如下:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
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">
<!-- 配置自定義的BeanDefinitionRegistryPostProcessor為Bean -->
<bean class="com.learn.ioc.extendpoint.process.MyBdRegistryPostProcessor"></bean>
</beans>
方法呼叫執行結果如下:
my name is:myBeanUsedBdRegistryPostProcessor
BeanDefinitionRegistryPostProcessor中自定義的Bean成功的被上下文註冊為單例。當然這裡只是簡單的示例,對於更復雜的需要進行依賴處理。
基於ImportBeanDefinitionRegistrar擴充套件
上節中介紹了ImportBeanDefinitionRegistrar是基於註解的方式BeanDefinition註冊器,允許應用向上下文中註冊更多的BeanDefinition。這裡以筆者專案中的案例作為分析,幫助理解ImportBeanDefinitionRegistrar。
筆者在spring-boot工程的專案中使用了Elastic-Job v1.1.1版本,由於該版本Elastic-Job不支援不支援註解式配置Job Bean,筆者嫌在spring-boot中再引入XML不夠方便和友好,故簡單自己實現了Elastic-Job對註解支援的模組。其中就使用到了Spring提供的ImportBeanDefinitionRegistrar擴充套件點。
原有的Elastic——Job的XML配置主要分為兩大類,第一類是任務註冊中心的配置,第二類是Job相關的配置。其中Job分為多種,每種Job的配置方式不一樣,這裡只實現了對SimpleJob的支援。
首先分析SimpleJob的配置,同Spring Bean的配置差異不大。也是代表Job的標籤,然後就是屬性的配置,再者就是子元素的Bean的配置。如:
<job:simple id="..." class="..."
registry-center-ref="..."
overwrite="..."
cron="..."
sharding-total-count="..."
sharding-item-parameters="..."
monitor-execution="..."
monitor-port="..."
failover="..."
description="...."
disabled="...">
<job:listener class="..." started-timeout-milliseconds="..." completed-timeout-milliseconds="..."></job:listener>
</job:simple>
一個job:simple用於定義一個Job的配置,這樣可以抽象一個註解來描述該Job配置,其中子元素job:listener又是屬於這個Job的監聽器子元素配置,同樣也需要抽象出一個註解用於定義該監聽器,如:
/**
* Elastic-job的Simple型別Job對應的註解
*
* @author huaijin
*/
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Component
@Import(ElasticJobRegistrar.class)
public @interface ElasticSimpleJob {
String id();
Class<?> classStr();
boolean overwrite() default true;
String registryCenterRef();
String jobParameter() default "";
String cron();
String shardingTotalCount();
String shardingItemParameters() default "";
boolean jobFailover() default true;
int monitorPort() default 9880;
boolean monitorExecution() default false;
String description() default "";
String maxTimeDiffSeconds() default "";
String misfire() default "";
String jobShardingStrategyClass() default "";
JobListener jobListener() default @JobListener(startedTimeoutMilliseconds = 0,
completedTimeoutMilliseconds = 0);
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@interface JobListener {
Class<?> classStr() default Class.class;
long startedTimeoutMilliseconds();
long completedTimeoutMilliseconds();
}
}
其中該註解被@Component修飾,表示該註解標註的類是一個Spring Bean,能夠被Spring的@Component註解處理檢測載入該類的註解屬性。使用@Import(ElasticJobRegistrar.class)該配置,表示該註解應用被哪個ImportBeanDefinitionRegistrar實現進行處理。
然後就是實現ImportBeanDefinitionRegistrar,用於處理ElasticSimpleJob註解,將其標註的類註冊為Spring中特定型別的BeanDefinition。
/**
* 解析{@link ElasticSimpleJob},註冊SpringJobScheduler和SimpleJobConfiguration
*
* @author huaijin
*/
@Component
public class ElasticJobRegistrar implements ImportBeanDefinitionRegistrar, EnvironmentAware {
private Environment environment;
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata,
BeanDefinitionRegistry registry) {
// 獲取ElasticSimpleJob註解的屬性集合
AnnotationAttributes annoAttrs = AnnotationAttributes.fromMap(importingClassMetadata
.getAnnotationAttributes(ElasticSimpleJob.class.getName()));
// 獲取job id屬性
String id = annoAttrs.getString(BeanDefinitionParserDelegate.ID_ATTRIBUTE);
// 使用Spring提供的建造者模式構造BeanDefinition,其中型別為SpringJobScheduler
BeanDefinitionBuilder factory = BeanDefinitionBuilder.rootBeanDefinition(SpringJobScheduler.class);
// 設定初始化方法
factory.setInitMethodName("init");
// 增加該Bean的第一個構造引數引用,即對註冊中心Bean的引用
factory.addConstructorArgReference(annoAttrs.getString("registryCenterRef"));
// 增加該Bean的第二個構造引數引用,對Job配置的引用
factory.addConstructorArgReference(createJobConfiguration(annoAttrs, registry));
// 增加第三個構造引數引用,是對job listener的引用
factory.addConstructorArgValue(createJobListeners(annoAttrs.getAnnotation("jobListener")));
// 註冊該BeanDefinition
BeanDefinitionHolder holder = new BeanDefinitionHolder(factory.getBeanDefinition(), id + "SpringJobScheduler");
BeanDefinitionReaderUtils.registerBeanDefinition(holder, registry);
}
@Override
public void setEnvironment(Environment environment) {
this.environment = environment;
}
private String createJobConfiguration(final AnnotationAttributes annoAttrs, final BeanDefinitionRegistry registry) {
Class<?> simpleJobConfigurationDto;
try {
simpleJobConfigurationDto = Class.forName("com.dangdang.ddframe.job.spring.namespace.parser.simple." +
"SimpleJobConfigurationDto");
} catch (ClassNotFoundException e) {
throw new RuntimeException(e);
}
factory.addConstructorArgValue(annoAttrs.getString(BeanDefinitionParserDelegate.ID_ATTRIBUTE));
factory.addConstructorArgValue(annoAttrs.getClass("classStr"));
factory.addConstructorArgValue(annoAttrs.getString("shardingTotalCount"));
factory.addConstructorArgValue(annoAttrs.getString("cron"));
addPropertyValueIfExists(annoAttrs, "shardingItemParameters", factory);
addPropertyValueIfExists(annoAttrs, "jobParameter", factory);
addPropertyValueIfExists(annoAttrs, "jobMonitorExecution", factory);
addPropertyValueIfExists(annoAttrs, "monitorPort", factory);
addPropertyValueIfExists(annoAttrs, "maxTimeDiffSeconds", factory);
addPropertyValueIfExists(annoAttrs, "failover", factory);
addPropertyValueIfExists(annoAttrs, "misfire", factory);
addPropertyValueIfExists(annoAttrs, "jobShardingStrategyClass", factory);
addPropertyValueIfExists(annoAttrs, "description", factory);
String propertyName = "elastic.job.disabled";
addPropertyValueIfExists(environment, propertyName, factory);
addPropertyValueIfExists(annoAttrs, "overwrite", factory);
String result = annoAttrs.getString(BeanDefinitionParserDelegate.ID_ATTRIBUTE) + "Conf";
registry.registerBeanDefinition(result, factory.getBeanDefinition());
return result;
}
public List<BeanDefinition> createJobListeners(AnnotationAttributes jobListener) {
List<BeanDefinition> listeners = new ManagedList<>();
Class<?> listenerClass = jobListener.getClass("classStr");
if (listenerClass == Class.class) {
return new ManagedList<>(0);
}
BeanDefinitionBuilder factory = BeanDefinitionBuilder.rootBeanDefinition(listenerClass);
factory.setScope(BeanDefinition.SCOPE_PROTOTYPE);
if (AbstractDistributeOnceElasticJobListener.class.isAssignableFrom(listenerClass)) {
factory.addConstructorArgValue(jobListener.getNumber("startedTimeoutMilliseconds"));
factory.addConstructorArgValue(jobListener.getNumber("completedTimeoutMilliseconds"));
}
listeners.add(factory.getBeanDefinition());
return listeners;
}
protected final void addPropertyValueIfExists(final AnnotationAttributes annoAttrs, final String propertyName,
final BeanDefinitionBuilder factory) {
if (annoAttrs.containsKey(propertyName)) {
Object attributeValue = annoAttrs.get(propertyName);
if (Objects.nonNull(attributeValue)) {
factory.addPropertyValue(propertyName, attributeValue.toString());
}
}
}
protected final void addPropertyValueIfExists(final Environment env, final String propertyName,
final BeanDefinitionBuilder factory) {
String propertyValue = env.getProperty(propertyName);
if (propertyValue != null && !propertyValue.isEmpty()) {
factory.addPropertyValue("disabled", propertyValue);
}
}
}
以上實現利用ImportBeanDefinitionRegistrar擴充套件點,獲取ElasticSimpleJob註解的屬性,然後將其解析填充到相應型別的BeanDefinition中,最後再將BeanDefinition註冊到上下文中。這樣就完成了使用ElasticSimpleJob註解配置Job,並能夠讓Spring正常的載入例項化Job。
基於BeanDefinitionParser擴充套件
BeanDefinitionParser是Spring提供的對XML解析生成BeanDefinition的擴充套件點,應用可以擴充套件該介面,提供自定義XML Tag的解析能力,並生成BeanDefinition註冊至上下文中。
本節將通過定義自定義xsd,編寫自定義的XML配置,編寫BeanDefinitionParser擴充套件實現來展示基於BeanDefinitionParser擴充套件。主要分為以下幾個步驟:
- 定義應用自身的xsd(XML Schema)
- 編寫Spring XML配置
- 編寫BeanDefinitionParser實現
- 編寫自定義的NameSpaceHandler,其中需要註冊以上實現的BeanDefinitionParser
- 配置整合BeanDefinitionParser和xsd至Spring中
首先自定義的XML Schema,這裡使用xsd方式(關於dtd,讀者可以自行研究)。如下:
<?xml version="1.0" encoding="UTF-8"?>
<xsd:schema xmlns="http://www.huaijin.com/schema/MyBean"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
targetNamespace="http://www.huaijin.com/schema/MyBean"
elementFormDefault="qualified">
<xsd:element name="MyBean">
<xsd:complexType>
<xsd:attribute name="id" type="xsd:string" use="required"></xsd:attribute>
<xsd:attribute name="name" type="xsd:string"></xsd:attribute>
<xsd:attribute name="class" type="xsd:string" use="required"></xsd:attribute>
</xsd:complexType>
</xsd:element>
</xsd:schema>
該xsd自定義了XML Tag MyBean的描述。MyBean有三個基本屬性id,name,class。
然後再使用自定義的XML Tag定義Bean:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:Mybean="http://www.huaijin.com/schema/MyBean"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.huaijin.com/schema/MyBean http://www.huaijin.com/schema/MyBean/MyBean.xsd">
<!-- 利用自定義的XML Tag定義Bean -->
<Mybean:MyBean id="myHelloService" class="com.learn.ioc.bean.parser.extend.MyHelloService"></Mybean:MyBean>
</beans>
再編寫BeanDefinitionParser實現:
/**
* 自定義Bean定義解析器
*
* @author huaijin
*/
public class MyBeanBeanDefinitionParser implements BeanDefinitionParser {
private static final String TAG_ID = "id";
private static final String TAG_CLASS = "class";
@Override
public BeanDefinition parse(Element element, ParserContext parserContext) {
// 獲取id屬性
String id = element.getAttribute(TAG_ID);
// 獲取class屬性
String classType = element.getAttribute(TAG_CLASS);
// 校驗id和class屬性
if (id == null || id.isEmpty()) {
throw new BeanDefinitionParsingException(new Problem("id must be not null.",
new Location(parserContext.getReaderContext().getResource())));
}
if (classType == null || classType.isEmpty()) {
throw new BeanDefinitionParsingException(new Problem("classType must be not null.",
new Location(parserContext.getReaderContext().getResource())));
}
// 使用class建立BeanDefintion
BeanDefinition beanDefinition;
try {
beanDefinition = BeanDefinitionReaderUtils.createBeanDefinition(null, classType,
parserContext.getReaderContext().getBeanClassLoader());
} catch (ClassNotFoundException e) {
throw new BeanDefinitionParsingException(new Problem("classType can't exist.",
new Location(parserContext.getReaderContext().getResource())));
}
// 使用id作為BeanName註冊該BeanDefinition至上下文中
BeanDefinitionHolder beanDefinitionHolder = new BeanDefinitionHolder(beanDefinition, id);
BeanDefinitionReaderUtils.registerBeanDefinition(beanDefinitionHolder, parserContext.getRegistry());
return beanDefinition;
}
}
然後便是編寫NameSpaceHandler,註冊以上的BeanDefinitionParser:
/**
* 自定義擴張的名稱空間解析器
*
* @author huaijin
*/
public class MyBeanNamespaceHandler extends NamespaceHandlerSupport {
@Override
public void init() {
// 註冊BeanDefinitionParser
registerBeanDefinitionParser("MyBean", new MyBeanBeanDefinitionParser());
}
}
最後再配置xsd和自定義的BeanDefinitionParser至Spring中。這個過程需要在resource目錄下配置兩個檔案/META-INF/spring.handlers和/META-INF/spring.schemas。
其中spring.handlers中定義名稱空間和xsd檔案位置的對映,使得Spring能夠根據名稱空間找xsd檔案方便對XML配置進行格式校驗;
spring.schemas中定義名稱空間和NameSpaceHandler的對映,使得Spring在處理XML名稱空間時能夠獲取具體的NameSpaceHandler,通過其獲得註冊的BeanDefinitionParser針對性處理該名稱空間的XML配置。
spring.handlers中配置如下:
http\://www.huaijin.com/schema/MyBean=com.learn.ioc.bean.parser.extend.MyBeanNamespaceHandler
spring.schemas中配置如下:
http\://www.huaijin.com/schema/MyBean/MyBean.xsd=com/learn/ioc/bean/parser/extend/MyBean.xsd
最後再編寫測試主類,從上下文中後去該自定義的配置的Bean,並呼叫方法執行驗證
/**
* 自定義擴充套件解析器Demo
*
* @author huaijin
*/
public class ExtendBeanDefinitionParserDemo {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("/bean.parser-extend/extend-parser.xml");
MyHelloService myHelloService = context.getBean("myHelloService", MyHelloService.class);
myHelloService.sayMyHello();
}
}
執行結果如下:
hello, you!
到這裡,關於BeanDefinition的擴充套件點實戰基本都詳細介紹結束,其中關於各種方式都詳細編碼,如果需要了解更多詳情,可以參考Spring官網對各種方式的描述。下節將從原始碼實現的角度分析這幾種方式的擴充套件原理。
BeanDefinition擴充套件點的原理
本節針對以上的四種方式的擴充套件點原理展開介紹,關於BeanFactoryPostProcessor的原理在前文中已經介紹,這裡不再贅述。關於BeanDefinitionRegistryPostProcessor的原理在BeanFactoryPostProcessor一文的原始碼分析中也有涉獵,即在Spring上下文建立完內部的BeanFactory,載入BeanDefinition後,在例項化和喚醒BeanFactoryPostProcessor的邏輯前,預留了BeanDefinitionRegistryPostProcessor的擴充套件,允許應用向BeanFactory中註冊更多BeanDefinition,以背後續的BeanFactoryPostProcessor進行後置處理。
同時需要注意的是BeanDefinitionRegistryPostProcessor本身也是BeanFactoryProcessor的擴充套件抽象:
// 繼承BeanFactoryProcessor
public interface BeanDefinitionRegistryPostProcessor extends BeanFactoryPostProcessor {
// 該擴充套件點提供了BeanDefinitionRegistry,利用其可以向上下文中註冊BeanDefinition
// 同時也能修改同時也能修改BeanDefinitionRegistry的屬性
void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException;
}
關於ImportBeanDefinitionRegistrar的原理,其中ImportBeanDefinitionRegistrar主要是Spring在處理@Configurer註解時的擴充套件點,需要了解Spring註解配置處理原理的基礎,才能夠清晰的理解,故本文中不做詳細介紹,待後續文章中介紹Spring註解配置原理中再細說ImportBeanDefinitionRegistrar的原理。
本節主要針對BeanDefinitionParser的原理實現做詳細介紹。
為了更好的講解BeanDefinitionParser,這裡先總結下幾個與其相關的重要元件:
- DefaultBeanDefinitionDocumentReader
- BeanDefinitionParserDelegate
- DefaultNamespaceHandlerResolver
- NameSpaceHandler
DefaultBeanDefinitionDocumentReader和BeanDefinitionParserDelegate在前面的Spring原始碼系列 — BeanDefinition文章有過原始碼程度的分析。前者主要負責讀取Document文件中的BeanDefinition配置,後者負責解析配置並負責委託處理其他的名稱空間配置的解析。
DefaultNamespaceHandlerResolver是用於解析名稱空間處理器,它主要提供根據XML名稱空間解析NameSpaceHandler的能力。
NameSpaceHandler提供兩個能力,其一是能夠註冊BeanDefinitionParser和XML Tag的對映關係;其二提供根據XML Tag尋找BeanDefinitionParser。
總結下,即DefaultNamespaceHandlerResolver包含XML名稱空間和NameSpaceHandler的對映關係,NameSpaceHandler中包含XML Tag和BeanDefinitionParser的對映關係。
觸發BeanDefinitionParser XML Tag的流程如下:
接下來就從原始碼的角度分析下這個流程。仍然回到DefaultBeanDefinitionDocumentReader中parseBeanDefinitions方法:
protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) {
// 判斷XML根元素是否為預設Beans名稱空間,如果是則按照預設方式解析
if (delegate.isDefaultNamespace(root)) {
NodeList nl = root.getChildNodes();
for (int i = 0; i < nl.getLength(); i++) {
Node node = nl.item(i);
if (node instanceof Element) {
Element ele = (Element) node;
// 判斷子元素是否為預設的Beans名稱空間,如果是則解析Beans
if (delegate.isDefaultNamespace(ele)) {
parseDefaultElement(ele, delegate);
}
else {
// 如果不是,則認為是自定義的
delegate.parseCustomElement(ele);
}
}
}
}
else {
// 如果不是,則認為是自定義的
delegate.parseCustomElement(root);
}
}
對於非Beans名稱空間而言,主要進入delegate.parseCustomElement分支,解析自定義的XML Tag。
再來詳細看parseCustomElement實現:
// 解析BeanDefinition
public BeanDefinition parseCustomElement(Element ele) {
return parseCustomElement(ele, null);
}
public BeanDefinition parseCustomElement(Element ele, BeanDefinition containingBd) {
// 獲取該Element對應的名稱空間,利用了Java XML提供的介面
String namespaceUri = getNamespaceURI(ele);
// 根據名稱空間獲取NameSpaceHandler
NamespaceHandler handler = this.readerContext.getNamespaceHandlerResolver().resolve(namespaceUri);
if (handler == null) {
error("Unable to locate Spring NamespaceHandler for XML schema namespace [" + namespaceUri + "]", ele);
return null;
}
// 利用NameSpaceHandler解析Element
return handler.parse(ele, new ParserContext(this.readerContext, this, containingBd));
}
其中主要就是根據名稱空間獲取NameSpaceHandler,然後利用handler解析XML ELemnent為BeanDefinition。主要關注NameSpaceHandler的獲取過程:
@Override
public NamespaceHandler resolve(String namespaceUri) {
// 獲取名稱空間和NameSpaceHandler的對映關係
Map<String, Object> handlerMappings = getHandlerMappings();
// 根據名稱空間獲取NameSpaceHandler
Object handlerOrClassName = handlerMappings.get(namespaceUri);
// 如果為空,則返回null
if (handlerOrClassName == null) {
return null;
}
// 如果直接是NameSpaceHandler的例項,則直接返回
else if (handlerOrClassName instanceof NamespaceHandler) {
return (NamespaceHandler) handlerOrClassName;
}
// 否則認為是NameSpaceHandler實現的類名
else {
// 轉化為類名
String className = (String) handlerOrClassName;
try {
// 獲取對應的Class物件
Class<?> handlerClass = ClassUtils.forName(className, this.classLoader);
if (!NamespaceHandler.class.isAssignableFrom(handlerClass)) {
throw new FatalBeanException("Class [" + className + "] for namespace [" + namespaceUri +
"] does not implement the [" + NamespaceHandler.class.getName() + "] interface");
}
// 根據Class物件,建立NameSpaceHandler例項
NamespaceHandler namespaceHandler = (NamespaceHandler) BeanUtils.instantiateClass(handlerClass);
// 執行初始化方法
namespaceHandler.init();
// 覆蓋原有的對映關係,快取作用
handlerMappings.put(namespaceUri, namespaceHandler);
// 返回NameSpaceHandler
return namespaceHandler;
}
catch (ClassNotFoundException ex) {
throw new FatalBeanException("NamespaceHandler class [" + className + "] for namespace [" +
namespaceUri + "] not found", ex);
}
catch (LinkageError err) {
throw new FatalBeanException("Invalid NamespaceHandler class [" + className + "] for namespace [" +
namespaceUri + "]: problem with handler class file or dependent class", err);
}
}
}
以上的邏輯也非常簡單,首先獲取名稱空間和NameSpaceHandler的對映關係,然後根據名稱空間獲取相應的NameSpaceHandler。這裡主要需要關注的是如何獲取名稱空間和NameSpaceHandler的對映關係:
private Map<String, Object> getHandlerMappings() {
Map<String, Object> handlerMappings = this.handlerMappings;
// 如果handlerMappings不為空,則直接返回,否則載入handlerMappings
if (handlerMappings == null) {
// 對handlerMappings的修改有資料競態,同步
synchronized (this) {
// 雙重鎖定檢查,如果仍然為空,則載入handlerMappings
handlerMappings = this.handlerMappings;
if (handlerMappings == null) {
if (logger.isDebugEnabled()) {
logger.debug("Loading NamespaceHandler mappings from [" + this.handlerMappingsLocation + "]");
}
try {
// 根據handlerMappingsLocation指定的文章,使用工具載入properties
Properties mappings =
PropertiesLoaderUtils.loadAllProperties(this.handlerMappingsLocation, this.classLoader);
if (logger.isDebugEnabled()) {
logger.debug("Loaded NamespaceHandler mappings: " + mappings);
}
// 將properties轉為ConcurrentHashMap
handlerMappings = new ConcurrentHashMap<String, Object>(mappings.size());
CollectionUtils.mergePropertiesIntoMap(mappings, handlerMappings);
this.handlerMappings = handlerMappings;
}
catch (IOException ex) {
throw new IllegalStateException(
"Unable to load NamespaceHandler mappings from location [" + this.handlerMappingsLocation + "]", ex);
}
}
}
}
return handlerMappings;
}
需要注意的是,這裡Spring使用了約定配置的做法,對於獲取對映關係配置,是由Spring框架內建和應用擴充套件的。在spring中定義了預設的配置檔案位置:
public static final String DEFAULT_HANDLER_MAPPINGS_LOCATION = "META-INF/spring.handlers";
即在類路徑下的META-INF/spring.handlers中配置。這是spring約定。所以上節的案例中也配置該檔案。同時在spring的其他模組,如:beans、context、aop中都有該配置檔案。
beans模組中配置如下:
http\://www.springframework.org/schema/c=org.springframework.beans.factory.xml.SimpleConstructorNamespaceHandler
http\://www.springframework.org/schema/p=org.springframework.beans.factory.xml.SimplePropertyNamespaceHandler
http\://www.springframework.org/schema/util=org.springframework.beans.factory.xml.UtilNamespaceHandler
context模組中配置如下:
http\://www.springframework.org/schema/context=org.springframework.context.config.ContextNamespaceHandler
http\://www.springframework.org/schema/jee=org.springframework.ejb.config.JeeNamespaceHandler
http\://www.springframework.org/schema/lang=org.springframework.scripting.config.LangNamespaceHandler
http\://www.springframework.org/schema/task=org.springframework.scheduling.config.TaskNamespaceHandler
http\://www.springframework.org/schema/cache=org.springframework.cache.config.CacheNamespaceHandler
aop模組配置如下:
http\://www.springframework.org/schema/aop=org.springframework.aop.config.AopNamespaceHandler
DefaultNameSpaceHandlerResovler中中持有名稱空間和名稱空間處理器的對映關係。在獲取到相應的名稱空間處理器後,需要進行初始化。初始化的過程就是註冊BeanDefinitionParser的過程,該過程主要是建立XML Tag與BeanDefinitionParser的之間的對映關係。如上節的案例中,建立了"MyBean"的Tag和MyBeanDefinitionParser之間的關係。這裡以ContextNamespaceHandler為例,講解其init方法的細節:
public class ContextNamespaceHandler extends NamespaceHandlerSupport {
// 初始化,註冊BeanDefinitionParser,建立XML Tag與BeanDefinitionParser之間的關係
@Override
public void init() {
// 註冊PropertyPlaceholderBeanDefinitionParser,讓其解析Tag:"property-placeholder"
registerBeanDefinitionParser("property-placeholder", new PropertyPlaceholderBeanDefinitionParser());
// 註冊PropertyOverrideBeanDefinitionParser,讓其解析Tag:"property-override"
registerBeanDefinitionParser("property-override", new PropertyOverrideBeanDefinitionParser());
// 註冊AnnotationConfigBeanDefinitionParser,讓其解析Tag:"annotation-config"
registerBeanDefinitionParser("annotation-config", new AnnotationConfigBeanDefinitionParser());
// 註冊ComponentScanBeanDefinitionParser,讓其解析Tag:"ComponentScanBeanDefinitionParser"
registerBeanDefinitionParser("component-scan", new ComponentScanBeanDefinitionParser());
// 註冊LoadTimeWeaverBeanDefinitionParser,讓其解析Tag:"load-time-weaver"
registerBeanDefinitionParser("load-time-weaver", new LoadTimeWeaverBeanDefinitionParser());
// 註冊SpringConfiguredBeanDefinitionParser,讓其解析Tag:"spring-configured"
registerBeanDefinitionParser("spring-configured", new SpringConfiguredBeanDefinitionParser());
// 註冊MBeanExportBeanDefinitionParser,讓其解析Tag:"mbean-export"
registerBeanDefinitionParser("mbean-export", new MBeanExportBeanDefinitionParser());
// 註冊MBeanServerBeanDefinitionParser,讓其解析Tag:"mbean-server"
registerBeanDefinitionParser("mbean-server", new MBeanServerBeanDefinitionParser());
}
}
從以上ContextNameSpaceHandelr中可以看出Context名稱空間下的各個XML Tag所對應的BeanDefinitionParser是什麼。比如常用的component:scan標籤由ComponentScanBeanDefinitionParser負責解析。關於這些BeanDefinitonParser的實現細節,將在下篇Spring中註解處理中挑一些詳細介紹,這裡不再詳述。
再繼續看handler.parse的實現,其中主要是根據元素的Tag尋找對應的BeanDefinitionParser,然後解析XML Element為對應的BeanDefinition。仍然以ContextNameSpaceHandler為例介紹:
@Override
public BeanDefinition parse(Element element, ParserContext parserContext) {
// 查詢BeanDefinitionParser,然後解析Element
return findParserForElement(element, parserContext).parse(element, parserContext);
}
private BeanDefinitionParser findParserForElement(Element element, ParserContext parserContext) {
// 獲取Element的Tag
String localName = parserContext.getDelegate().getLocalName(element);
// 根據Tag獲取BeanDefinitionParser
BeanDefinitionParser parser = this.parsers.get(localName);
if (parser == null) {
parserContext.getReaderContext().fatal(
"Cannot locate BeanDefinitionParser for element [" + localName + "]", element);
}
// 返回Parser
return parser;
}
在NameSpaceHandler中是利用Map儲存Tag與BeanDefinitionParser之間的對映關係的。
到這裡,應該能從頭至尾非常清楚的瞭解了BeanDefinitionParser支撐應用自定義擴充套件XML Tag解析BeanDefintion的原理了。
總結
本文主要介紹了Spring中BeanDefition中處理的擴充套件點。主要從擴充套件點的方式、實戰案例、原理三個方面層層深入介紹。