在上一篇文章中,分析了ApplicationContext容器的建立,載入資原始檔,將資原始檔讀取為Document。spring將xml檔案中的Bean註冊spring定義的BeanDefinition物件。在DefaultBeanDefinitionDocumentReader中對Document屬性的解析委託給BeanDefinitionParserDelegate這個代理類來實現的。
Bean註冊前的準備
DefaultBeanDefinitionDocumentReader的registerBeanDefinitions方法實現如下,首先獲取Document的根元素,接著呼叫doRegisterBeanDefinitions(root)進行註冊
public void registerBeanDefinitions(Document doc, XmlReaderContext readerContext) {
this.readerContext = readerContext;
//獲取根元素
Element root = doc.getDocumentElement();
//註冊BeanDefinition
doRegisterBeanDefinitions(root);
}
doRegisterBeanDefinitions(root)方法的實現如下:
protected void doRegisterBeanDefinitions(Element root) {
//獲取代理類
BeanDefinitionParserDelegate parent = this.delegate;
this.delegate = createDelegate(getReaderContext(), root, parent);
//是否為預設名稱空間
if (this.delegate.isDefaultNamespace(root)) {
String profileSpec = root.getAttribute(PROFILE_ATTRIBUTE);
//是否有profile屬性
if (StringUtils.hasText(profileSpec)) {
String[] specifiedProfiles = StringUtils.tokenizeToStringArray(
profileSpec, BeanDefinitionParserDelegate.MULTI_VALUE_ATTRIBUTE_DELIMITERS);
if (!getReaderContext().getEnvironment().acceptsProfiles(specifiedProfiles)) {
return;
}
}
}
preProcessXml(root);
//解析BeanDefinition
parseBeanDefinitions(root, this.delegate);
postProcessXml(root);
this.delegate = parent;
}
根據不同節點名進行解析
parseBeanDefinitions(root, this.delegate)方法是解析根源素下定義的每一個bean。首先,獲取節點List。其次,判斷每個元素是否為預設的名稱空間中的元素,然後交給不同的方法去解析,具體的實現如下:
protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) {
//如果根元素為預設名稱空間中的元素
if (delegate.isDefaultNamespace(root)) {
//獲取字元素List
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;
//判斷此元素是否為預設名稱空間的元素
if (delegate.isDefaultNamespace(ele)) {
parseDefaultElement(ele, delegate);
}
else {
delegate.parseCustomElement(ele);
}
}
}
}
else {
delegate.parseCustomElement(root);
}
}
spring預設名稱空間節點的解析
下面,我們首先看spring預設名稱空間元素的解析過程,parseDefaultElement(ele, delegate)方法的實現如下:
private void parseDefaultElement(Element ele, BeanDefinitionParserDelegate delegate) {
//節點名為import
if (delegate.nodeNameEquals(ele, IMPORT_ELEMENT)) {
importBeanDefinitionResource(ele);
}
//節點名為alias
else if (delegate.nodeNameEquals(ele, ALIAS_ELEMENT)) {
processAliasRegistration(ele);
}
//節點名為bean
else if (delegate.nodeNameEquals(ele, BEAN_ELEMENT)) {
processBeanDefinition(ele, delegate);
}
//節點名為beans
else if (delegate.nodeNameEquals(ele, NESTED_BEANS_ELEMENT)) {
// recurse 迴圈呼叫
doRegisterBeanDefinitions(ele);
}
}
- import節點的解析
import元素是引入其他的配置檔案,resource屬性是配置檔案的路徑,importBeanDefinitionResource(ele)方法的實現如下,省略了異常處理程式碼:
protected void importBeanDefinitionResource(Element ele) {
//獲取resource屬性值,即其他配置檔案的路徑
String location = ele.getAttribute(RESOURCE_ATTRIBUTE);
// 解析路徑,如"${user.dir}" 這樣的路徑是從在propertie檔案中載入的
location = getReaderContext().getEnvironment().resolveRequiredPlaceholders(location);
Set<Resource> actualResources = new LinkedHashSet<>(4);
// 判斷location 是絕對路徑還是相對路徑
boolean absoluteLocation = false;
absoluteLocation = ResourcePatternUtils.isUrl(location) || ResourceUtils.toURI(location).isAbsolute();
// 絕對路徑
if (absoluteLocation) {
//呼叫loadBeanDefinitions(location, actualResources)方法解析此配置檔案
int importCount = getReaderContext().getReader().loadBeanDefinitions(location, actualResources);
}
}
//相對路徑
else {
int importCount;
Resource relativeResource = getReaderContext().getResource().createRelative(location);
if (relativeResource.exists()) {
//呼叫loadBeanDefinitions(relativeResource)方法解析此配置檔案
importCount = getReaderContext().getReader().loadBeanDefinitions(relativeResource);
actualResources.add(relativeResource);
}
else {
String baseLocation = getReaderContext().getResource().getURL().toString();
importCount = getReaderContext().getReader().loadBeanDefinitions(
StringUtils.applyRelativePath(baseLocation, location), actualResources);
}
}
}
//廣播Import元素處理事件
Resource[] actResArray = actualResources.toArray(new Resource[0]);
getReaderContext().fireImportProcessed(location, actResArray, extractSource(ele));
}
- alias節點的解析
下面介紹alias元素的方法,processAliasRegistration(ele)方法的實現如下:
protected void processAliasRegistration(Element ele) {
//獲取name屬性
String name = ele.getAttribute(NAME_ATTRIBUTE);
//獲取alias屬性
String alias = ele.getAttribute(ALIAS_ATTRIBUTE);
boolean valid = true;
if (!StringUtils.hasText(name)) {
getReaderContext().error("Name must not be empty", ele);
valid = false;
}
if (!StringUtils.hasText(alias)) {
getReaderContext().error("Alias must not be empty", ele);
valid = false;
}
if (valid) {
//呼叫SimpleAliasRegistry類的registerAlias(name, alias)進行註冊
getReaderContext().getRegistry().registerAlias(name, alias);
getReaderContext().fireAliasRegistered(name, alias, extractSource(ele));
}
}
在SimpleAliasRegistry中定義了aliasMap來儲存alias和name的關係,具體實現如下:
private final Map<String, String> aliasMap = new ConcurrentHashMap<>(16);
public void registerAlias(String name, String alias) {
Assert.hasText(name, "`name` must not be empty");
Assert.hasText(alias, "`alias` must not be empty");
synchronized (this.aliasMap) {
//如果alias和name相等,將此關係移除
if (alias.equals(name)) {
this.aliasMap.remove(alias);
}
else {
//先從aliasMap獲取key為alias的beanName
String registeredName = this.aliasMap.get(alias);
if (registeredName != null) {
//如果已存在,return
if (registeredName.equals(name)) {
// An existing alias - no need to re-register
return;
}
//如果不存在,判斷alias是否可以繼承,預設是true
if (!allowAliasOverriding()) {
throw new IllegalStateException("Cannot register alias `" + alias + "` for name `" +
name + "`: It is already registered for name `" + registeredName + "`.");
}
}
//檢查是否存在迴圈依賴
checkForAliasCircle(name, alias);
//註冊alias和name
this.aliasMap.put(alias, name);
}
}
}
- bean節點的解析
processBeanDefinition(ele, delegate)實現如下:
protected void processBeanDefinition(Element ele, BeanDefinitionParserDelegate delegate) {
//解析bean元素,建立BeanDefinitionHolder例項
BeanDefinitionHolder bdHolder = delegate.parseBeanDefinitionElement(ele);
if (bdHolder != null) {
//完成必須的裝配
bdHolder = delegate.decorateBeanDefinitionIfRequired(ele, bdHolder);
try {
// 進行最終的註冊bean
BeanDefinitionReaderUtils.registerBeanDefinition(bdHolder, getReaderContext().getRegistry());
}
catch (BeanDefinitionStoreException ex) {
getReaderContext().error("Failed to register bean definition with name `" +
bdHolder.getBeanName() + "`", ele, ex);
}
// Send registration event.
getReaderContext().fireComponentRegistered(new BeanComponentDefinition(bdHolder));
}
}
bean元素的解析和註冊相對複雜,在下一節中討論。