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); } }