`
jiangshuiy
  • 浏览: 335987 次
  • 性别: 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  

相关推荐

    Java Spring 源码解析 Xmind 思维导图

    Java Spring 源码解析 Xmind 思维导图。 Spring源码解析Xmind思维导图,阅读spring源码之后整理的。包括源码流程、时序图、bean生命周期等等总结 spring java

    spring源码解析思维导图

    而DefaultListableBeanFactory是整个bean加载的核心部分,是Spring注册及加载bean的默认实现,XmlBeanFactory与DefaultListableBeanFactory不同的地方其实是在XmlBeanFactory中使用了自定义的XML读取器...

    Spring Data集成MyBatis完整源码解析

    共计296个文件,主要包括200个Java源码文件,以及39个VM模板、19个XML配置、7个SQL脚本、4个Markdown文档、2个属性文件、2个Shell脚本、2个工厂配置文件和2个Processor文件及2个Mustache模板。该模块特别强化了对...

    spring_MVC源码

    -- 这里在配成spring,下边也要写一个名为spring-servlet.xml的文件,主要用来配置它的controller --&gt; 19. *.do&lt;/url-pattern&gt; 20. &lt;/servlet-mapping&gt; 21. &lt;welcome-file-list&gt; 22. &lt;welcome-file&gt;index.jsp...

    DWR.xml配置文件说明书(含源码)

    spring Location* 任何以location开头的参数,每个参数都是指定一个spring的配置文件,在参数没有设置的情况下DWR会去读取spring的全局的配置文件. spring beanName 从配置文件中读取的bean的名称 “scope参数允许你...

    Spring-Reference_zh_CN(Spring中文参考手册)

    2.2.1. 更简单的XML配置 2.2.2. 新的bean作用域 2.2.3. 可扩展的XML编写 2.3. 面向切面编程(AOP) 2.3.1. 更加简单的AOP XML配置 2.3.2. 对@AspectJ 切面的支持 2.4. 中间层 2.4.1. 在XML里更为简单的声明性事务配置 ...

    Springboot整合SSM框架的实战项目——own_demo源码解析

    - XML配置文件:1个,用于配置项目相关设置。 - YML配置文件:1个,提供了项目配置信息,以实现环境分离。 项目特色:通过整合现代开发工具和框架,own_demo旨在创建一个高效、可扩展的Spring Boot项目脚手架,便于...

    Spring 2.0 开发参考手册

    2.2.1. 更简单的XML配置 2.2.2. 新的bean作用域 2.2.3. 可扩展的XML编写 2.3. 面向切面编程(AOP) 2.3.1. 更加简单的AOP XML配置 2.3.2. 对@AspectJ 切面的支持 2.4. 中间层 2.4.1. 在XML里更为简单的声明性...

    搞定J2EE:STRUTS+SPRING+HIBERNATE整合详解与典型案例 (1)

    12.6.16 编写Spring和Hibernate的配置文件spring-config.xml 12.6.17 编写web.xml 12.6.18 验证示例 12.7 小结 第四篇 J2EE项目案例精选 第十三章 网上调查系统 13.1 系统概述 13.2 需求分析 13.2.1 系统用例图 ...

    Spring中文帮助文档

    2.2.2. 更简单的XML配置 2.2.3. 可扩展的XML编写 2.2.4. Annotation(注解)驱动配置 2.2.5. 在classpath中自动搜索组件 2.3. 面向切面编程(AOP) 2.3.1. 更加简单的AOP XML配置 2.3.2. 对@AspectJ 切面的支持 ...

    Spring API

    2.2.2. 更简单的XML配置 2.2.3. 可扩展的XML编写 2.2.4. Annotation(注解)驱动配置 2.2.5. 在classpath中自动搜索组件 2.3. 面向切面编程(AOP) 2.3.1. 更加简单的AOP XML配置 2.3.2. 对@AspectJ 切面的支持 ...

    Spring高级之注解驱动开发视频教程

    学习spring,要有一定的Java基础,同时应用过spring基于xml的配置。(或者学习过官网的Spring课程) 学习springmvc,要有一定java web开发基础。同时对spring框架要有一定了解。 3、课程亮点 系统的学习Spring框架...

    spring chm文档

    2.4.1. 在XML里更为简单的声明性事务配置 2.4.2. JPA 2.4.3. 异步的JMS 2.4.4. JDBC 2.5. Web层 2.5.1. Spring MVC的表单标签库 2.5.2. Spring MVC合理的默认值 2.5.3. Portlet 框架 2.6. 其他特性 2.6.1. ...

    搞定J2EE:STRUTS+SPRING+HIBERNATE整合详解与典型案例 (3)

    12.6.16 编写Spring和Hibernate的配置文件spring-config.xml 12.6.17 编写web.xml 12.6.18 验证示例 12.7 小结 第四篇 J2EE项目案例精选 第十三章 网上调查系统 13.1 系统概述 13.2 需求分析 13.2.1 系统用例图 ...

    JavaBean实体类 配置文件 代码一键自动生成工具

    代码一键自动生成工具 可生成Action、JavaBean实体类、Dao及实现类、service及实现类、spring.xml、struts.xml、mybatis.xml *该工具目前支持3种数据源的生成方式,分别是:JDBC、.table、PDM *JDBC:选择JDBC是只...

    搞定J2EE:STRUTS+SPRING+HIBERNATE整合详解与典型案例 (2)

    12.6.16 编写Spring和Hibernate的配置文件spring-config.xml 12.6.17 编写web.xml 12.6.18 验证示例 12.7 小结 第四篇 J2EE项目案例精选 第十三章 网上调查系统 13.1 系统概述 13.2 需求分析 13.2.1 系统用例图 ...

    Spring攻略(第二版 中文高清版).part1

    1.2 配置Spring IoC容器中的Bean 4 1.2.1 问题 4 1.2.2 解决方案 4 1.2.3 工作原理 4 1.3 调用构造程序创建Bean 14 1.3.1 问题 14 1.3.2 解决方案 14 1.3.3 工作原理 14 1.4 解决构造程序歧义 17 ...

    Spring攻略(第二版 中文高清版).part2

    1.2 配置Spring IoC容器中的Bean 4 1.2.1 问题 4 1.2.2 解决方案 4 1.2.3 工作原理 4 1.3 调用构造程序创建Bean 14 1.3.1 问题 14 1.3.2 解决方案 14 1.3.3 工作原理 14 1.4 解决构造程序歧义 17 ...

    叮当书城项目-叮当书城项目部署代码视频教程带源码(java毕业设计项目-java练手项目)

    mybatis-config.xml是对mapper.xml文件进行扫描的配置, spring.xml主要是配置组件扫描器,加载外部的properties配置文件,配置数据库连接池等等,springmvc里面是对视图解析器的配置, 4、controller文件是控制层...

    java8源码-Spring5:Spring5新特性

    XML解析、工厂模式、反射 通过加载配置文件、获取类的全路径、反射类 IOC接口(BeanFactory) IOC思想基于IOC容器完成,IOC容器底层就是对象工厂。 Spring提供IOC容器实现的2种方式: BeanFactory:IOC容器基本实现,...

Global site tag (gtag.js) - Google Analytics