Spring 二、主要邏輯代碼賞析之獲取bean的定義

  上一節中,我們講了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。具體的源碼邏輯我們下一節再說。

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章