很多时候,真心欣赏 Spring 的封装、分层的方案实现。Spring 对资源文件的统一定义以及获取都是很好封装体现。
1. 资源装载的概述
在前面的分享中,使用 FileSystemXmlApplicationContext 举例。同时提到了在构建 FileSystemXmlApplicationContext 的时候会调用org.springframework.context.support.AbstractApplicationContext#refresh方法。在这个 refresh 方法中会调用AbstractApplicationContext中的refreshBeanFactory方法,这个方法就是加载资源去触发点。下图是是具体调用过程一览:
具体实现参考AbstractRefreshableApplicationContext#refreshBeanFactory实现。在这个方法中有 Bean 定义文件加载的入口:AbstractRefreshableApplicationContext#loadBeanDefinitions
同时有必要把资源加载接口在 Spring 体系中整理一下,有个统一的感性认识:
上面类图有两个地方需要说明一下
▪ PathMatchingResourcePatternResolver的实现
PathMatchingResourcePatternResolver 自己并不真实的执行加载资源操作,他依靠了外部传入的 ResourceLoader。如果外部没有传入,他就默认了 DefaultResourceLoader。有一点适配器模式的意思,他把 ResourceLoader 当成自己的一部分,否则他无法进行工作。
同时 PathMatchingResourcePatternResolver 中依赖了 AntPathMatcher,通过 AntPathMatcher 来处理匹配定义在资源文件名称,同时也支持了在Jar中获取资源的方式方法,如果读者感兴趣,可以查找对应的源码。
▪ AbstractApplicationContext的实现
AbstractApplicationContext 继承了 DefaultResourceLoader,同时实现了 ResourcePatternResolver。继承 DefaultResourceLoader,使其具有了 ResourceLoader 的服务能力,实现了 ResourcePatternResolver,使其具有了使用资源名称表达式的方式查找资源的能力。值得一提的是,他实现了 ResourcePatternResolver,并没有自己重新实现,而是让 PathMatchingResourcePatternResolver 作为真正服务的提供者。
2. 分析重要源码
2.1 AbstractXmlApplicationContext 中加载资源入口
还是以 FileSystemXmlApplicationContext 会使用到的实现为例来说明。参考实现代码:org.springframework.context.support.AbstractXmlApplicationContext#loadBeanDefinitions(org.springframework.beans.factory.support.DefaultListableBeanFactory)
// 加载Bean定义资源
protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws BeansException, IOException {
// Create a new XmlBeanDefinitionReader for the given BeanFactory.
XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory);
// Configure the bean definition reader with this context's
// 资源装载的环境
beanDefinitionReader.setEnvironment(this.getEnvironment());
// Application 本身就是一个资源装载器,因为他继承了DefaultResourceLoader
beanDefinitionReader.setResourceLoader(this);
// 定义 xml 的实体,在解析 xml 时使用
beanDefinitionReader.setEntityResolver(new ResourceEntityResolver(this));
// Allow a subclass to provide custom initialization of the reader,
// then proceed with actually loading the bean definitions.
initBeanDefinitionReader(beanDefinitionReader);
// 真正的装载资源入口
loadBeanDefinitions(beanDefinitionReader);
}
// 真正的装载资源入口
protected void loadBeanDefinitions(XmlBeanDefinitionReader reader) throws BeansException, IOException {
Resource[] configResources = getConfigResources();
if (configResources != null) {
reader.loadBeanDefinitions(configResources);
}
String[] configLocations = getConfigLocations();
if (configLocations != null) {
reader.loadBeanDefinitions(configLocations);
}
}
通过上面代码,我们了解到
AbstractXmlApplicationContext
并没有真正的加载
Bean
定义文件,他把这项艰巨的工作委托给了XmlBeanDefinitionReader。
2.2 XmlBeanDefinitionReader
首先看看上面构建 XmlBeanDefinitionReader 代码,他最终会使用下面代码进行实例的创建。
protected AbstractBeanDefinitionReader(BeanDefinitionRegistry registry) {
Assert.notNull(registry, "BeanDefinitionRegistry must not be null");
this.registry = registry;
// Determine ResourceLoader to use.
if (this.registry instanceof ResourceLoader) {
this.resourceLoader = (ResourceLoader) this.registry;
}
else {
this.resourceLoader = new PathMatchingResourcePatternResolver();
}
// Inherit Environment if possible
if (this.registry instanceof EnvironmentCapable) {
this.environment = ((EnvironmentCapable)this.registry).getEnvironment();
}
else {
this.environment = new StandardEnvironment();
}
}
我们知道
AbstractXmlApplication
他本身就是实现了
BeanDefinitionRegistry 和
ResourceLoader
的,所以构建的
XmlBeanDefinitionReader
中的
resourceLoader
和
registry
都是
Application
自己。
经过一层一层的转化和转发,最终完成使命,成功把自己的加载的任务成功转交给BeanDefinitionParserDelegate。
下面是重要节点源码分析:
▪ AbstractBeanDefinitionReader#loadBeanDefinitions(String, Set<Resource>)
public int loadBeanDefinitions(String location, Set<Resource> actualResources) throws BeanDefinitionStoreException {
// AbstractXmlApplicationContext 实现了 ResourceLoader 接口,所以到运行时就是其子类的真实实例。
ResourceLoader resourceLoader = getResourceLoader();
if (resourceLoader == null) {
throw new BeanDefinitionStoreException(
"Cannot import bean definitions from location [" + location + "]: no ResourceLoader available");
}
// 所有的 Application 都是实现了 ResourcePatternResolver 接口。在前面提到过 ApplicationContext 的实现
// ResourcePatternResolver 的方式。通过这种匹配算法,可以加载多个资源文件。
if (resourceLoader instanceof ResourcePatternResolver) {
// Resource pattern matching available.
try {
Resource[] resources = ((ResourcePatternResolver) resourceLoader).getResources(location);
// 后面说明这个方法
int loadCount = loadBeanDefinitions(resources);
if (actualResources != null) {
for (Resource resource : resources) {
actualResources.add(resource);
}
}
if (logger.isDebugEnabled()) {
logger.debug("Loaded " + loadCount + " bean definitions from location pattern [" + location + "]");
}
return loadCount;
}
catch (IOException ex) {
throw new BeanDefinitionStoreException(
"Could not resolve bean definition resource pattern [" + location + "]", ex);
}
}
// 如果不是 ResourcePatternResolver 只能加载一个资源文件
else {
// Can only load single resources by absolute URL.
Resource resource = resourceLoader.getResource(location);
// 后面说明这个方法
int loadCount = loadBeanDefinitions(resource);
if (actualResources != null) {
actualResources.add(resource);
}
if (logger.isDebugEnabled()) {
logger.debug("Loaded " + loadCount + " bean definitions from location [" + location + "]");
}
return loadCount;
}
}
在AbstractBeanDefinitionReader#loadBeanDefinitions(String, Set<Resource>)中调用了loadBeanDefinitions方法,会转发到这个方法。
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<EncodedResource>(4);
this.resourcesCurrentlyBeingLoaded.set(currentResources);
}
// 把当前解析的资源加到当前线程中,如果加入失败,说明出现了循环依赖。
// 例如有资源文件bean-a.xml 引用了 bean-b.xml ,bean-b.xml 引用 bean-c.xml ,bean-c.xml 引用了 bean-a.xml
// 他们就出现了循环依赖
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());
}
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();
}
}
}
这个方法是真正加载资源的地方,获取对xml进行验证的模式,以及读取xml到 Document 中。其中关于 EntityResolver 的内容会在后续的分享中详细讲解。
protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource)
throws BeanDefinitionStoreException {
try {
int validationMode = getValidationModeForResource(resource);
// 加载资源,解析XML文件变成可用的 document 元素,方便后面使用。具体的内容请查看 Spring 源码。
Document doc = this.documentLoader.loadDocument(
inputSource, getEntityResolver(), this.errorHandler, validationMode, isNamespaceAware());
// 十分重要的方法,注册Bean定义,后面会详细讲解。
return registerBeanDefinitions(doc, resource);
}
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);
}
}
下面的代码在下次分享中着重来讲
public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException {
BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader();
documentReader.setEnvironment(this.getEnvironment());
int countBefore = getRegistry().getBeanDefinitionCount();
documentReader.registerBeanDefinitions(doc, createReaderContext(resource));
return getRegistry().getBeanDefinitionCount() - countBefore;
}