注 :源碼對應版本爲 4.2.8.RELEASEjava
引入Spring依賴的時候,是否發現spring依賴有spring-beans和spring-context兩個jar,spring容器的頂層接口BeanFactory在spring-beans,而咱們經常使用的ApplicationContext則在spring-context中定義。目前的認知是,ApplicationContext是在BeanFactory的基礎上,提供了不少容器上下文的功能。那麼到底BeanFactory提供了哪些功能,ApplicationContext在這些功能的基礎上又額外提供了哪些上下文功能,其中的實現原理是什麼?node
首先拋開ApplicationContext,咱們單獨來看一下spring-beans模塊中提供了哪些接口和功能:web
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" 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-4.2.xsd"> <bean class="lujing.sample.bean.TargetBean" /> </beans>
public class XmlBeanFacotyTest { public static void main(String[] args) { //指定xml文件 Resource resource = new ClassPathResource("spring/applicationContext-beanFactory.xml"); //實例化一個XmlBeanFactory XmlBeanFactory beanFactory = new XmlBeanFactory(resource); //這樣就能夠工做啦,能夠getBean了 TargetBean targetBean = beanFactory.getBean(TargetBean.class); System.out.println(targetBean.getName()); } }
查看XmlBeanFactory的源碼能夠看到,也比較簡單:spring
@Deprecated @SuppressWarnings({"serial", "all"}) public class XmlBeanFactory extends DefaultListableBeanFactory { private final XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(this); /** * Create a new XmlBeanFactory with the given resource, * which must be parsable using DOM. * @param resource XML resource to load bean definitions from * @throws BeansException in case of loading or parsing errors */ public XmlBeanFactory(Resource resource) throws BeansException { this(resource, null); } /** * Create a new XmlBeanFactory with the given input stream, * which must be parsable using DOM. * @param resource XML resource to load bean definitions from * @param parentBeanFactory parent bean factory * @throws BeansException in case of loading or parsing errors */ public XmlBeanFactory(Resource resource, BeanFactory parentBeanFactory) throws BeansException { super(parentBeanFactory); this.reader.loadBeanDefinitions(resource); } }
其實就是2個核心類,分別是 DefaultListableBeanFactory 和 XmlBeanDefinitionReader ,spring自己標識了XmlBeanFactory廢棄,取而代之使用 DefaultListableBeanFactory 和 XmlBeanDefinitionReader,因此以上代碼能夠寫成:設計模式
public class BeanFactoryTest { public static void main(String[] args) { Resource resource = new ClassPathResource("spring/applicationContext-beanFactory.xml"); DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory(null); new XmlBeanDefinitionReader(beanFactory).loadBeanDefinitions(resource); TargetBean targetBean = beanFactory.getBean(TargetBean.class); System.out.println(targetBean.getName()); } }
顯而易見,spring中xml解析是由 XmlBeanDefinitionReader 來完成的。剝離了xml這一層具體的實現(也能夠有基於Annotation的實現等)緩存
從上述代碼中能夠看到,xml到解析工做經過 loadBeanDefinitions方法完成:安全
public int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException { return loadBeanDefinitions(new EncodedResource(resource)); }
public int loadBeanDefinitions(EncodedResource encodedResource) throws BeanDefinitionStoreException { Assert.notNull(encodedResource, "EncodedResource must not be null"); if (logger.isInfoEnabled()) { logger.info("Loading XML bean definitions from " + encodedResource.getResource()); } //這裏使用ThreadLocal一方面爲了線程安全考慮,另外一方面提供存儲 //使用 // private final ThreadLocal<Set<EncodedResource>> resourcesCurrentlyBeingLoaded = // new NamedThreadLocal<Set<EncodedResource>>("XML bean definition resources currently // being loaded"); //而不用 Set<EncodedResource> currentResources = new HashSet<EncodedResource>(4); //是由於該方法可能會被多線程調用。爲了記錄當前正在解析的Resource文件 Set<EncodedResource> currentResources = this.resourcesCurrentlyBeingLoaded.get(); if (currentResources == null) { //初始化set並設置線程上文 currentResources = new HashSet<EncodedResource>(4); this.resourcesCurrentlyBeingLoaded.set(currentResources); } //利用Set的add方法,判斷重複的定義 if (!currentResources.add(encodedResource)) { throw new BeanDefinitionStoreException( "Detected cyclic loading of " + encodedResource + " - check your import definitions!"); } try { InputStream inputStream = encodedResource.getResource().getInputStream(); try { InputSource inputSource = new InputSource(inputStream); if (encodedResource.getEncoding() != null) { inputSource.setEncoding(encodedResource.getEncoding()); } //繼續調用解析 return doLoadBeanDefinitions(inputSource, encodedResource.getResource()); } finally { inputStream.close(); } } catch (IOException ex) { throw new BeanDefinitionStoreException( "IOException parsing XML document from " + encodedResource.getResource(), ex); } finally { //移除線程上限爲變量 currentResources.remove(encodedResource); if (currentResources.isEmpty()) { this.resourcesCurrentlyBeingLoaded.remove(); } } }
從上面的代碼中能夠看到,該方法主要作了:網絡
利用線程上下文值,記錄當前線程正在解析的xml文件。檢查import標籤可能引入的循環解析問題多線程
從這個方法中咱們也能夠借鑑兩種經常使用的寫法:架構
1. 關於ThreadLocal的用法,在使用get(),set()使用完成之後,必需要在finally代碼塊中 remove()掉
2. loadBeanDefinitions 和 doLoadBeanDefinitions的方法命名,咱們發現spring中這類命名特別多。do開頭的方法每每是正真的業務實現,一些外圍處理都環繞在外面。這樣的寫法看起來真的特別舒服,一層一層的,極大的減小了代碼的理解複雜度。同時也爲擴展提供了很好的架構。通常spring中do開頭的方法都是protected的,顯然告訴spring的使用者,這是一個擴展點。這也是設計模式中 「模板模式」很好的體現。
protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource) throws BeanDefinitionStoreException { try { //生成XML Document 文檔對象 Document doc = doLoadDocument(inputSource, resource); //註冊Bean定義 return registerBeanDefinitions(doc, resource); } //catch 各類異常,統一包裝爲 XmlBeanDefinitionStoreException //這裏就不貼代碼了 }
生成XML文檔對象的時候,使用了DocumentLoader實現類來完成,底層主要是利用JAXP完成從xml解析問Document對象:
protected Document doLoadDocument(InputSource inputSource, Resource resource) throws Exception { //private DocumentLoader documentLoader = new DefaultDocumentLoader(); return this.documentLoader.loadDocument(inputSource, getEntityResolver(), this.errorHandler, getValidationModeForResource(resource), isNamespaceAware()); }
Bean的解析使用BeanDefinitionDocumentReader
public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException { BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader(); int countBefore = getRegistry().getBeanDefinitionCount(); documentReader.registerBeanDefinitions(doc, createReaderContext(resource)); return getRegistry().getBeanDefinitionCount() - countBefore; }
spring底層經過JAXP技術,完成從xml到Document對象的解析。
在xml解析中有一點比較重要,就是xml的驗證,它主要分爲DTD驗證和XSD驗證兩種。不管哪種驗證,java都須要加載相應的DTD或XSD文件,默認是直接從xml中對應的地址下載,這裏是 http://www.springframework.org/schema/beans/spring-beans-4.2.xsd
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" 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-4.2.xsd"> <bean class="lujing.sample.bean.TargetBean" /> </beans>
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd"> <beans> <bean class="lujing.sample.bean.TargetBean" /> </beans>
爲了不這樣的網絡請求,jaxp提供了EntityResolver方法,spring就是經過實現該接口,實現了從classpath中加載對應的文件。
protected EntityResolver getEntityResolver() { if (this.entityResolver == null) { // Determine default EntityResolver to use. ResourceLoader resourceLoader = getResourceLoader(); if (resourceLoader != null) { this.entityResolver = new ResourceEntityResolver(resourceLoader); } else { this.entityResolver = new DelegatingEntityResolver(getBeanClassLoader()); } } return this.entityResolver; }
public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException { //建立 BeanDefinitionDocumentReader 對象 BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader(); //記錄調用BeanDefinitionDocumentReader以前已經解析完成的 Bean 數量 //getRegistry返回的是咱們建立的 DefaultListableBeanFactory int countBefore = getRegistry().getBeanDefinitionCount(); //解析 documentReader.registerBeanDefinitions(doc, createReaderContext(resource)); //如今的Bean數量減去以前的數量,就是本次加載的Bean數量 return getRegistry().getBeanDefinitionCount() - countBefore; }
建立BeanDefinitionDocumentReader就是根據設定的class實例化,默認實現是org.springframework.beans.factory.xml.DefaultBeanDefinitionDocumentReader
protected BeanDefinitionDocumentReader createBeanDefinitionDocumentReader() { return BeanDefinitionDocumentReader.class.cast(BeanUtils.instantiateClass(this.documentReaderClass)); }
具體的BeanDefinitionDocumentReader經過registerBeanDefinitions方法完成。
建立XmlReaderContext主要是把XmlBeanDefinitionReader中設置的NamespaceHandlerResolver傳遞過去,在解析的時候須要用到。這也是設計模式中「門面模式」的體現。真正的邏輯都是委託給別的核心組件來完成。
public XmlReaderContext createReaderContext(Resource resource) { return new XmlReaderContext(resource, this.problemReporter, this.eventListener, this.sourceExtractor, this, getNamespaceHandlerResolver()); }
public void registerBeanDefinitions(Document doc, XmlReaderContext readerContext) { //設置解析上下文 this.readerContext = readerContext; logger.debug("Loading bean definitions"); //獲取根節點 Element root = doc.getDocumentElement(); //調用真正的解析方法 doRegisterBeanDefinitions(root); }
下面是真正的解析核心方法了,方法仍舊是一個模版方法,對最外層的beans節點,作了過濾
解析代碼的時候都用到了 delegate.isDefaultNamespace(element)方法,該方法的用處就是判斷元素節點是否是 beans 命名空間下的 節點,或者是 自定義節點如 <tx:annotation-driver> 。spring判斷的依據就是節點的namespace是否是固定的 http://www.springframework.org/schema/beans,spring在xml解析的時候,設置了 namespaceAware參數爲true,每一個節點均可以取到 namespace。
protected void doRegisterBeanDefinitions(Element root) { // 解析一個<beans/>節點,任何嵌套的beans標籤都會從新調用一次這個方法 //每一個beans節點都有一個BeanDefinitionParserDelegate,同時再實例化的時候指定 parent //這裏主要考慮 傳播和保存 benas節點中 default-*(default-init-method等)值 BeanDefinitionParserDelegate parent = this.delegate; this.delegate = createDelegate(getReaderContext(), root, parent); //profile機制只對標準beans定義的生效,對於自定義標籤訂義的beans不生效 if (this.delegate.isDefaultNamespace(root)) { //讀取profile屬性值,用於區分如生產、測試等 String profileSpec = root.getAttribute(PROFILE_ATTRIBUTE); //未指定profile默認bean都是生效的 if (StringUtils.hasText(profileSpec)) { String[] specifiedProfiles = StringUtils.tokenizeToStringArray( profileSpec, BeanDefinitionParserDelegate.MULTI_VALUE_ATTRIBUTE_DELIMITERS); //若是指定了 profiles 且 不符合 environment定義的 激活的 profiles ,那麼直接忽略 if (!getReaderContext().getEnvironment().acceptsProfiles(specifiedProfiles)) { return; } } } //擴展點方法,默認實現爲空 preProcessXml(root); //利用BeanDefinitionParserDelegate真正的解析節點 parseBeanDefinitions(root, this.delegate); //擴展點方法,默認實現爲空 postProcessXml(root); this.delegate = parent; }
裏面使用了BeanDefinitionParserDelegate委派處理:
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; if (delegate.isDefaultNamespace(ele)) { //默認節點 parseDefaultElement(ele, delegate); } else { //自定義標籤解析,如常見的 <tx:annotation-driven/> delegate.parseCustomElement(ele); } } } } //是自定義節點,調用自定義節點的解析方式,這裏的考慮點仍是 自定義的 beans 標籤,好比 <coustom:beans/> else { delegate.parseCustomElement(root); } }
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)) { doRegisterBeanDefinitions(ele); } }
import標籤其實就是循環解析xml文件,這裏就不貼代碼了;alias標籤其實就是註冊別名,也很是簡單。
spring 的 自定義標籤機制是 spring的擴展的基礎,衆多功能如context,webmvc,transaction都使用自定義標籤,方便用戶再極簡配置就能享受強大的功能。如 bean掃描<context:component-scan base-package=""/>,事務的<tx:annotation-driven/>等
public BeanDefinition parseCustomElement(Element ele) { return parseCustomElement(ele, null); } public BeanDefinition parseCustomElement(Element ele, BeanDefinition containingBd) { //獲取命名空間 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; } //自定義解析器的 parse 方法 return handler.parse(ele, new ParserContext(this.readerContext, this, containingBd)); }
從上面的代碼能夠看到,自定義解析大概分爲3步:
1. 獲取自定義標籤的 namespaceUri ,用的是 node.getNamespaceURI(); ,以<tx:annotation-driven/>爲例就是: http://www.springframework.org/schema/tx
xmlns:tx="http://www.springframework.org/schema/tx"
2. 利用 NamespaceHandlerResolver 的 resolve 方法實例化一個 NamespaceHandler
public interface NamespaceHandlerResolver { /** * 根據namespaceUri實例化一個NamespaceHandler對象 */ NamespaceHandler resolve(String namespaceUri); }
這裏使用的是 DefaultNamespaceHandlerResolver 實現類:
public NamespaceHandler resolve(String namespaceUri) { //加載 namespaceUri=namespaceHandler 對應表 Map<String, Object> handlerMappings = getHandlerMappings(); Object handlerOrClassName = handlerMappings.get(namespaceUri); if (handlerOrClassName == null) { return null; } // 若是是NamespaceHandler對象就直接返回。這裏對象的判斷是,前面若是有該標籤出現過,這裏直接緩存對象了 else if (handlerOrClassName instanceof NamespaceHandler) { return (NamespaceHandler) handlerOrClassName; } //NamespaceHandler的完整類路徑處理 else { String className = (String) handlerOrClassName; try { 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"); } NamespaceHandler namespaceHandler = (NamespaceHandler) BeanUtils.instantiateClass(handlerClass); //init方法,注意這個方法很重要,是用戶擴展點中很重要的一點 namespaceHandler.init(); //覆蓋原來的,避免屢次實例化 handlerMappings.put(namespaceUri, 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); } } }
能夠看到 getHandlerMappings()方法讀取了全部的 META-INF/spring.handlers" 文件。仍是以 tx爲例,在spring-tx.jar 能夠看到 META-INF/spring.handlers 文件的內容:
http\://www.springframework.org/schema/tx=org.springframework.transaction.config.TxNamespaceHandler
在上面的代碼中咱們能夠看到,init()方法是生命週期方法,用於初始化。
public class TxNamespaceHandler extends NamespaceHandlerSupport { static final String TRANSACTION_MANAGER_ATTRIBUTE = "transaction-manager"; static final String DEFAULT_TRANSACTION_MANAGER_BEAN_NAME = "transactionManager"; static String getTransactionManagerName(Element element) { return (element.hasAttribute(TRANSACTION_MANAGER_ATTRIBUTE) ? element.getAttribute(TRANSACTION_MANAGER_ATTRIBUTE) : DEFAULT_TRANSACTION_MANAGER_BEAN_NAME); } @Override public void init() { registerBeanDefinitionParser("advice", new TxAdviceBeanDefinitionParser()); registerBeanDefinitionParser("annotation-driven", new AnnotationDrivenBeanDefinitionParser()); registerBeanDefinitionParser("jta-transaction-manager", new JtaTransactionManagerBeanDefinitionParser()); } }
init()方法基本上是固定的寫法,就是爲每一個標籤訂義一個 BeanDefinitionParser
org.springframework.beans.factory.xml.DefaultBeanDefinitionDocumentReader.java
/** * Process the given bean element, parsing the bean definition * and registering it with the registry. */ protected void processBeanDefinition(Element ele, BeanDefinitionParserDelegate delegate) { //利用delegate獲取一個BeanDefinitionHolder對象,包含了beanName,alias, BeanDefinition BeanDefinitionHolder bdHolder = delegate.parseBeanDefinitionElement(ele); if (bdHolder != null) { //裝飾BeanDefinitionHolder bdHolder = delegate.decorateBeanDefinitionIfRequired(ele, bdHolder); try { // 註冊到容器中,這裏就是 DefaultListableBeanFactory BeanDefinitionReaderUtils.registerBeanDefinition(bdHolder, getReaderContext().getRegistry()); } catch (BeanDefinitionStoreException ex) { getReaderContext().error("Failed to register bean definition with name '" + bdHolder.getBeanName() + "'", ele, ex); } // 發送事件通知 getReaderContext().fireComponentRegistered(new BeanComponentDefinition(bdHolder)); } }
org.springframework.beans.factory.xml.BeanDefinitionParserDelegate.java
/** * 解析<bean>節點,若是解析錯誤返回null.錯誤被通知到 * org.springframework.beans.factory.parsing.ProblemReporter */ public BeanDefinitionHolder parseBeanDefinitionElement(Element ele) { return parseBeanDefinitionElement(ele, null); }
簡單的作了一個適配:
/** * 解析<bean>節點,若是解析錯誤返回null.錯誤被通知到 * org.springframework.beans.factory.parsing.ProblemReporter */ public BeanDefinitionHolder parseBeanDefinitionElement(Element ele, BeanDefinition containingBean) { //讀取id屬性 String id = ele.getAttribute(ID_ATTRIBUTE); //讀取name屬性 String nameAttr = ele.getAttribute(NAME_ATTRIBUTE); //處理name屬性爲別名 List<String> aliases = new ArrayList<String>(); if (StringUtils.hasLength(nameAttr)) { String[] nameArr = StringUtils.tokenizeToStringArray(nameAttr, MULTI_VALUE_ATTRIBUTE_DELIMITERS); aliases.addAll(Arrays.asList(nameArr)); } //默認是beanName爲id屬性,沒有id屬性使用第一個beanName做爲beanName 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) { //校驗beanName的惟一性,在同一個Beans標籤下面不能重複 checkNameUniqueness(beanName, aliases, ele); } AbstractBeanDefinition beanDefinition = parseBeanDefinitionElement(ele, beanName, containingBean); if (beanDefinition != null) { //若是沒有指定beanName使用,使用spring規則生成beanName if (!StringUtils.hasText(beanName)) { try { if (containingBean != null) { beanName = BeanDefinitionReaderUtils.generateBeanName( beanDefinition, this.readerContext.getRegistry(), true); } else { beanName = this.readerContext.generateBeanName(beanDefinition); String beanClassName = beanDefinition.getBeanClassName(); if (beanClassName != null && beanName.startsWith(beanClassName) && beanName.length() > beanClassName.length() && !this.readerContext.getRegistry().isBeanNameInUse(beanClassName)) { aliases.add(beanClassName); } } if (logger.isDebugEnabled()) { logger.debug("Neither XML 'id' nor 'name' specified - " + "using generated bean name [" + beanName + "]"); } } catch (Exception ex) { error(ex.getMessage(), ele); return null; } } String[] aliasesArray = StringUtils.toStringArray(aliases); return new BeanDefinitionHolder(beanDefinition, beanName, aliasesArray); } return null; }
public AbstractBeanDefinition parseBeanDefinitionElement( Element ele, String beanName, BeanDefinition containingBean) { //出錯的時候 error 要用到 this.parseState.push(new BeanEntry(beanName)); //class屬性 String className = null; if (ele.hasAttribute(CLASS_ATTRIBUTE)) { className = ele.getAttribute(CLASS_ATTRIBUTE).trim(); } try { //parent屬性 String parent = null; if (ele.hasAttribute(PARENT_ATTRIBUTE)) { parent = ele.getAttribute(PARENT_ATTRIBUTE); } //自定義的bean標籤返回的是 GenericBeanDefinition AbstractBeanDefinition bd = createBeanDefinition(className, parent); //解析各類屬性 lazy-init,init-method 等 parseBeanDefinitionAttributes(ele, beanName, containingBean, bd); //description節點直接讀 bd.setDescription(DomUtils.getChildElementValueByTagName(ele, DESCRIPTION_ELEMENT)); //解析meta parseMetaElements(ele, bd); //解析lookup-method parseLookupOverrideSubElements(ele, bd.getMethodOverrides()); //解析replaced-method parseReplacedMethodSubElements(ele, bd.getMethodOverrides()); //解析構造函數參數 parseConstructorArgElements(ele, bd); //解析property parsePropertyElements(ele, bd); //解析qualifier parseQualifierElements(ele, bd); bd.setResource(this.readerContext.getResource()); bd.setSource(extractSource(ele)); return bd; } catch (ClassNotFoundException ex) { error("Bean class [" + className + "] not found", ele, ex); } catch (NoClassDefFoundError err) { error("Class that bean class [" + className + "] depends on not found", ele, err); } catch (Throwable ex) { error("Unexpected failure during bean definition parsing", ele, ex); } finally { this.parseState.pop(); } return null; }
終於看到了對全部屬性元素的解析,其餘都是一些相對簡單的解析,最難的就是property節點的解析:
public void parsePropertyElements(Element beanEle, BeanDefinition bd) { NodeList nl = beanEle.getChildNodes(); for (int i = 0; i < nl.getLength(); i++) { //遍歷子節點 //isCandidateElement: //(node instanceof Element && (isDefaultNamespace(node) || !isDefaultNamespace(node.getParentNode()))); //標籤名稱是property //調用parsePropertyElement解析 Node node = nl.item(i); if (isCandidateElement(node) && nodeNameEquals(node, PROPERTY_ELEMENT)) { parsePropertyElement((Element) node, bd); } } }