原文: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 的配置更容易阅读和理解。