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 的配置更容易閱讀和理解。


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章