回顾上篇
在上篇文章环境搭建中,我们创建了一个测试模块,在测试模块中,我们只要创建一个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)解析出来。