Spring如何加载XSD文件

来源:互联网 发布:视频动态壁纸软件 编辑:程序博客网 时间:2024/05/22 06:14

http://blog.csdn.net/bluishglc/article/details/7596118

本文原文连接: http://blog.csdn.net/bluishglc/article/details/7596118 ,转载请注明出处!

有时候你会发现过去一直启动正常的系统,某天启动时会报出形如下面的错误:

[plain] view plaincopyprint?
  1. org.xml.sax.SAXParseException: schema_reference.4: Failed to read schema document 'http://www.springframework.org/schema/beans/spring-beans-2.0.xsd', because 1) could not find the document; 2) the document could not be read; 3) the root element of the document is not <xsd:schema>.  


很显然,spring xml配置文件中指定的xsd文件读取不到了,原因多是因为断网或spring的官网暂时无法连接导致的。 你可以通过在浏览器输入xsd文件的URL,如:http://www.springframework.org/schema/beans/spring-beans-2.0.xsd 进行确认。


关于这个问题,网上有两种常见的解决方法,第一种简单有效,但是工作量大,即:把所有spring配置文件中url形式的xsd路径转换成指向本地xsd文件的classpath形式的路径,例如:classpath:org/springframework/beans/factory/xml/spring-beans-2.5.xsd ,再有一种方法就是在本机搭建web服务器,按URL创建相应文件夹,放入对应xsd文件,在本机hosts文件中加入"127.0.0.1 www.springframework.org".实际上,这两种方法都属于“歪打正着”式的方法,直正弄明白这一问题还需要从spring的XSD文件加载机制谈起。


首先:你必须知道一点:spring在加载xsd文件时总是先试图在本地查找xsd文件(spring的jar包中已经包含了所有版本的xsd文件),如果没有找到,才会转向去URL指定的路径下载。这是非常合理的做法,并不像看上去的那样,每次都是从站点下载的。事实上,假如你的所有配置是正确定的,你的工程完全可以在断网的情况下启动而不会报上面的错误。Spring加载xsd文件的类是PluggableSchemaResolver,你可以查看一下它的源码来验证上述说法。另外,你可以在log4j.xml文件中加入:

[html] view plaincopyprint?
  1. <logger name="org.springframework.beans.factory.xml">  
  2.     <level value="all" />  
  3. </logger>  


通过日志了解spring是何加载xsd文件的。


接下来,问题就是为什么spring在本地没有找到需要的文件,不得不转向网站下载。关于这个问题,其实也非常简单。在很多spring的jar包里,在META-INF目录下都有一个spring.schemas,这是一个property文件,其内容类似于下面:

[plain] view plaincopyprint?
  1. http\://www.springframework.org/schema/beans/spring-beans-2.0.xsd=org/springframework/beans/factory/xml/spring-beans-2.0.xsd  
  2. http\://www.springframework.org/schema/beans/spring-beans-2.5.xsd=org/springframework/beans/factory/xml/spring-beans-2.5.xsd  
  3. http\://www.springframework.org/schema/beans/spring-beans-3.0.xsd=org/springframework/beans/factory/xml/spring-beans-3.0.xsd  
  4. ....  


实际上,这个文件就是spring关于xsd文件在本地存放路径的映射,spring就是通过这个文件在本地(也就是spring的jar里)查找xsd文件的。那么,查找不到的原因排除URL输入有误之外,可能就是声明的xsd文件版本在本地不存在。一般来说,新版本的spring jar包会将过去所有版本(应该是自2.0以后)的xsd打包,并在spring.schemas文件中加入了对应项,出现问题的情况往往是声明使用了一个高版本的xsd文件,如3.0,但依赖的spring的jar包却是2.5之前的版本,由于2.5版本自然不可能包含3.0的xsd文件,此时就会导致spring去站点下载目标xsd文件,如遇断网或是目标站点不可用,上述问题就发生了。


但是,在实现开发中,出现上述错误的几率并不高,最常见的导致这一问题的原因其实与使用了一个名为“assembly”的maven打包插件有关。很多项目需要将工程连同其所依赖的所有jar包打包成一个jar包,maven的assembly插件就是用来完成这个任务的。但是由于工程往往依赖很多的jar包,而被依赖的jar又会依赖其他的jar包,这样,当工程中依赖到不同的版本的spring时,在使用assembly进行打包时,只能将某一个版本jar包下的spring.schemas文件放入最终打出的jar包里,这就有可能遗漏了一些版本的xsd的本地映射,进而出现了文章开始提到的错误。如果你的项目是打成单一jar的,你可以通过检查最终生成的jar里的spring.schemas文件来确认是不是这种情况。而关于这种情况,解决的方法一般是推荐使用另外一种打包插件shade,它确实是一款比assembly更加优秀的工具,在对spring.schemas文件处理上,shade能够将所有jar里的spring.schemas文件进行合并,在最终生成的单一jar包里,spring.schemas包含了所有出现过的版本的集合!


以上就是spring加载XSD文件的机制和出现问题的原因分析。实际上,我们应该让我们工程在启动时总是加载本地的xsd文件,而不是每次去站点下载,做到这一点就需要你结合上述提及的种种情况对你的工程进行一番检查。

 

 

==========================

http://blog.csdn.net/javabenface/article/details/7441923

以前一直没注意spring对xml的解析过程,
特别是xml文件头上的一堆xmlns:


1<?xmlversion="1.0"encoding="UTF-8"?>
2<?XML:NAMESPACEPREFIX = [default] http://www.springframework.org/schema/beans NS= "http://www.springframework.org/schema/beans"/><beans xmlns="http://www.springframework.org/schema/beans"xsi:schemaLocation="http://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans-3.0.xsd&#10;http://www.springframework.org/schema/aop&#10;http://www.springframework.org/schema/aop/spring-aop-3.0.xsd&#10;http://www.springframework.org/schema/tx&#10;http://www.springframework.org/schema/tx/spring-tx-3.0.xsd&#10;http://www.springframework.org/schema/context&#10;                           http://www.springframework.org/schema/context/spring-context-3.0.xsd"xmlns:context="http://www.springframework.org/schema/context"xmlns:tx="http://www.springframework.org/schema/tx"xmlns:aop="http://www.springframework.org/schema/aop"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
3</beans>

这些命名空间中是怎么解析的,
大概可以分为下面这个步骤:
1. 解析XML, 找到所有的 命名空间 如: http://www.springframework.org/schema/context
2. 在Classpath中查找所有的 spring.handlers 并解析其中配置的 命名空间 与 对应的处理类, 如:

http\://www.springframework.org/schema/aop=org.springframework.aop.config.AopNamespaceHandler

3. 根据查找到的处理Handler去解析配置文件中相应的结点.

而命名空间对应的xsd文件, 则是在 spring.schemas 中指定的, 如:

http\://www.springframework.org/schema/aop/spring-aop-2.0.xsd=org/springframework/aop/config/spring-aop-2.0.xsdhttp\://www.springframework.org/schema/aop/spring-aop-2.5.xsd=org/springframework/aop/config/spring-aop-2.5.xsdhttp\://www.springframework.org/schema/aop/spring-aop-3.0.xsd=org/springframework/aop/config/spring-aop-3.0.xsdhttp\://www.springframework.org/schema/aop/spring-aop.xsd=org/springframework/aop/config/spring-aop-3.0.xsd

 

 

 

====================

http://kiminotes.iteye.com/blog/547103

本文通过对 spring 源代码的阅读,解析 spring 如何校验、解析 xml 格式的 bean 配置文件,还会介绍如何扩展 spring,这也是我阅读这部分代码的原因所在。


在使用 spring 的时候,最常用的配置 bean 的方式就是 xml 文件,spring 本身提供了很多 xml namespace 的配置,如 jms、aop 等。我在使用这些 spring 内建的 namespace(其实也就用过 jms 这个namesapce) 的时候并没有觉得有什么疑问或奇怪的,但当我使用 activemq 以及了解 mule 的时候发现,它们的配置都是基于 spring 构建的,只是提供了它们自己的 namespace 而已。我很迷惑,它们这是怎么实现的呢,难道这些框架本身也替换了 spring 实现的解析 spring bean xml 配置文件的实现,想想也没这必要,它们完全可以定义自己的 xml 配置文件格式,然后自己实现解析,为什么要嵌入到 spring 中呢?感觉很奇怪。带着这个疑问,我就在查看了 spring 提供的 reference,在 spring reference 的 appendix B - Extensible XML authoring 中发现,原来可以通过 xml schema 来扩展 spring,并通过一些配置可以使 spring 识别并处理这些配置。这是疑问又出来了,spring 是怎么实现的呢?于是决定读读源代码,看看是如何实现的。为了节省时间,我从 XmlBeanFactory 开始,如果从 ApplicationContext 的实现开始的话,会遇到很多干扰,因为 ApplicationContext 比 BeanFactory 提供了更多的更强大的功能。

spring 使用 XmlBeanDefinitionReader 来读取并解析 xml 文件,XmlBeanDefinitionReader 是 BeanDefinitionReader 接口的实现。BeanDefinitionReader 定义了 spring 读取 bean 定义的一个接口,这个接口中有一些 loadBeanDefinitions 方法,从它们的方法签名可知,spring 把读取 bean 配置的来源抽象为 Resource 接口。BeanDefinitionReader 接口有两个具体的实现,其中之一就是从 xml 文件中读取配置的 XmlBeanDefinitionReader,另一个则是从 java properties 文件中读取配置的 PropertiesBeanDefinitionReader。开发人员也可以提供自己的 BeanDefinitionReader 实现,根据自己的需要来读取 spring bean 定义的配置。在 XmlBeanFactory 中创建了 XmlBeanDefinitionReader 的实例,并在 XmlBeanFactory 的构造方法中调用了 XmlBeanDefinitionReader 的 loadBeanDefinitions 方法,由 loadBeanDefinitions 方法负责加载 bean 配置并把 bean 配置注册到 XmlBeanFactory 中。

loadBeanDefinitions 方法首先要通过 Resource 接口读取 xml 配置文件,并把它读到一个 Document 对象中,用于解析,这个动作是由接口 DocumentLoader 的实现来完成的。spring 有一个默认实现 DefaultDocumentLoader。对于如何读取一个 xml 文件为 Document 对象,我想大部分都很熟悉:创建 DocumentBuilderFactory,由 DocumentBuilderFacoty 创建 DocumentBuidler,调用 DocumentBuilder 的 parse 方法把文件或流解析为 Document。的确 spring 也是这样做的,但有一点不要忘记,spring 需要使用 xml schema 来验证 xml,spring 使用的 jaxp 1.2 中提供的 xml schema 验证方式,并没有使用 jaxp 1.3 中引入的 Schema 对象来验证(jboss cache 也是使用的这种方式)。DefaultDocumentLoader 在创建了 DocumentBuilderFactory 对象后会判断当前是否使用 xml schema 验证,如果是则会在 DocumentBuiderFactory 上设置一个属性,这个属性名为 http://java.sun.com/xml/jaxp/properties/schemaLanguage,如果把这个属性设置为 http://www.w3.org/2001/XMLSchema,jaxp 则会使用 xml schema 来验证 xml 文档,使用这种验证方式需要提供一个 EntityResolver 的实现,EntityResolver 的使用 DocumentBuilder 的 setEntityResolver 方法设置。spring 提供了 EntityResolver 的实现,这个实现也是扩展 spring 的关键所在,这个后面会提到,暂且略过。

在完成了 Resource 到 Document 的转换后,下面就是从 Document 中解析出各个 bean 的配置了,为此 spring 又抽象了一个接口 BeanDefinitionDocumentReader,从它的名称中可以一目了然这个接口负责从 Document 中读取 bean 定义,这个接口中只定义了一个方法 registerBeanDefinitions。spring 也提供了一个默认实现 DefaultBeanDefinitionDocumentReader。DefaultBeanDefinitionDocumentReader 主要完成两件事情,解析 <bean> 元素,为扩展 spring 的元素寻找合适的解析器,并把相应的元素交给解析器解析。第一个任务,解析 <bean> 元素,这个 spring 的核心功能及 IoC 或者是 DI,这由 spring 自己来处理,这个工作有一个专门的委托类来处理 BeanDefinitionParserDelegate,由它来解析 <bean> 元素,并把解析的结果注册到 BeanDefinitionRegistry(XmlBeanFactory 实现了此接口) 中。那么 spring 如何来区别 bean 元素以及其它扩展元素的,大家可能很自然地就能想到使用元素名啊,的确使用元素名可以处理,但这就会出现这样的情况,程序员 A 扩展 spring 定一个元素名为 c 的元素,同样程序员 B 扩展 spring 也定义了名为 c 的元素,此时就无法区分了。其实 spring 是通过 xml namespace 来区分的,同样查找扩展元素的解析器也是通过 xml namespace 来处理的。spring 从根元素开始,在解析每个元素的时候,都会先查询元素的 namespace uri,如果元素的 namespace uri 为 http://www.springframework.org/schema/beans,则由 spring IoC 来解析处理,这些元素包括 beans、bean、import、alias,如果 namespace uri 不是 http://www.springframework.org/schema/beans,则会使用 NamespaceHandlerResolver 来解析出一个 NamespaceHandler,使用 NamespaceHandler 来解析处理这个元素。NamespaceHandlerResovler 和 NamespaceHandler 就是扩展 spring 的秘密所在。NamespaceHandlerResolver 是一个接口,spring 使用与 EntityResolver 相同的策略来实现,这个后面会提到。当这一步完成了 spring 也就完成了读取解析 xml 配置。

上面仅仅是根据代码的执行路径简单地描述了 spring 解析 xml 配置的一个大体过程,至于更细节性的东西,感兴趣的朋友不妨根据我上面的思路,详细的读一读这一部分的源代码。(我阅读源代码都是借助于 ide 来完成的,这能提搞阅读的速度,如果碰到一些是在不理解的代码还可以写一些非常简单的 demo 单步执行,看看代码到底是如何执行的)

下面介绍一下如何扩展 spring。不知大家有没有注意到,在 spring 的源代码目录中有两个很特殊的文件:spring.schemas 和 spring.handlers,这两个文件以及 spring 中对 EntityResolver 和 NamespaceHandlerResolver 的实现 PluggableSchemaResolver 和 DefaultNamespaceHandlerResolver 是扩展 spring 的关键所在。其实 spring.schemas 和 spring.handlers 文件是标准的 java properties 文件。这两个文件都被大包到 spring jar 包中的 META-INF 目录中,PluggableSchemaResolver 通过读取 spring.schemas 文件,根据 xml 文件中实体的 system id 来解析这些实体,大家可以看一下 spring.schemas 文件中的 key 就可以知道 system id 是什么了(其实我也不知道 system id 和 public id 是啥,知道的朋友不妨在文后的回复中给我留言,谢谢);而 DefaultNamespaceHandlerResolver 则是根据元素的 namespace uri 在 spring.handlers 文件中查找具体的 NamespaceHandler 的实现。

如上面所提到的,扩展 spring 需要完成以下几个工作,定义一个 xml schema,并编写相应的 spring.schemas 文件,实现 NamespaceHandler 接口,根据需要还可能需要实现 BeanDefinitionParser 和 BeanDefinitionDecorator 等接口,更详细的信息可以参考 spring 的 reference 或者其他 spring 相关的文档,由于我想了解的事情已经了解了,就没有继续研究下去。



提到 扩展 spring 这里简单地说一下 activemq。说 activemq 通过扩展 spring 来实现本身的配置其实并不完全正确,事实上 activemq 并没有扩展 spring,完全使用 spring 的 IoC 配置 activemq 也是可以实现的。activemq 本身的架构是基于 pojo 的,所以使用 spring 的 IoC 完成可以完成 activemq 的配置任务。但 spring 的 bean 配置看上去有点“专业化”,尤其是第一次看到 spring 配置的时候,会困惑与 property 元素(其实当了解了 spring 之后也是很好理解的)。开源社区里不知道是哪位神人开发了 xbean 这样一个框架。这个框架具体做什么呢,它主要完成三件事情,第一根据源代码中的一些特殊的 doclet 生成一个 xml schema,看看 activemq 的源代码,大家可能会发现,很多类的 javadoc 中多了这样一个 tag @org.apache.xbean.XBean 以及其它的一些 tag,xbean 会根据这些特殊的 tag 来生成一个 xml schema;xbean 完成的第二件事情就是它会生成扩展 spring 所需的一些配置;第三它重新实现了一些 spring 中的可替换组件,如它扩展了 XmlBeanDefinitionReader 实现了自己的 BeanDefinitionReader XBeanXmlDefinitionReader,实现了自己的 ApplicationContext ResourceXmlApplicationContext,如果使用了 xbean 就必须使用 xbean 实现的 ApplicationContext。xbean 提供的 BeanDefinitionReader 实现只是把一些定制的元素转换成了 spring 中的 bean 元素,这样使 spring 的配置更容易阅读和理解。


本文简单地介绍了 spring 解析 xml 配置以及扩展 spring 方面的一些内容,顺带还介绍了一下 xbean,感兴趣的朋友可以详细地读一下这部分的源代码。文中的解析并没有上升到什么架构或设计的高度,说实话自己还没有达到这样的高度,仅仅是对源代码执行路径的一个简单描述,如果能使各位看官对 spring 的内部实现有一点的了解,或者使您对 spring 的内部实现感兴趣并希望进一步了解,能达到这样的效果我已经很知足了,别无他求。如果您有关于这方面的自己的想法不妨与大家一同分享。如果文中有言之有误的地方,还望各位指正。