在Spring中,配置文件主要格式是XML,spring 本身提供了很多 xml namespace 的配置,如 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 实现了自己的 BeanDefinitionReader XBeanXmlDefinitionReader,实现了自己的 ApplicationContext ResourceXmlApplicationContext,如果使用了 xbean 就必须使用 xbean 实现的 ApplicationContext。xbean 提供的 BeanDefinitionReader 实现只是把一些定制的元素转换成了 spring 中的 bean 元素,这样使 spring 的配置更容易阅读和理解。
此文参考: