`
jiangshuiy
  • 浏览: 339990 次
  • 性别: Icon_minigender_1
  • 来自: 杭州
社区版块
存档分类
最新评论

Spring读取XML配置源码解析

 
阅读更多

 

Spring中,配置文件主要格式是XMLspring 本身提供了很多 xml namespace 的配置,如 jmsaop 等。并且,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文件的。而这个实际读取转发转发到XmlBeanDefinitionReaderloadBeanDefinitions方法:

 

 

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,很明显,矛头转向DefaultDocumentLoaderloadDocument方法,请看:

 

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 来验证 xmlspring 使用的 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/XMLSchemajaxp 则会使用 xml schema 来验证 xml 文档,使用这种验证方式需要提供一个 EntityResolver 的实现,EntityResolver 的使用 DocumentBuilder setEntityResolver 方法设置。spring 提供了 EntityResolver 的实现,这个实现也是扩展 spring 的关键所在

 

 

 

    在完成了 Resource Document 的转换后,下面就是从 Document 中解析出各个 bean 的配置了,为此 spring 又抽象了一个接口 BeanDefinitionDocumentReader,从它的名称中可以一目了然这个接口负责从 Document 中读取 bean 定义,这个接口中只定义了一个方法 registerBeanDefinitionsspring 也提供了一个默认实现 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> 元素,并把解析的结果注册到 BeanDefinitionRegistryXmlBeanFactory 实现了此接口) 中。

 

 

 

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 来解析处理,这些元素包括 beansbeanimportalias,如果 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));
	}

 

 

Spring读取xml

 

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 以及其它的一些 tagxbean 会根据这些特殊的 tag 来生成一个 xml schemaxbean 完成的第二件事情就是它会生成扩展 spring 所需的一些配置;第三它重新实现了一些 spring 中的可替换组件,如它扩展了 XmlBeanDefinitionReader 实现了自己的 BeanDefinitionReader XBeanXmlDefinitionReader,实现了自己的 ApplicationContext ResourceXmlApplicationContext,如果使用了 xbean 就必须使用 xbean 实现的 ApplicationContextxbean 提供的 BeanDefinitionReader 实现只是把一些定制的元素转换成了 spring 中的 bean 元素,这样使 spring 的配置更容易阅读和理解。

 

 

此文参考:

spring 读取 xml 配置源代码解析

分享到:
评论
2 楼 yueshang520 2016-08-18  
太厉害了  
1 楼 Charles2628 2013-07-12  

相关推荐

    模拟spring的xml配置文件注入

    这个项目的源码演示了如何使用SAXBuilder解析XML配置,然后模拟Spring的bean管理和依赖注入。通过学习和理解这个示例,你将能够更好地掌握Spring的XML配置原理,以及如何在不使用Spring框架的情况下,自行实现类似的...

    Spring源码解析.pdf

    ### Spring源码解析知识点 #### 一、Spring IoC 容器详解 ##### 1. BeanFactory —— 最基础的IoC容器 - **概念**:`BeanFactory` 是Spring框架中最基本的IoC容器,它负责管理Bean的生命周期,包括创建、配置和...

    这一次搞懂Spring的XML解析原理说明

    在深入探讨Spring的XML解析原理之前,让我们先理解Spring框架的核心概念:控制反转(Inversion of Control...总的来说,理解Spring的XML解析原理有助于我们更好地利用Spring框架,同时也为深入学习Spring源码打下基础。

    Spring源码解析.zip

    《Spring源码解析》 Spring框架作为Java领域最流行的开源框架之一,它的设计思想和实现原理一直是许多开发者深入研究的重点。本压缩包“Spring源码解析”提供了对Spring框架核心组件——IOC(Inversion of Control...

    spring源码合集spring源码合集

    4. **MyBatis源码解析**:尽管"19-MyBatis源码—SQL操作执行流程源码深度剖析-徐庶"和"12-Spring之整合Mybatis底层源码解析-周瑜"主要关注MyBatis,但这两部分也是Spring生态的重要组成部分。我们将学习Spring如何与...

    Spring核心源码解析.pdf

    本篇文档将对Spring框架的核心源码进行解析,以帮助开发者更深入地理解Spring的工作原理和核心概念。 首先,Spring框架通过使用IoC容器来管理应用对象的创建和依赖关系。这种做法可以让程序员从创建对象的复杂性中...

    spring核心工厂配置源码

    当Spring启动时,它会读取这些配置,实例化Bean并建立它们之间的依赖关系。 在Spring源码中,`DefaultListableBeanFactory`是实现Bean工厂的主要类,它实现了`BeanFactory`接口。这个类负责解析XML配置,创建Bean...

    spring中的BeanFactory解析xml文件

    然而,理解XML配置仍然是理解和使用Spring的基础,特别是对于源码级的调试和定制。 在实际开发中,开发者可以根据需求选择适合的配置方式,利用Spring的BeanFactory或其高级版本ApplicationContext,灵活管理应用中...

    spring源码解析

    在"spring源码解析"的资料中,我们可能会深入探讨以下几个关键知识点: 1. **IoC(Inversion of Control,控制反转)与DI(Dependency Injection,依赖注入)**:这是Spring的核心特性,它通过反转对象的创建和管理...

    spring读取jar中的配置文件

    Spring支持多种方式加载配置,包括XML、Java配置类和属性文件。在处理JAR内的配置文件时,通常会使用`@PropertySource`注解来指示Spring从特定资源加载属性。例如: ```java @Configuration @PropertySource(...

    Java Spring 源码解析 Xmind 思维导图

    源码解析中,会详细讲解Bean的定义方式,如XML配置、注解驱动和Java配置类,以及Bean的生命周期,包括初始化方法、后处理器、销毁方法等。 其次,时序图是理解系统间交互的重要工具。在Spring源码解析中,时序图...

    Spring读取配置文件原理(Spring如何依赖注入的)

    首先,Spring解析XML配置文件的过程是由`BeanDefinitionReader`完成的,它负责读取并解析XML文件,生成BeanDefinition对象。Spring提供了多种类型的BeanDefinitionReader,例如`XmlBeanDefinitionReader`,用于处理...

    spring源码解析和mybatis学习

    本文将深入探讨这两个技术,并基于提供的资源——"Spring源码深度解析.pdf"和"MyBatis3用户指南中文版.pdf",对它们进行详细的知识点解析。 首先,让我们来了解Spring框架。Spring是一个开源的Java平台,它简化了...

    Spring源码解析

    《Spring源码深度解析》 在Java开发领域,Spring框架无疑是最重要的组件之一,它以其强大的功能和灵活性赢得了广大开发者的心。深入理解Spring源码对于提升开发能力、优化系统设计以及解决实际问题至关重要。本文将...

    spring 源码中文注释

    在源码分析的过程中,读者会深入理解Spring的内部工作机制,例如如何解析配置、如何创建bean实例、如何实现AOP代理等。这将有助于开发者编写更高效、更健壮的代码,也能为参与Spring的扩展或定制打下坚实基础。 总...

    SpringBoot之logback-spring.xml不生效的解决方法

    Spring Boot在初始化日志系统时,会通过`LoggingApplicationListener`这个类来查找和解析配置文件。`LoggingApplicationListener`遵循一定的顺序来查找日志配置,如`logback-test.groovy`, `logback-test.xml`, `...

    Spring学习笔记&源码

    5. **Spring MVC**:详述Spring MVC的架构,控制器、模型、视图和处理器映射器的工作原理,以及视图解析器的配置。 6. **数据访问**:涉及JDBC模板、Hibernate、MyBatis等持久层集成,讲解事务管理的编程式和声明式...

Global site tag (gtag.js) - Google Analytics