2.1 doLoadBeanDefinitions(inputSource, encodedResource.getResource())
/** * Actually load bean definitions from the specified XML file. * @param inputSource the SAX InputSource to read from * @param resource the resource descriptor for the XML file * @return the number of bean definitions found * @throws BeanDefinitionStoreException in case of loading or parsing errors * @see #doLoadDocument * @see #registerBeanDefinitions */ protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource) throws BeanDefinitionStoreException { try { //獲取XML的驗證方式,加載XML文件得到對應的Document Document doc = doLoadDocument(inputSource, resource); //根據返回的Dcoument注冊Bean信息 return registerBeanDefinitions(doc, resource); } ......................................... }
2,2 Document doc = doLoadDocument(inputSource, resource);
/** * Actually load the specified document using the configured DocumentLoader. * @param inputSource the SAX InputSource to read from * @param resource the resource descriptor for the XML file * @return the DOM Document * @throws Exception when thrown from the DocumentLoader * @see #setDocumentLoader * @see DocumentLoader#loadDocument */ protected Document doLoadDocument(InputSource inputSource, Resource resource) throws Exception { return this.documentLoader.loadDocument(inputSource, getEntityResolver(), this.errorHandler, getValidationModeForResource(resource), isNamespaceAware()); }
2.2.3 getValidationModeForResource(resource) DTD或者XSD文件格式的讀取
/** * Gets the validation mode for the specified {@link Resource}. If no explicit * validation mode has been configured then the validation mode is * {@link #detectValidationMode detected}. * <p>Override this method if you would like full control over the validation * mode, even when something other than {@link #VALIDATION_AUTO} was set. */ protected int getValidationModeForResource(Resource resource) { int validationModeToUse = getValidationMode(); // 如果手動指定了校驗模式,則使用指定的校驗模式 if (validationModeToUse != VALIDATION_AUTO) { return validationModeToUse; } // 如果手動沒有指定校驗模式,則使用自動檢測 int detectedMode = detectValidationMode(resource); if (detectedMode != VALIDATION_AUTO) { return detectedMode; } // Hmm, we didn't get a clear indication... Let's assume XSD, // since apparently no DTD declaration has been found up until // detection stopped (before finding the document's root tag). return VALIDATION_XSD; }
自動檢測的代碼邏輯分析:
先定義一個變量 isDtdValidated 是否是DTD約束 默認是 false,然后一行行的讀取xml資源文件,如果里面包含 DOCTYPE 字符串則是DTD約束,則上返回,停止自動檢測。
int detectedMode = detectValidationMode(resource); public int detectValidationMode(InputStream inputStream) throws IOException { // Peek into the file to look for DOCTYPE. BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream)); try { boolean isDtdValidated = false; String content; while ((content = reader.readLine()) != null) { content = consumeCommentTokens(content); // 如果讀取的行是空行或者注釋,則省略 if (this.inComment || !StringUtils.hasText(content)) { continue; } // 判斷是否含有 DOCTYPE 標簽 if (hasDoctype(content)) { isDtdValidated = true; break; } if (hasOpeningTag(content)) { // End of meaningful data... break; } } return (isDtdValidated ? VALIDATION_DTD : VALIDATION_XSD); }
2,2,4 getEntityResolver() 獲取資源文件約束的聲明
entityResolver接口有以下一個方法
public abstract InputSource resolveEntity (String publicId, String systemId) throws SAXException, IOException; }
他接受兩個參數:publicId和systemId,並返回InputSource 對象
如果是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" xmlns:cutesource="http://blog.csdn.net/cutesource/schema/people" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd http://blog.csdn.net/cutesource/schema/people http://blog.csdn.net/cutesource/schema/people.xsd"> <cutesource:people id="cutesource" name="袁志俊" age="27"/> </beans> 將會讀取到以下兩個參數: publicId:null systemId:http://www.springframework.org/schema/beans/spring-beans-2.5.xsd 如果是 DTD約束: <?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper> ............................... </mapper> 將會讀取到以下兩個參數: publicId:-//mybatis.org//DTD systemId:http://mybatis.org/dtd/mybatis-3-mapper.dtd 通過getEntityResolver方法獲取EntityResolver接口對象,DelegatingEntityResolver為EntityResolver的實現類。 protected EntityResolver getEntityResolver() { if (this.entityResolver == null) { // Determine default EntityResolver to use. ResourceLoader resourceLoader = getResourceLoader(); if (resourceLoader != null) { 如果資源加載器不為空,則使用ResourceEntityResolver對象進行文件約束的聲明 this.entityResolver = new ResourceEntityResolver(resourceLoader); } else { this.entityResolver = new DelegatingEntityResolver(getBeanClassLoader()); } } return this.entityResolver; }
2.2.5 new ResourceEntityResolver(resourceLoader)
public DelegatingEntityResolver(ClassLoader classLoader) { // 判斷是否是DTD約束 this.dtdResolver = new BeansDtdResolver(); // 如果是XSD約束 在獲取聲明的默認路經是: // META-INF/spring.schemas this.schemaResolver = new PluggableSchemaResolver(classLoader); }
2.3 解析並注冊 BeanDefinitions
將文件裝換為Document以后,接下來的提取以及注冊Bean就是我們的重頭戲。
/** * Register the bean definitions contained in the given DOM document. * Called by {@code loadBeanDefinitions}. * <p>Creates a new instance of the parser class and invokes * {@code registerBeanDefinitions} on it. * @param doc the DOM document * @param resource the resource descriptor (for context information) * @return the number of bean definitions found * @throws BeanDefinitionStoreException in case of parsing errors * @see #loadBeanDefinitions * @see #setDocumentReaderClass * @see BeanDefinitionDocumentReader#registerBeanDefinitions */ public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException { // 使用DefaultBeanDefinitionDocumentReader 實例化BeanDefinitionDocumentReader 對象 BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader(); // 記錄統計前BeanDefinition的加載個數 int countBefore = getRegistry().getBeanDefinitionCount(); // 加載以及注冊Bean
// 這里使用到了單一職責原則,將邏輯處理委托給單一的類進行處理,這個邏輯處理類就是 BeanDefinitionDocumentReader 對象
documentReader.registerBeanDefinitions(doc, createReaderContext(resource)); // 統計本次加載Beanfinition的個數 return getRegistry().getBeanDefinitionCount() - countBefore; }
繼續深入registerBeanDefinitions方法:
/** * This implementation parses bean definitions according to the "spring-beans" XSD * (or DTD, historically). * <p>Opens a DOM Document; then initializes the default settings * specified at the {@code <beans/>} level; then parses the contained bean definitions. */ @Override public void registerBeanDefinitions(Document doc, XmlReaderContext readerContext) { this.readerContext = readerContext; logger.debug("Loading bean definitions"); Element root = doc.getDocumentElement(); doRegisterBeanDefinitions(root); }
繼續深入doRegisterBeanDefinitions方法:doRegisterBeanDefinitions算開始真正解析XML文件了。
protected void doRegisterBeanDefinitions(Element root) { // Any nested <beans> elements will cause recursion in this method. In // order to propagate and preserve <beans> default-* attributes correctly, // keep track of the current (parent) delegate, which may be null. Create // the new (child) delegate with a reference to the parent for fallback purposes, // then ultimately reset this.delegate back to its original (parent) reference. // this behavior emulates a stack of delegates without actually necessitating one. BeanDefinitionParserDelegate parent = this.delegate; this.delegate = createDelegate(getReaderContext(), root, parent); // 處理profile 屬性 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; } } } // 空代碼留給子類去實現 模板設計模式 繼承 DefaultBeanDefinitionDocumentReader 的子類咋XML解析前做一些處理,可以實現此方法 preProcessXml(root); // 解析除了 profile以外的屬性 parseBeanDefinitions(root, this.delegate); // 空代碼留給子類去實現 模板設計模式 繼承 DefaultBeanDefinitionDocumentReader 的子類咋XML解析后做一些處理,可以實現此方法 postProcessXml(root); this.delegate = parent; }
2.4 判斷標簽的解析方式
protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) { // 對Bean的處理 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 { // 自定義標簽解析 delegate.parseCustomElement(ele); } } } } else { // 自定義標簽解析 delegate.parseCustomElement(root); } }