Spring Ioc 源码分析(一)- XML 解析
2019獨角獸企業重金招聘Python工程師標準>>>
注 :源碼對應版本為 4.2.8.RELEASE
引入Spring依賴的時候,是否發現spring依賴有spring-beans和spring-context兩個jar,spring容器的頂層接口BeanFactory在spring-beans,而我們常用的ApplicationContext則在spring-context中定義。目前的認知是,ApplicationContext是在BeanFactory的基礎上,提供了很多容器上下文的功能。那么到底BeanFactory提供了哪些功能,ApplicationContext在這些功能的基礎上又額外提供了哪些上下文功能,其中的實現原理是什么?
一、BeanFactory
首先拋開ApplicationContext,我們單獨來看一下spring-beans模塊中提供了哪些接口和功能:
<?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");//實例化一個XmlBeanFactoryXmlBeanFactory beanFactory = new XmlBeanFactory(resource);//這樣就可以工作啦,可以getBean了TargetBean targetBean = beanFactory.getBean(TargetBean.class);System.out.println(targetBean.getName());} }查看XmlBeanFactory的源碼可以看到,也比較簡單:
@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的實現等)
二、XmlBeanDefinitionReader?
從上述代碼中可以看到,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;}三、DocumentLoader
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;}四、BeanDefinitionDocumentReader
public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException {//創建 BeanDefinitionDocumentReader 對象BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader();//記錄調用BeanDefinitionDocumentReader之前已經解析完成的 Bean 數量//getRegistry返回的是我們創建的 DefaultListableBeanFactoryint 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;}4.1? BeanDefinitionParserDelegate
里面使用了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);}@Overridepublic void init() {registerBeanDefinitionParser("advice", new TxAdviceBeanDefinitionParser());registerBeanDefinitionParser("annotation-driven", new AnnotationDrivenBeanDefinitionParser());registerBeanDefinitionParser("jta-transaction-manager", new JtaTransactionManagerBeanDefinitionParser());}}init()方法基本上是固定的寫法,就是為每個標簽定義一個?BeanDefinitionParser?
六、Bean標簽的解析
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, BeanDefinitionBeanDefinitionHolder bdHolder = delegate.parseBeanDefinitionElement(ele);if (bdHolder != null) {//裝飾BeanDefinitionHolder bdHolder = delegate.decorateBeanDefinitionIfRequired(ele, bdHolder);try {// 注冊到容器中,這里就是 DefaultListableBeanFactoryBeanDefinitionReaderUtils.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作為beanNameString 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規則生成beanNameif (!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));//解析metaparseMetaElements(ele, bd);//解析lookup-methodparseLookupOverrideSubElements(ele, bd.getMethodOverrides());//解析replaced-methodparseReplacedMethodSubElements(ele, bd.getMethodOverrides());//解析構造函數參數parseConstructorArgElements(ele, bd);//解析propertyparsePropertyElements(ele, bd);//解析qualifierparseQualifierElements(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);}}}?
轉載于:https://my.oschina.net/u/206123/blog/1549747
總結
以上是生活随笔為你收集整理的Spring Ioc 源码分析(一)- XML 解析的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: C语言对结构体何时用- , 何时用.
- 下一篇: 彻底理解mysql服务器的字符集转换问题