Spring IOC - 资源装载
很多时候,真心欣赏 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; }
上一篇: 孩子夏天出游小贴士:旅游生病怎么办
下一篇: 杭州晚上哪里好玩 杭州适合晚上玩的地方