spring源码:加载xml文件

一、目的

  用过spring的同学都知道,spring的目的之一是为了让管理bean的工作与业务代码分离。管理这些bean需要一些基础的属性信息(如pojo的属性值)和定义信息(如是否单例、作用域),这些信息可以使用spring注解的方式定义,也可以使用spring标签在xml配置文件中编写。目前spring注解的方式较为主流,xml配置的方式是spring初期大部分的选择,也是较为基础部分,本文探讨的是后者。读取xml文件的目的,就是为了把这些bean的信息提取出来,供后面解析bean使用。

二、功能演示

  1. Person类
package com.kaka.spring.beans;

public class Person {
    private Integer age;
    
    public Integer getAge() {
        return age;
    }
    public void setAge(Integer age) {
        this.age = age;
    }
}
  1. xml配置文件:applicationContext.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean id="person" class="com.kaka.spring.beans.Person">
        <property name="age" value="22"/>
    </bean>

</beans>
  1. 程序运行,这样person中的age属性就是22了
    public static void main(String[] args) {
        Resource classPathResource = new ClassPathResource("applicationContext.xml");
        BeanFactory xmlBeanFactory = new XmlBeanFactory(classPathResource);
        Person person = (Person) xmlBeanFactory.getBean("person");
        System.out.println(person);
    }

三、主要相关类

  • org.springframework.beans.factory.xml.XmlBeanFactory:程序的入口
  • org.springframework.beans.factory.xml.XmlBeanDefinitionReader:加载Bean
  • org.springframework.beans.factory.xml.DefaultDocumentLoader:加载xml配置文件
  • org.springframework.core.io.ClassPathResource:对类路径下文件资源的表示

三、源码分析

  1. 使用ClassPathResource读取xml文件
Resource classPathResource = new ClassPathResource("applicationContext.xml");

这就是一个读取文件的简单操作,在spring中文件放在不同的地方就是一种不同的资源,进而都被抽象成一个Resource对象。我们这个applicationContext.xml文件是放在类路径下的,就可以使用ClassPathResource来读取。

  1. XmlBeanFactory初始化
public XmlBeanFactory(Resource resource) throws BeansException {
	this(resource, null);
}
public XmlBeanFactory(Resource resource, BeanFactory parentBeanFactory) throws BeansException {
	super(parentBeanFactory);
	// 上来就是加载bean的操作啊
	this.reader.loadBeanDefinitions(resource);
}

这个reader就是XmlBeanDefinitionReader对象,继续看reader类中的方法

  1. XmlBeanDefinitionReader去实现loadBeanDefinition的操作
	@Override
	public int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException {
		// 把Resource包装成编码资源EncodedResource
		return loadBeanDefinitions(new EncodedResource(resource));
	}

	public int loadBeanDefinitions(EncodedResource encodedResource) throws BeanDefinitionStoreException {
		// 判断参数
		Assert.notNull(encodedResource, "EncodedResource must not be null");
		if (logger.isInfoEnabled()) {
			logger.info("Loading XML bean definitions from " + encodedResource.getResource());
		}

		Set<EncodedResource> currentResources = this.resourcesCurrentlyBeingLoaded.get();
		if (currentResources == null) {
			currentResources = new HashSet<>(4);
			this.resourcesCurrentlyBeingLoaded.set(currentResources);
		}
		// 一个资源只加载一次
		if (!currentResources.add(encodedResource)) {
			throw new BeanDefinitionStoreException(
					"Detected cyclic loading of " + encodedResource + " - check your import definitions!");
		}
		try {
			// 从encodedResource获取输入流
			InputStream inputStream = encodedResource.getResource().getInputStream();
			try {
				// 把inputStream包装成org.xml.sax.InputSource
				InputSource inputSource = new InputSource(inputStream);
				if (encodedResource.getEncoding() != null) {
					inputSource.setEncoding(encodedResource.getEncoding());
				}
				// 一般spring中xx()方法是校验参数、准备数据的,真正干活儿的就是doXx()方法;继续分析这个方法
				return doLoadBeanDefinitions(inputSource, encodedResource.getResource());
			}
			finally {
				inputStream.close();
			}
		}
		catch (IOException ex) {
			throw new BeanDefinitionStoreException(
					"IOException parsing XML document from " + encodedResource.getResource(), ex);
		}
		finally {
			currentResources.remove(encodedResource);
			if (currentResources.isEmpty()) {
				this.resourcesCurrentlyBeingLoaded.remove();
			}
		}
	}

	protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource)
			throws BeanDefinitionStoreException {
		try {
			// 继续看怎么获取这个Document的
			Document doc = doLoadDocument(inputSource, resource);
			// 注册bean
			return registerBeanDefinitions(doc, resource);
		}
		// 省略catch的异常
		...
	}
	protected Document doLoadDocument(InputSource inputSource, Resource resource) throws Exception {
		// 这里调用了DefaultDocumentLoader的loadDocument的方法
		return this.documentLoader.loadDocument(inputSource, getEntityResolver(), this.errorHandler,
				getValidationModeForResource(resource), isNamespaceAware());
	}
  1. XmlBeanDefinitionReader把加载xml文件的操作,交由DefaultDocumentLoader处理
	public Document loadDocument(InputSource inputSource, EntityResolver entityResolver,
			ErrorHandler errorHandler, int validationMode, boolean namespaceAware) throws Exception {
		// 1.创建解析器工厂
		DocumentBuilderFactory factory = createDocumentBuilderFactory(validationMode, namespaceAware);
		if (logger.isDebugEnabled()) {
			logger.debug("Using JAXP provider [" + factory.getClass().getName() + "]");
		}
		// 2.创建解析器对象
		DocumentBuilder builder = createDocumentBuilder(factory, entityResolver, errorHandler);
		// 3.获取Document
		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,
			@Nullable EntityResolver entityResolver, @Nullable ErrorHandler errorHandler)
			throws ParserConfigurationException {
		// 从解析器工厂中创建解析器
		DocumentBuilder docBuilder = factory.newDocumentBuilder();
		if (entityResolver != null) {
			docBuilder.setEntityResolver(entityResolver);
		}
		if (errorHandler != null) {
			docBuilder.setErrorHandler(errorHandler);
		}
		return docBuilder;
	}

四、关键点总结

spring先使用Resource对象封装xml资源,然后把Resource传给XmlBeanFactory的构造方法;XmlBeanFactory交由XmlBeanDefinitionReader实现加载bean的操作,XmlBeanDefinitionReader在加载Bean的第一步先使用XmlBeanDefinitionReader去loadDocument进而返回xml对应的Document。

五、借鉴

  1. org.springframework.beans.factory.support.AbstractBeanDefinitionReader
    类中用到的日志对象,可以使用final关键字声明,此类打印的日志就不会被改变。
protected final Log logger = LogFactory.getLog(getClass());
  1. org.springframework.beans.factory.xml.XmlBeanDefinitionReader#loadBeanDefinitions(org.springframework.core.io.support.EncodedResource)
    打日志的时候使用级别控制
if (logger.isDebugEnabled()) {
	logger.debug("Using JAXP provider [" + factory.getClass().getName() + "]");
}
  1. org.springframework.beans.factory.xml.XmlBeanDefinitionReader#loadBeanDefinitions(org.springframework.core.io.support.EncodedResource)
    在方法第一步判断必要参数
Assert.notNull(encodedResource, "EncodedResource must not be null");
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值