原文:http://jiangshuiy.iteye.com/blog/1670708
在Spring中,配置文件主要格式是XML,spring 本身提供了很多 xmlnamespace 的配置,如 jms、aop 等。並且,Spring提供了很多擴展點來供用戶來實現自己的配置,這究竟是怎麼實現的呢?讓我們來一探究竟。
讓我們從XmlBeanFactory開始吧。在這個類中:
- public class XmlBeanFactory extends DefaultListableBeanFactory {
- private final XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(this);
- public XmlBeanFactory(Resource resource) throws BeansException {
- this(resource, null);
- }
- public XmlBeanFactory(Resource resource, BeanFactory parentBeanFactory) throws BeansException {
- super(parentBeanFactory);
- this.reader.loadBeanDefinitions(resource);
- }
- }
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方法:
- public class XmlBeanDefinitionReader extends AbstractBeanDefinitionReader {
- ……
- private DocumentLoader documentLoader = new DefaultDocumentLoader();
- public int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException {
- return loadBeanDefinitions(new EncodedResource(resource));
- }
- public int loadBeanDefinitions(EncodedResource encodedResource) throws BeanDefinitionStoreException {
- ……
- try {
- InputStream inputStream = encodedResource.getResource().getInputStream();
- try {
- InputSource inputSource = new InputSource(inputStream);
- if (encodedResource.getEncoding() != null) {
- inputSource.setEncoding(encodedResource.getEncoding());
- }
- return doLoadBeanDefinitions(inputSource, encodedResource.getResource());
- }
- finally {
- inputStream.close();
- }
- }
- ……
- }
- protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource)
- throws BeanDefinitionStoreException {
- try {
- int validationMode = getValidationModeForResource(resource);
- Document doc = this.documentLoader.loadDocument(
- inputSource, getEntityResolver(), this.errorHandler, validationMode, isNamespaceAware());
- return registerBeanDefinitions(doc, resource);
- }
- ……
- }
- ……
- }
loadBeanDefinitions方法首先要通過 Resource 接口讀取 xml 配置文件,並把它讀到一個 Document 對象中,用於解析,這個動作是由接口 DocumentLoader 的實現來完成的。spring 有一個默認實現DefaultDocumentLoader。
可以發現,上面定義了一個documentLoader,很明顯,矛頭轉向DefaultDocumentLoader的loadDocument方法,請看:
- public class DefaultDocumentLoader implements DocumentLoader {
- private static final String SCHEMA_LANGUAGE_ATTRIBUTE = "http://java.sun.com/xml/jaxp/properties/schemaLanguage";
- private static final String XSD_SCHEMA_LANGUAGE = "http://www.w3.org/2001/XMLSchema";
- public Document loadDocument(InputSource inputSource, EntityResolver entityResolver,
- ErrorHandler errorHandler, int validationMode, boolean namespaceAware) throws Exception {
- DocumentBuilderFactory factory = createDocumentBuilderFactory(validationMode, namespaceAware);
- if (logger.isDebugEnabled()) {
- logger.debug("Using JAXP provider [" + factory.getClass().getName() + "]");
- }
- DocumentBuilder builder = createDocumentBuilder(factory, entityResolver, errorHandler);
- return builder.parse(inputSource);
- }
- protected DocumentBuilderFactory createDocumentBuilderFactory(int validationMode, boolean namespaceAware)
- throws ParserConfigurationException {
- DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
- factory.setNamespaceAware(namespaceAware);
- if (validationMode != XmlValidationModeDetector.VALIDATION_NONE) {
- factory.setValidating(true);
- if (validationMode == XmlValidationModeDetector.VALIDATION_XSD) {
- // Enforce namespace aware for XSD...
- factory.setNamespaceAware(true);
- try {
- 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;
- }
- protected DocumentBuilder createDocumentBuilder(
- DocumentBuilderFactory factory, EntityResolver entityResolver, ErrorHandler errorHandler)
- throws ParserConfigurationException {
- DocumentBuilder docBuilder = factory.newDocumentBuilder();
- if (entityResolver != null) {
- docBuilder.setEntityResolver(entityResolver);
- }
- if (errorHandler != null) {
- docBuilder.setErrorHandler(errorHandler);
- }
- return docBuilder;
- }
- }
對於如何讀取一個 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。
- public class XmlBeanDefinitionReader extends AbstractBeanDefinitionReader {
- protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource)
- throws BeanDefinitionStoreException {
- try {
- ……
- return registerBeanDefinitions(doc, resource);
- ……
- }
- public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException {
- // Support old XmlBeanDefinitionParser SPI for backwards-compatibility.
- if (this.parserClass != null) {
- XmlBeanDefinitionParser parser =
- (XmlBeanDefinitionParser) BeanUtils.instantiateClass(this.parserClass);
- return parser.registerBeanDefinitions(this, doc, resource);
- }
- // Read document based on new BeanDefinitionDocumentReader SPI.
- BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader();
- int countBefore = getRegistry().getBeanDefinitionCount();
- documentReader.registerBeanDefinitions(doc, createReaderContext(resource));
- return getRegistry().getBeanDefinitionCount() - countBefore;
- }
- ……}
DefaultBeanDefinitionDocumentReader 主要完成兩件事情,解析<bean> 元素,爲擴展spring 的元素尋找合適的解析器,並把相應的元素交給解析器解析。第一個任務,解析<bean> 元素,這個spring 的核心功能及IoC 或者是 DI,這由spring 自己來處理,這個工作有一個專門的委託類來處理BeanDefinitionParserDelegate,由它來解析 <bean> 元素,並把解析的結果註冊到BeanDefinitionRegistry(XmlBeanFactory 實現了此接口) 中。
- public class DefaultBeanDefinitionDocumentReader implements BeanDefinitionDocumentReader {
- public void registerBeanDefinitions(Document doc, XmlReaderContext readerContext) {
- this.readerContext = readerContext;
- logger.debug("Loading bean definitions");
- Element root = doc.getDocumentElement();
- BeanDefinitionParserDelegate delegate = createHelper(readerContext, root);
- preProcessXml(root);
- parseBeanDefinitions(root, delegate);
- postProcessXml(root);
- }
- protected BeanDefinitionParserDelegate createHelper(XmlReaderContext readerContext, Element root) {
- BeanDefinitionParserDelegate delegate = new BeanDefinitionParserDelegate(readerContext);
- delegate.initDefaults(root);
- return delegate;
- }
- protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) {
- if (delegate.isDefaultNamespace(root.getNamespaceURI())) {
- 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;
- String namespaceUri = ele.getNamespaceURI();
- if (delegate.isDefaultNamespace(namespaceUri)) {
- parseDefaultElement(ele, delegate);
- }
- else {
- delegate.parseCustomElement(ele);
- }
- }
- }
- }
- else {
- delegate.parseCustomElement(root);
- }
- }
- ……
- }
那麼 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 配置。
- public static final String BEANS_NAMESPACE_URI = "http://www.springframework.org/schema/beans";
- public boolean isDefaultNamespace(String namespaceUri) {
- return (!StringUtils.hasLength(namespaceUri) || BEANS_NAMESPACE_URI.equals(namespaceUri));
- }
- public BeanDefinition parseCustomElement(Element ele) {
- return parseCustomElement(ele, null);
- }
- private void parseDefaultElement(Element ele, BeanDefinitionParserDelegate delegate) {
- if (DomUtils.nodeNameEquals(ele, IMPORT_ELEMENT)) {
- importBeanDefinitionResource(ele);
- }
- else if (DomUtils.nodeNameEquals(ele, ALIAS_ELEMENT)) {
- processAliasRegistration(ele);
- }
- else if (DomUtils.nodeNameEquals(ele, BEAN_ELEMENT)) {
- processBeanDefinition(ele, delegate);
- }
- }
- public BeanDefinition parseCustomElement(Element ele, BeanDefinition containingBd) {
- String namespaceUri = ele.getNamespaceURI();
- NamespaceHandler handler = this.readerContext.getNamespaceHandlerResolver().resolve(namespaceUri);
- if (handler == null) {
- error("Unable to locate Spring NamespaceHandler for XML schema namespace [" + namespaceUri + "]", ele);
- return null;
- }
- return handler.parse(ele, new ParserContext(this.readerContext, this, containingBd));
- }
- public static final String BEAN_ELEMENT = BeanDefinitionParserDelegate.BEAN_ELEMENT;
- public static final String ALIAS_ELEMENT = "alias";
- 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 的實現。
- public interface NamespaceHandlerResolver {
- /**
- * Resolve the namespace URI and return the located {@link NamespaceHandler}
- * implementation.
- * @param namespaceUri the relevant namespace URI
- * @return the located {@link NamespaceHandler} (may be <code>null</code>)
- */
- NamespaceHandler resolve(String namespaceUri);
- }
如上面所提到的,擴展 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 的配置更容易閱讀和理解。