Spring 加载xml

原文:http://jiangshuiy.iteye.com/blog/1670708


在Spring中,配置文件主要格式是XML,spring 本身提供了很多 xmlnamespace 的配置,如 jms、aop 等。并且,Spring提供了很多扩展点来供用户来实现自己的配置,这究竟是怎么实现的呢?让我们来一探究竟。

 

让我们从XmlBeanFactory开始吧。在这个类中:

 

 

Java代码   收藏代码
  1. public class XmlBeanFactory extends DefaultListableBeanFactory {  
  2.   
  3.     private final XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(this);  
  4.   
  5.   
  6.     public XmlBeanFactory(Resource resource) throws BeansException {  
  7.         this(resource, null);  
  8.     }  
  9.   
  10.     public XmlBeanFactory(Resource resource, BeanFactory parentBeanFactory) throws BeansException {  
  11.         super(parentBeanFactory);  
  12.         this.reader.loadBeanDefinitions(resource);  
  13.     }  
  14.   
  15. }  

 

 

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 中。

 

 

 可以看到,XmlBeanFactory是使用XmlBeanDefinitionReader来读取XML文件的。而这个实际读取转发转发到XmlBeanDefinitionReader的loadBeanDefinitions方法:

 

 

Java代码   收藏代码
  1. public class XmlBeanDefinitionReader extends AbstractBeanDefinitionReader {  
  2. ……  
  3.       
  4. private DocumentLoader documentLoader = new DefaultDocumentLoader();  
  5. public int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException {  
  6.         return loadBeanDefinitions(new EncodedResource(resource));  
  7.     }  
  8.   
  9. public int loadBeanDefinitions(EncodedResource encodedResource) throws BeanDefinitionStoreException {  
  10. ……  
  11. try {  
  12.             InputStream inputStream = encodedResource.getResource().getInputStream();  
  13.             try {  
  14.                 InputSource inputSource = new InputSource(inputStream);  
  15.                 if (encodedResource.getEncoding() != null) {  
  16.                     inputSource.setEncoding(encodedResource.getEncoding());  
  17.                 }  
  18.                 return doLoadBeanDefinitions(inputSource, encodedResource.getResource());  
  19.             }  
  20.             finally {  
  21.                 inputStream.close();  
  22.             }  
  23.         }  
  24. ……  
  25. }  
  26.   
  27. protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource)  
  28.             throws BeanDefinitionStoreException {  
  29.         try {  
  30.             int validationMode = getValidationModeForResource(resource);  
  31.             Document doc = this.documentLoader.loadDocument(  
  32.                     inputSource, getEntityResolver(), this.errorHandler, validationMode, isNamespaceAware());  
  33.             return registerBeanDefinitions(doc, resource);  
  34.         }  
  35. ……  
  36. }  
  37.   
  38. ……  
  39. }  

   loadBeanDefinitions方法首先要通过 Resource 接口读取 xml 配置文件,并把它读到一个 Document 对象中,用于解析,这个动作是由接口 DocumentLoader 的实现来完成的。spring 有一个默认实现DefaultDocumentLoader。

 

 

 

    可以发现,上面定义了一个documentLoader,很明显,矛头转向DefaultDocumentLoader的loadDocument方法,请看:

 

Java代码   收藏代码
  1. public class DefaultDocumentLoader implements DocumentLoader {  
  2. private static final String SCHEMA_LANGUAGE_ATTRIBUTE = "http://java.sun.com/xml/jaxp/properties/schemaLanguage";  
  3.   
  4.       
  5.     private static final String XSD_SCHEMA_LANGUAGE = "http://www.w3.org/2001/XMLSchema";  
  6. public Document loadDocument(InputSource inputSource, EntityResolver entityResolver,  
  7.             ErrorHandler errorHandler, int validationMode, boolean namespaceAware) throws Exception {  
  8.   
  9.         DocumentBuilderFactory factory = createDocumentBuilderFactory(validationMode, namespaceAware);  
  10.         if (logger.isDebugEnabled()) {  
  11.             logger.debug("Using JAXP provider [" + factory.getClass().getName() + "]");  
  12.         }  
  13.         DocumentBuilder builder = createDocumentBuilder(factory, entityResolver, errorHandler);  
  14.         return builder.parse(inputSource);  
  15.     }  
  16.   
  17. protected DocumentBuilderFactory createDocumentBuilderFactory(int validationMode, boolean namespaceAware)  
  18.             throws ParserConfigurationException {  
  19.   
  20.         DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();  
  21.         factory.setNamespaceAware(namespaceAware);  
  22.   
  23.         if (validationMode != XmlValidationModeDetector.VALIDATION_NONE) {  
  24.             factory.setValidating(true);  
  25.   
  26.             if (validationMode == XmlValidationModeDetector.VALIDATION_XSD) {  
  27.                 // Enforce namespace aware for XSD...  
  28.                 factory.setNamespaceAware(true);  
  29.                 try {  
  30.                     factory.setAttribute(SCHEMA_LANGUAGE_ATTRIBUTE, XSD_SCHEMA_LANGUAGE);  
  31.                 }  
  32.                 catch (IllegalArgumentException ex) {  
  33.                     ParserConfigurationException pcex = new ParserConfigurationException(  
  34.                             "Unable to validate using XSD: Your JAXP provider [" + factory +  
  35.                             "] does not support XML Schema. Are you running on Java 1.4 with Apache Crimson? " +  
  36.                             "Upgrade to Apache Xerces (or Java 1.5) for full XSD support.");  
  37.                     pcex.initCause(ex);  
  38.                     throw pcex;  
  39.                 }  
  40.             }  
  41.         }  
  42.   
  43.         return factory;  
  44.     }  
  45.   
  46. protected DocumentBuilder createDocumentBuilder(  
  47.             DocumentBuilderFactory factory, EntityResolver entityResolver, ErrorHandler errorHandler)  
  48.             throws ParserConfigurationException {  
  49.   
  50.         DocumentBuilder docBuilder = factory.newDocumentBuilder();  
  51.         if (entityResolver != null) {  
  52.             docBuilder.setEntityResolver(entityResolver);  
  53.         }  
  54.         if (errorHandler != null) {  
  55.             docBuilder.setErrorHandler(errorHandler);  
  56.         }  
  57.         return docBuilder;  
  58.     }  
  59.   
  60. }  

 

对于如何读取一个 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。

 

Java代码   收藏代码
  1. public class XmlBeanDefinitionReader extends AbstractBeanDefinitionReader {  
  2. protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource)  
  3.             throws BeanDefinitionStoreException {  
  4.         try {  
  5. ……  
  6. return registerBeanDefinitions(doc, resource);  
  7. ……  
  8. }  
  9.   
  10.     public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException {  
  11.         // Support old XmlBeanDefinitionParser SPI for backwards-compatibility.  
  12.         if (this.parserClass != null) {  
  13.             XmlBeanDefinitionParser parser =  
  14.                     (XmlBeanDefinitionParser) BeanUtils.instantiateClass(this.parserClass);  
  15.             return parser.registerBeanDefinitions(this, doc, resource);  
  16.         }  
  17.         // Read document based on new BeanDefinitionDocumentReader SPI.  
  18.         BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader();  
  19.         int countBefore = getRegistry().getBeanDefinitionCount();  
  20.         documentReader.registerBeanDefinitions(doc, createReaderContext(resource));  
  21.         return getRegistry().getBeanDefinitionCount() - countBefore;  
  22.     }  
  23. ……}  
 

    DefaultBeanDefinitionDocumentReader 主要完成两件事情,解析<bean> 元素,为扩展spring 的元素寻找合适的解析器,并把相应的元素交给解析器解析。第一个任务,解析<bean> 元素,这个spring 的核心功能及IoC 或者是 DI,这由spring 自己来处理,这个工作有一个专门的委托类来处理BeanDefinitionParserDelegate,由它来解析 <bean> 元素,并把解析的结果注册到BeanDefinitionRegistry(XmlBeanFactory 实现了此接口) 中。

 

 

 

Java代码   收藏代码
  1. public class DefaultBeanDefinitionDocumentReader implements BeanDefinitionDocumentReader {  
  2.   
  3.     public void registerBeanDefinitions(Document doc, XmlReaderContext readerContext) {  
  4.         this.readerContext = readerContext;  
  5.   
  6.         logger.debug("Loading bean definitions");  
  7.         Element root = doc.getDocumentElement();  
  8.   
  9.         BeanDefinitionParserDelegate delegate = createHelper(readerContext, root);  
  10.   
  11.         preProcessXml(root);  
  12.         parseBeanDefinitions(root, delegate);  
  13.         postProcessXml(root);  
  14.     }  
  15.     protected BeanDefinitionParserDelegate createHelper(XmlReaderContext readerContext, Element root) {  
  16.         BeanDefinitionParserDelegate delegate = new BeanDefinitionParserDelegate(readerContext);  
  17.         delegate.initDefaults(root);  
  18.         return delegate;  
  19.     }  
  20.   
  21.     protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) {  
  22.         if (delegate.isDefaultNamespace(root.getNamespaceURI())) {  
  23.             NodeList nl = root.getChildNodes();  
  24.             for (int i = 0; i < nl.getLength(); i++) {  
  25.                 Node node = nl.item(i);  
  26.                 if (node instanceof Element) {  
  27.                     Element ele = (Element) node;  
  28.                     String namespaceUri = ele.getNamespaceURI();  
  29.                     if (delegate.isDefaultNamespace(namespaceUri)) {  
  30.                         parseDefaultElement(ele, delegate);  
  31.                     }  
  32.                     else {  
  33.                         delegate.parseCustomElement(ele);  
  34.                     }  
  35.                 }  
  36.             }  
  37.         }  
  38.         else {  
  39.             delegate.parseCustomElement(root);  
  40.         }  
  41.     }  
  42. ……  
  43. }  
 

 

那么 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 配置。

 

Java代码   收藏代码
  1. public static final String BEANS_NAMESPACE_URI = "http://www.springframework.org/schema/beans";  
  2.   
  3.     public boolean isDefaultNamespace(String namespaceUri) {  
  4.         return (!StringUtils.hasLength(namespaceUri) || BEANS_NAMESPACE_URI.equals(namespaceUri));  
  5.     }  
  6.   
  7.     public BeanDefinition parseCustomElement(Element ele) {  
  8.         return parseCustomElement(ele, null);  
  9.     }  
  10.   
  11.     private void parseDefaultElement(Element ele, BeanDefinitionParserDelegate delegate) {  
  12.         if (DomUtils.nodeNameEquals(ele, IMPORT_ELEMENT)) {  
  13.             importBeanDefinitionResource(ele);  
  14.         }  
  15.         else if (DomUtils.nodeNameEquals(ele, ALIAS_ELEMENT)) {  
  16.             processAliasRegistration(ele);  
  17.         }  
  18.         else if (DomUtils.nodeNameEquals(ele, BEAN_ELEMENT)) {  
  19.             processBeanDefinition(ele, delegate);  
  20.         }  
  21.     }  
  22.   
  23.     public BeanDefinition parseCustomElement(Element ele, BeanDefinition containingBd) {  
  24.         String namespaceUri = ele.getNamespaceURI();  
  25.         NamespaceHandler handler = this.readerContext.getNamespaceHandlerResolver().resolve(namespaceUri);  
  26.         if (handler == null) {  
  27.             error("Unable to locate Spring NamespaceHandler for XML schema namespace [" + namespaceUri + "]", ele);  
  28.             return null;  
  29.         }  
  30.         return handler.parse(ele, new ParserContext(this.readerContext, this, containingBd));  
  31.     }  

 

 

Spring读取xml

 

Java代码   收藏代码
  1. public static final String BEAN_ELEMENT = BeanDefinitionParserDelegate.BEAN_ELEMENT;  
  2.   
  3. public static final String ALIAS_ELEMENT = "alias";  
  4.   
  5. public static final String IMPORT_ELEMENT = "import";  

 

在 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 的实现。

 

 

Java代码   收藏代码
  1. public interface NamespaceHandlerResolver {  
  2.   
  3.     /** 
  4.      * Resolve the namespace URI and return the located {@link NamespaceHandler} 
  5.      * implementation. 
  6.      * @param namespaceUri the relevant namespace URI 
  7.      * @return the located {@link NamespaceHandler} (may be <code>null</code>) 
  8.      */  
  9.     NamespaceHandler resolve(String namespaceUri);  
  10.   
  11. }  

 

 

 

如上面所提到的,扩展 spring 需要完成以下几个工作,定义一个 xml schema,并编写相应的 spring.schemas 文件,实现 NamespaceHandler 接口,根据需要还可能需要实现BeanDefinitionParser 和BeanDefinitionDecorator 等接口,更详细的信息可以参考 spring 的 reference 或者其他 spring 相关的文档

 

开源社区里不知道是哪位神人开发了 xbean 这样一个框架。这个框架具体做什么呢,它主要完成三件事情,第一根据源代码中的一些特殊的 doclet 生成一个 xml schema,看看 activemq 的源代码,大家可能会发现,很多类的 javadoc 中多了这样一个 tag @org.apache.xbean.XBean 以及其它的一些 tag,xbean 会根据这些特殊的 tag 来生成一个 xml schema;xbean 完成的第二件事情就是它会生成扩展 spring 所需的一些配置;第三它重新实现了一些 spring 中的可替换组件,如它扩展了XmlBeanDefinitionReader 实现了自己的 BeanDefinitionReaderXBeanXmlDefinitionReader,实现了自己的 ApplicationContext ResourceXmlApplicationContext,如果使用了 xbean 就必须使用 xbean 实现的 ApplicationContext。xbean 提供的 BeanDefinitionReader 实现只是把一些定制的元素转换成了 spring 中的 bean 元素,这样使 spring 的配置更容易阅读和理解。


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值