本文共 10132 字,大约阅读时间需要 33 分钟。
在 XmlBeanDefinitionReader.doLoadDocument() 方法中做了两件事情,一是调用 getValidationModeForResource() 获取 XML 的验证模式,二是调用 DocumentLoader.loadDocument() 获取 Document 对象,上文我们获取了xml文件的验证方式 ( ),接下来我们看一下获取domcument
XmlBeanDefinitionReader.java/ * 使用配置好的DocumentLoader文档加载器加载指定的文档 * documentLoader 默认实现DefaultDocumentLoader */ protected Document doLoadDocument(InputSource inputSource, Resource resource) throws Exception { return this.documentLoader.loadDocument(inputSource, getEntityResolver(), this.errorHandler, getValidationModeForResource(resource), isNamespaceAware()); }
其中documentLoader 默认实现DefaultDocumentLoader,点击进查看他的代码
DefaultDocumentLoader.java/** * Load the {@link Document} at the supplied {@link InputSource} using the standard JAXP-configured * XML parser. * 使用标准JAXP配置XML解析器加载InputSource的Document对象 * namespaceAware 默认命名为false * validationMode 上面getValidationModeForResource返回过来的类型 */ @Override public Document loadDocument(InputSource inputSource, EntityResolver entityResolver, ErrorHandler errorHandler, int validationMode, boolean namespaceAware) throws Exception { //创建文档构建器工厂对象,并初始化一些属性 //如果验证模式为XSD,那么强制支持XML名称空间,并加上schema属性 DocumentBuilderFactory factory = createDocumentBuilderFactory(validationMode, namespaceAware); // 判断记录器Trace跟踪是否激活。Trace跟踪激活后会打印比较详细的信息。有助于提高系统性能 if (logger.isTraceEnabled()) { logger.trace("Using JAXP provider [" + factory.getClass().getName() + "]"); } // 创建一个JAXP文档构建器 DocumentBuilder builder = createDocumentBuilder(factory, entityResolver, errorHandler); // 按照XML文档解析给定inputSource的内容,然后返回一个新的DOM对象 return builder.parse(inputSource); }
由上可以看出Spring在这里执行步骤是
1,建立DocumentBuildFactory**(属于javax.xml.parsers)** 2,通过DocumentBuildFactory创建DocumentBuilder 3,解析inputSource来返回Document对象我们看一下createDocumentBuilderFactory
protected DocumentBuilderFactory createDocumentBuilderFactory(int validationMode, boolean namespaceAware) throws ParserConfigurationException { DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); factory.setNamespaceAware(namespaceAware);// 如果没有指令应用禁止验证, VALIDATION_NONE=0 if (validationMode != XmlValidationModeDetector.VALIDATION_NONE) { factory.setValidating(true); // VALIDATION_XSD=3 if (validationMode == XmlValidationModeDetector.VALIDATION_XSD) { // 使用xsd命名空间 factory.setNamespaceAware(true); try { // 设置schema属性 factory.setAttribute(SCHEMA_LANGUAGE_ATTRIBUTE, XSD_SCHEMA_LANGUAGE); } catch (IllegalArgumentException ex) { ParserConfigurationException pcex = new ParserConfigurationException( "Unable to validate using XSD: Your JAXP provider [" + factory + "] does not support XML Schema. Are you running on Java 1.4 with Apache Crimson? " + "Upgrade to Apache Xerces (or Java 1.5) for full XSD support."); pcex.initCause(ex); throw pcex; } } } return factory; }
这里其实使用SAX解析XML的,使用SAX我们就不得不说一下EntityResolver,EntityResolve是org.xml.sax下面的一个接口,官方定义如果SAX应用实现自定义处理外部实体,则必须实现这个接口,并且用setEntityResolver向SAX驱动器注册一个实例,EntityResolver 的作用就是项目本身就可以提供一个如何寻找DTD 的声明方法,在Spring中的参数entityResolver是这样获取的
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; }
由于验证文件的默认加载方式是通过url解析网络下载获取,这样会造成延迟,用户体验也不好,一般我们都会放到我们自己的工程里,那么怎么才可以可以将url转为从我们的工程中的对应文件了,我们这里来看spring是怎么实现的
spring中一般使用DelegatingEntityResolver类为EntityResolver的实现类 DelegatingEntityResolver实现了EntityResolver,DelegatingEntityResolver实现的resolveEntity方法是只有这样的@Override @Nullable public InputSource resolveEntity(@Nullable String publicId, @Nullable String systemId) throws SAXException, IOException { if (systemId != null) { // 如果是dtd DTD_SUFFIX=.dtd if (systemId.endsWith(DTD_SUFFIX)) { return this.dtdResolver.resolveEntity(publicId, systemId); } // XSD_SUFFIX = .xsd else if (systemId.endsWith(XSD_SUFFIX)) { return this.schemaResolver.resolveEntity(publicId, systemId); } } // Fall back to the parser's default behavior. return null; }
对应不同的验证模式,spring使用了不同的解析器解析,比如:
继续上面的代码,解析注册BeanDefinitions其实是在**documentReader.registerBeanDefinitions(doc,createReaderContext(resource))**方法里面的,也就是图中红线部分点击进去我们看一下他的源码
XmlBeanDefinitionReader.java public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException { // 使用DefaultBeanDefinitionDocumentReader实例化BeanDefinitionDocumentReader, // 目的是为了对xml格式的BeanDefinitions进行解析 BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader(); // getRegistry()方法拿的是bean工厂对象,beanDefinition注册在工厂中 //这个方法就是返回已经被注册在工厂中的beanDefinitions数量 int countBefore = getRegistry().getBeanDefinitionCount(); // 具体的解析过程由DefaultBeanDefinitionDocumentReader完成 documentReader.registerBeanDefinitions(doc, createReaderContext(resource)); // 返回上个方法真正注册在工厂中的beanDefinition数量 return getRegistry().getBeanDefinitionCount() - countBefore; }
注意这里documentReader.registerBeanDefinitions(doc,
createReaderContext(resource));这使用了委派模式,关于设计模式可以关注我的java设计模式系列,一直不断更新中,
我们先来看一下createBeanDefinitionDocumentReader()方法以及documentReaderClass
也就是说BeanDefinitionDocumentReader是一个接口,registerBeanDefinitions(doc, createReaderContext(resource)),实现的其实是他的子类,点击进去registerBeanDefinitions进去DefaultBeanDefinitionDocumentReader.java @Override public void registerBeanDefinitions(Document doc, XmlReaderContext readerContext) { //入参时创建的XmlReaderContext对象 this.readerContext = readerContext; //传进xml文档对象的根元素 doRegisterBeanDefinitions(doc.getDocumentElement()); }
由上面可以知道这段代码的重要目的之一就是提取root,以便于再次将xml的文档根元素作为参数传递,具体的注册其实是通过**doRegisterBeanDefinitions(doc.getDocumentElement());**实现的
还是老规矩点击进doRegisterBeanDefinitions这个方法
protected void doRegisterBeanDefinitions(Element root) { // 任何被嵌套的元素都会导致此方法的递归。为了正确的传播和保存 的默认属性、 // 保持当前(父)代理的跟踪,它可能为null // 为了能够回退,新的(子)代理具有父的引用,最终会重置this.delegate回到它的初始(父)引用。 // 这个行为模拟了一堆代理,但实际上并不需要一个代理 // BeanDefinitionParserDelegate中定义了一系列具体的spring xml文件定义的各种元素 // 这里实现的是DefaultBeanDefinitionDocumentReader BeanDefinitionParserDelegate parent = this.delegate; // 创建一个代理 初始化 this.delegate = createDelegate(getReaderContext(), root, parent); //如果默认名称空间是"http://www.springframework.org/schema/beans" if (this.delegate.isDefaultNamespace(root)) { String profileSpec = root.getAttribute(PROFILE_ATTRIBUTE); //存在xml文件设置"profile" 运行时环境 if (StringUtils.hasText(profileSpec)) { String[] specifiedProfiles = StringUtils.tokenizeToStringArray( profileSpec, BeanDefinitionParserDelegate.MULTI_VALUE_ATTRIBUTE_DELIMITERS); // We cannot use Profiles.of(...) since profile expressions are not supported // in XML config. See SPR-12458 for details. if (!getReaderContext().getEnvironment().acceptsProfiles(specifiedProfiles)) { if (logger.isDebugEnabled()) { logger.debug("Skipped XML bean definition file due to specified profiles [" + profileSpec + "] not matching: " + getReaderContext().getResource()); } return; } } } //xml预处理,子类没有重写里面就是空实现 preProcessXml(root); //2.2生成BeanDefinition,并注册在工厂中 parseBeanDefinitions(root, this.delegate); //xml后处理,子类没有重写里面就是空实现 postProcessXml(root); this.delegate = parent; }
我们按照代码执行顺序进入方法一个个的查看,先是createDelegate,这主要是对delegate(DefaultBeanDefinitionDocumentReader进行一个初始化)
在继续往下走,进入if判断,这里我们可以看到spring从xml根元素中获取了一个名字叫PROFILE_ATTRIBUTE,值是profile的属性值,我们不着急往下走,先看看这个profile是什么Spring中的Profile功能其实早在Spring 3.1的版本就已经出来,它可以理解为我们在Spring容器中所定义的Bean的逻辑组名称,只有当这些Profile被激活的时候,才会将Profile中所对应的Bean注册到Spring容器中,就好像我们的运行环境差不多把,通常我们的开发会部署多套环境,如测试环境,开发环境,生产环境,每一套环境对应不同的配置文件,Profile使我们更加灵活的在不同的配置环境之间进行切换,最常用的应该就是切换不同的数据库把
具体使用web.xml 加入以下代码
spring.profiles.default development
回到主题,继续查看doRegisterBeanDefinitions的代码,我们看到preProcessXml(root)和postProcessXml(root)这两个方法,这两个方法都是同一个类(DefaultBeanDefinitionDocumentReader.jva)里面的
不难看出这两个都是空方法,具体的实现是留给子类去实现的,这里使用到了设计模式里的模板方法,我们直接去看parseBeanDefinitions,这个方法里面主要使用了Spring的Bean规则从文档根元素对文档进行解析DefaultBeanDefinitionDocumentReader protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) { //spring定义了元素默认名称空间是"http://www.springframework.org/schema/beans" //进入条件 if (delegate.isDefaultNamespace(root)) { //获取根元素下的子Node,注意,Node不一定是子标签,可能是回车,可能是注释 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; //3.如果该标签属于beans的名称空间,则进入这个方法 if (delegate.isDefaultNamespace(ele)) { parseDefaultElement(ele, delegate); } else { //4.如果该标签属于其他的名称空间比如:context,aop等 //xmlns:aop="http://www.springframework.org/schema/aop" //xmlns:context="http://www.springframework.org/schema/context" // 即如果没有使用默认命名空间,则使用用户自定义的配置规则 delegate.parseCustomElement(ele); } } } } else { // 文档的根节点没有使用Spring的默认命名空间 // 使用自定义的解析规则解析文档根目录 delegate.parseCustomElement(root); } }
对于上述代码,我们可以知道spring解析俩种不同类型的bean,即自定义bean(使用delegate.parseCustomElement)和默认的bean(使用parseDefaultElement),主要通过判断node.getNamespaceURI()获取命名空间,并与spring给定的命名空间进行对比
下 面 一 章 我 们 分 别 看 看 这 俩 种 标 签 对 于 s p r i n g 来 说 是 怎 么 解 析 并 实 现 的 {下面一章我们分别看看这俩种标签对于spring来说是怎么解析并实现的} 下面一章我们分别看看这俩种标签对于spring来说是怎么解析并实现的
转载地址:http://pnkgn.baihongyu.com/