Spring源码解析(2)——读取配置文件

回顾上篇

在上篇文章环境搭建中,我们创建了一个测试模块,在测试模块中,我们只要创建一个xml配置文件,配置相关bean信息,就能从spring容器中获取到bean,然后调用bean里面的方法。相关代码如下:
1、XML配置文件:

<?xml version="1.0" encoding="ISO-8859-1"?>
<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
                       https://www.springframework.org/schema/beans/spring-beans-3.0.xsd">

   <bean id="myTestBean" class="com.guiji.bean.MyTestBean">

   </bean>

</beans>

2、测试代码:

@Test
	public void testSimpleLoad(){

		BeanFactory beanFactory = new XmlBeanFactory(new ClassPathResource("BeanFactoryTest.xml"));

		MyTestBean myTestBean = (MyTestBean) beanFactory.getBean("myTestBean");

		String testStr = myTestBean.getTestStr();

		System.out.println(testStr);

		Assert.assertEquals("javaGuiji",testStr);

	}

这里面是如何实现的?spring帮我们做了什么东西,让我们可以不用new就能获得一个对象的?

分析源码实现

首先我们看到测试代码第一行。

BeanFactory beanFactory = new XmlBeanFactory(new ClassPathResource("BeanFactoryTest.xml"));

这里通过传入配置文件名称来创建一个ClassPathResource。用ClassPathResource来封装配置文件,主要是为了调用getInputStream()方法来拿到InputStream,对配置文件进行读取。ClassPathResource底层实现了Resource接口。

创建了ClassPathResource后,就可以对XmlBeanFactory进行初始化了。初始化代码如下:

public XmlBeanFactory(Resource resource) throws BeansException {
	this(resource, null);
}
public XmlBeanFactory(Resource resource, BeanFactory parentBeanFactory) throws BeansException {
	super(parentBeanFactory);
	this.reader.loadBeanDefinitions(resource);
}

其中this.reader.loadBeanDefinitions(resource);这句代码才是真正的去读取配置文件。我们看到这里面是调用reader的loadBeanDefinitions方法。这个reader的初始化是直接在XmlBeanFactory里面进行初始化的。代码如下:

private final XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(this);

我们进入到loadBeanDefinitions方法里面。

public int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException {
	return loadBeanDefinitions(new EncodedResource(resource));
}

首先对Resource使用EncodedResource进行封装,这么做主要是为了对资源文件进行编码处理。然后再继续调用loadBeanDefinitions(new EncodedResource(resource))方法。

public int loadBeanDefinitions(EncodedResource encodedResource) throws BeanDefinitionStoreException {
		Assert.notNull(encodedResource, "EncodedResource must not be null");
		if (logger.isTraceEnabled()) {
			logger.trace("Loading XML bean definitions from " + encodedResource);
		}

		Set<EncodedResource> currentResources = this.resourcesCurrentlyBeingLoaded.get();

		if (!currentResources.add(encodedResource)) {
			throw new BeanDefinitionStoreException(
					"Detected cyclic loading of " + encodedResource + " - check your import definitions!");
		}

		try (InputStream inputStream = encodedResource.getResource().getInputStream()) {
			InputSource inputSource = new InputSource(inputStream);
			if (encodedResource.getEncoding() != null) {
				inputSource.setEncoding(encodedResource.getEncoding());
			}
			return doLoadBeanDefinitions(inputSource, encodedResource.getResource());
		}
		catch (IOException ex) {
			throw new BeanDefinitionStoreException(
					"IOException parsing XML document from " + encodedResource.getResource(), ex);
		}
		finally {
			currentResources.remove(encodedResource);
			if (currentResources.isEmpty()) {
				this.resourcesCurrentlyBeingLoaded.remove();
			}
		}
	}

这段代码中主要的逻辑是:
1、根据EncodedResource获得InputStream 。
2、根据获得的InputStream 构造InputSource 。
3、EncodedResource有编码的话,设置InputSource 的编码。
4、将构造的 InputSource 实例和 Resource 实例通过参数传入核心处理部分doLoadBeanDefinitions(inputSource, encodedResource.getResource())。

protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource)
			throws BeanDefinitionStoreException {

		try {
			Document doc = doLoadDocument(inputSource, resource);
			int count = registerBeanDefinitions(doc, resource);
			if (logger.isDebugEnabled()) {
				logger.debug("Loaded " + count + " bean definitions from " + resource);
			}
			return count;
		}
		catch (BeanDefinitionStoreException ex) {
			throw ex;
		}
		catch (SAXParseException ex) {
			throw new XmlBeanDefinitionStoreException(resource.getDescription(),
					"Line " + ex.getLineNumber() + " in XML document from " + resource + " is invalid", ex);
		}
		catch (SAXException ex) {
			throw new XmlBeanDefinitionStoreException(resource.getDescription(),
					"XML document from " + resource + " is invalid", ex);
		}
		catch (ParserConfigurationException ex) {
			throw new BeanDefinitionStoreException(resource.getDescription(),
					"Parser configuration exception parsing XML from " + resource, ex);
		}
		catch (IOException ex) {
			throw new BeanDefinitionStoreException(resource.getDescription(),
					"IOException parsing XML document from " + resource, ex);
		}
		catch (Throwable ex) {
			throw new BeanDefinitionStoreException(resource.getDescription(),
					"Unexpected exception parsing XML document from " + resource, ex);
		}
	}

上面代码中,其实就做了两件事。
1、加载XML文件,得到Document。
2、根据Document注册bean。
接下来我们看看,加载XML文件,得到Document的这个方法。

protected Document doLoadDocument(InputSource inputSource, Resource resource) throws Exception {
	return this.documentLoader.loadDocument(inputSource, getEntityResolver(), this.errorHandler,
				getValidationModeForResource(resource), isNamespaceAware());
}

通过documentLoader调用loadDocument方法来获取Document。
documentLoader初始化时指定的是一个DefaultDocumentLoader。

private DocumentLoader documentLoader = new DefaultDocumentLoader();

所以调用的是DefaultDocumentLoader的loadDocument方法。

public Document loadDocument(InputSource inputSource, EntityResolver entityResolver,
		ErrorHandler errorHandler, int validationMode, boolean namespaceAware) throws Exception {

	DocumentBuilderFactory factory = createDocumentBuilderFactory(validationMode, namespaceAware);
	if (logger.isTraceEnabled()) {
		logger.trace("Using JAXP provider [" + factory.getClass().getName() + "]");
	}
	DocumentBuilder builder = createDocumentBuilder(factory, entityResolver, errorHandler);
	return builder.parse(inputSource);
}

到此XML的读取就已经完成了,spring已经将XML文件的信息转换成了Document 对象,后续就是对Document 进行解析,将里面的各个元素(如:beans、bean)和各个属性(如:id、class)解析出来。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值