上一節中,我們講了Spring主要類的實現邏輯與繼承關係,這一節中我們來看一下Spring如何通過解析xml文件來獲取到bean的定義。
首先根據一個例子來說明,下面是Spring配置文件中的一個 <bean> 節點定義,那麼Spring如何解析這個節點,然後生成對應的beanDefinition呢?
<bean id="dependC" class="com.happyheng.bean.DependC">
<constructor-arg ref="dependA"/>
</bean>
首先看一下ClassPathXmlApplicationContext初始化中的refresh()方法:
@Override
public void refresh() throws BeansException, IllegalStateException {
synchronized (this.startupShutdownMonitor) {
// Prepare this context for refreshing.
prepareRefresh();
. . .
// 此爲獲取一個 DefaultListableBeanFactory 的 beanFactory,並且會生成 XmlBeanDefinitionReader ,從xml文件中讀出beanDefinition並放置到 beanFactory中
ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
// Prepare the bean factory for use in this context.
prepareBeanFactory(beanFactory);
try {
. . .
// DefaultListableBeanFactory 會在此方法中根據 BeanDefinition 去加載bean的定義。
finishBeanFactoryInitialization(beanFactory);
finishRefresh();
}
catch (BeansException ex) {
. . .
throw ex;
}
finally {
. . .
}
}
}
XmlBeanDefinitionReader 會調用 DefaultBeanDefinitionDocumentReader 來進行bean定義的獲取,其中root即爲xml文件中的根節點:
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);
if (StringUtils.hasText(profileSpec)) {
String[] specifiedProfiles = StringUtils.tokenizeToStringArray(
profileSpec, BeanDefinitionParserDelegate.MULTI_VALUE_ATTRIBUTE_DELIMITERS);
if (!getReaderContext().getEnvironment().acceptsProfiles(specifiedProfiles)) {
return;
}
}
}
preProcessXml(root);
// 此爲將xml的root傳入,然後將解析的代理類傳入,使用代理類來解析這個root中的子節點
parseBeanDefinitions(root, this.delegate);
postProcessXml(root);
this.delegate = parent;
}
root中可能有很多子節點,所以得到root的子節點列表,得到之後,調用 parseDefaultElement(ele, delegate) 方法來進行解析子節點。
注意這有一個判斷 delegate.isDefaultNamespace(ele) ,這個判斷是很有必要的,比如我們如果寫一個
<bean id="user" class="com.happyheng.User"><bean/>
那麼肯定是屬於默認的namespace的,即xml解析的namespace。
但是如果是
<context:component-scan base-package="com.happyheng.runtime"/>
那麼就是不屬於xml中默認的 namespace的 ,就是由Spring-Context這個組件去解析了,即走的
delegate.parseCustomElement(ele);
這個方法,這個方法將節點傳入之後,先通過工廠類找到解析這節點的namespace的handler,(handler與delegate相同,都是來解析node的)再由handler來解析這個node。即如果有其他namespace的,實現handler與相應工廠類,就可以增加解析了。解析會得到相應的registry然後將beanDefinition設置進去的。
protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) {
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;
// 注意此爲判斷這個node的namaSpace,如果爲<bean/>,則爲beans的namespace,否則爲其它context的namespace
if (delegate.isDefaultNamespace(ele)) {
parseDefaultElement(ele, delegate);
}
else {
delegate.parseCustomElement(ele);
}
}
}
}
else {
delegate.parseCustomElement(root);
}
}
下面的方法即爲根據不同的bean的類型去獲取到bean的定義,因爲我們是
<bean><bean/>
所以是 processBeanDefinition
private void parseDefaultElement(Element ele, BeanDefinitionParserDelegate delegate) {
if (delegate.nodeNameEquals(ele, IMPORT_ELEMENT)) {
importBeanDefinitionResource(ele);
}
else if (delegate.nodeNameEquals(ele, ALIAS_ELEMENT)) {
processAliasRegistration(ele);
}
else if (delegate.nodeNameEquals(ele, BEAN_ELEMENT)) {
processBeanDefinition(ele, delegate);
}
else if (delegate.nodeNameEquals(ele, NESTED_BEANS_ELEMENT)) {
// recurse
doRegisterBeanDefinitions(ele);
}
}
最後進入這個方法中:
public BeanDefinitionHolder parseBeanDefinitionElement(Element ele, BeanDefinition containingBean) {
// 此爲得到bean的Id
String id = ele.getAttribute(ID_ATTRIBUTE);
String nameAttr = ele.getAttribute(NAME_ATTRIBUTE);
List<String> aliases = new ArrayList<String>();
if (StringUtils.hasLength(nameAttr)) {
String[] nameArr = StringUtils.tokenizeToStringArray(nameAttr, MULTI_VALUE_ATTRIBUTE_DELIMITERS);
aliases.addAll(Arrays.asList(nameArr));
}
// bean的Name使用id來代替,可見在第一節中說的是正確的,即bean的標識符使用id,否則使用name
String beanName = id;
if (!StringUtils.hasText(beanName) && !aliases.isEmpty()) {
beanName = aliases.remove(0);
if (logger.isDebugEnabled()) {
logger.debug("No XML 'id' specified - using '" + beanName +
"' as bean name and " + aliases + " as aliases");
}
}
if (containingBean == null) {
checkNameUniqueness(beanName, aliases, ele);
}
// 注意此爲解析bean中的一些屬性,比如 <constructor-arg><constructor-arg/> <property><<property/> 解析出來之後,將其放到BeanDefinition中
AbstractBeanDefinition beanDefinition = parseBeanDefinitionElement(ele, beanName, containingBean);
. . .
return null;
}
public AbstractBeanDefinition parseBeanDefinitionElement(
Element ele, String beanName, BeanDefinition containingBean) {
this.parseState.push(new BeanEntry(beanName));
String className = null;
if (ele.hasAttribute(CLASS_ATTRIBUTE)) {
className = ele.getAttribute(CLASS_ATTRIBUTE).trim();
}
try {
. . .
AbstractBeanDefinition bd = createBeanDefinition(className, parent);
parseBeanDefinitionAttributes(ele, beanName, containingBean, bd);
// 解析 <constructor-arg><constructor-arg/>這個初始化方法的屬性
parseConstructorArgElements(ele, bd);
// 解析 <property><property/>這個bean中的屬性
parsePropertyElements(ele, bd);
. . .
return bd;
}
. . .
return null;
}
我們看一下上述的
<bean id="dependC" class="com.happyheng.bean.DependC">
<constructor-arg ref="dependA"/>
</bean>
中的constructor-arg是如何解析出來的?
public void parseConstructorArgElement(Element ele, BeanDefinition bd) {
String indexAttr = ele.getAttribute(INDEX_ATTRIBUTE);
String typeAttr = ele.getAttribute(TYPE_ATTRIBUTE);
String nameAttr = ele.getAttribute(NAME_ATTRIBUTE);
if (StringUtils.hasLength(indexAttr)) {
try {
int index = Integer.parseInt(indexAttr);
if (index < 0) {
error("'index' cannot be lower than 0", ele);
}
else {
try {
. . .
this.parseState.push(new ConstructorArgumentEntry(index));
// 此爲獲取 constructor-arg 中的value,我們上述的value是一個ref,也就是Spring中的bean
Object value = parsePropertyValue(ele, bd, null);
. . .
}
finally {
this.parseState.pop();
}
}
}
catch (NumberFormatException ex) {
error("Attribute 'index' of tag 'constructor-arg' must be an integer", ele);
}
}
else {
. . .
}
}
public Object parsePropertyValue(Element ele, BeanDefinition bd, String propertyName) {
String elementName = (propertyName != null) ?
"<property> element for property '" + propertyName + "'" :
"<constructor-arg> element";
// Should only have one child element: ref, value, list, etc.
NodeList nl = ele.getChildNodes();
Element subElement = null;
for (int i = 0; i < nl.getLength(); i++) {
Node node = nl.item(i);
if (node instanceof Element && !nodeNameEquals(node, DESCRIPTION_ELEMENT) &&
!nodeNameEquals(node, META_ELEMENT)) {
// Child element is what we're looking for.
if (subElement != null) {
error(elementName + " must not contain more than one sub-element", ele);
}
else {
subElement = (Element) node;
}
}
}
// 如果是ref,那麼說明是Spring中的bean
boolean hasRefAttribute = ele.hasAttribute(REF_ATTRIBUTE);
boolean hasValueAttribute = ele.hasAttribute(VALUE_ATTRIBUTE);
if ((hasRefAttribute && hasValueAttribute) ||
((hasRefAttribute || hasValueAttribute) && subElement != null)) {
error(elementName +
" is only allowed to contain either 'ref' attribute OR 'value' attribute OR sub-element", ele);
}
if (hasRefAttribute) {
String refName = ele.getAttribute(REF_ATTRIBUTE);
if (!StringUtils.hasText(refName)) {
error(elementName + " contains empty 'ref' attribute", ele);
}
// 可以看到,如果是ref的話,那麼生成一個 RuntimeBeanReference來進行填充
RuntimeBeanReference ref = new RuntimeBeanReference(refName);
ref.setSource(extractSource(ele));
return ref;
}
else if (hasValueAttribute) {
TypedStringValue valueHolder = new TypedStringValue(ele.getAttribute(VALUE_ATTRIBUTE));
valueHolder.setSource(extractSource(ele));
return valueHolder;
}
else if (subElement != null) {
return parsePropertySubElement(subElement, bd);
}
else {
// Neither child element nor "ref" or "value" attribute found.
error(elementName + " must specify a ref or value", ele);
return null;
}
}
可以看到,我們在遍歷這個xml節點的時候,如果發現其有ref這個屬性,就將其生成一個 RuntimeBeanReference 來進行填充,這樣,當beanFactory根據beanDefinition來生成bean的時候,發現bean有對應的RuntimeBeanReference,則說明需要Spring的bean填充進來,然後就會去獲取到這個bean。具體的源碼邏輯我們下一節再說。