引子
我们都知道创建IOC
容器的方式有以下常用几种:
FileSystemXmlApplicationContext fileSystemXmlApplicationContext=new FileSystemXmlApplicationContext("/application.xml");
FileSystemXmlApplicationContext fileSystemXmlApplicationContext1 = new FileSystemXmlApplicationContext("/application.xml");
ClassPathXmlApplicationContext classPathXmlApplicationContext = new ClassPathXmlApplicationContext("/application.xml");
-- 工厂级别的
BeanFactory beanFactory = new XmlBeanFactory(new ClassPathResource("application.xml"));
但是万变不离其宗,我们通过源代码跟踪发现最终加载XML
的实现类还是进了XmlBeanDefinitionReader
类的
public int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException {
return loadBeanDefinitions(new EncodedResource(resource));
}
方法中,这也是本节的重点。
解析XML与装载BeanDefinition
-
资源定位 resource
Spring的配置文件读取是通过ClassPathResource进行封装的,这也正对应了上述方法中的参数,不过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 {
InputStream inputStream = encodedResource.getResource().getInputStream();
try {
InputSource inputSource = new InputSource(inputStream);
if (encodedResource.getEncoding() != null) {
inputSource.setEncoding(encodedResource.getEncoding());
}
// 核心步骤
return doLoadBeanDefinitions(inputSource, encodedResource.getResource());
}
......
}
在这个阶段整理数据准备的逻辑,首先对传入的resource参数做封装,目的是考虑到 Resource可能存在编码要求的情况,其次,通过SAX读取XML文件的方式来准备InputSource 对象,最后将准备的数据通过参数传入真正的核心处理部分doLoadBeanDefinitions(inputSource, encodedResource. getResource())
- 验证
XML
protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource)
throws BeanDefinitionStoreException {
try {
Document doc = doLoadDocument(inputSource, resource);
return registerBeanDefinitions(doc, resource);
}
......
}
protected Document doLoadDocument(InputSource inputSource, Resource resource) throws Exception {
return this.documentLoader.loadDocument(inputSource, getEntityResolver(), this.errorHandler,
getValidationModeForResource(resource), isNamespaceAware());
}
其实只做了三件事,这三件事的每一件都 必不可少。
- 获取对XML文件的验证模式。
- 加载XML文件,并得到对应的Document
- 根据返回的Document注册Bean信息。
通过方法名字的关键字我们就能大体预支该方法的作用, Spring这一点做的非常好。这3个步骤支撑着整个Spring容器部分的实现,尤其是第3步对配置文件的解析,逻辑非 常的复杂,这也是我们重点关注的部分。在这里多嘴说一句在getValidationModeForResource()验证XML文件的时候提到了DTD
与XSD
文件,具体两者的区别请参照[DTD与XSD]。(https://www.cnblogs.com/imsoft/p/6370077.html)
protected int getValidationModeForResource(Resource resource) {
int validationModeToUse = getValidationMode();
//如果手动指定了验证模式则使用指定的验证模式
if (validationModeToUse != VALIDATION_AUTO) {
return validationModeToUse;
}
//如果未指定则使用自动检测
int detectedMode = detectValidationMode(resource);
if (detectedMode != VALIDATION_AUTO) {
return detectedMode;
}
return VALIDATION_XSD;
}
在自动检测的代码中,Spring用来检 测验证模式的办法就是判断是否包含DOCTYPE,如果包含就是DTD,否则就是XSD。
- 获取
Document
public Document loadDocument(InputSource inputSource, EntityResolver entityResolver,
ErrorHandler errorHandler,