本文作者:chenssy
出处:http://cmsblogs.com/?p=2658
在学习Spring源码
的过程中发现的好站+好贴,感谢作者。Spring版本:Spring 5.0.6.RELEASE
开局先看一段代码:
ClassPathResource resource = new ClassPathResource ("bean.xml" );DefaultListableBeanFactory factory = new DefaultListableBeanFactory ();XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader (factory);reader.loadBeanDefinitions(resource);
这段代码是 Spring
中编程式使用 IOC
容器,通过这四段简单的代码,我们可以初步判断 IOC
容器的使用过程。
资源定位 。我们一般用外部资源来描述 Bean
对象,所以在初始化 IOC
容器的第一步就是需要定位这个外部资源。在( IOC 之 Spring 统一资源加载策略 )已经详细说明了资源加载的过程。
装载 。装载就是 BeanDefinition
的载入。BeanDefinitionReader
读取、解析 Resource
资源,也就是将用户定义的 Bean
表示成 IOC
容器的内部数据结构:BeanDefinition
。在 IOC
容器内部维护着一个 BeanDefinition Map
的数据结构,在配置文件中每一个 都对应着一个BeanDefinition
对象。
注册 。向IOC
容器注册在第二步解析好的 BeanDefinition
,这个过程是通过 BeanDefinitionRegistry
接口来实现的。在 IOC
容器内部其实是将第二个过程解析得到的 BeanDefinition
注入到一个 HashMap
容器中,IOC
容器就是通过这个 HashMap
来维护这些 BeanDefinition
的。在这里需要注意的一点是这个过程并没有完成依赖注入,依赖注册是发生在应用第一次调用 getBean()
向容器索要 Bean
时。当然我们可以通过设置预处理,即对某个 Bean
设置 lazyinit
属性,那么这个 Bean
的依赖注入就会在容器初始化的时候完成。
XML Resource => XML Document => Bean Definition 。
资源定位在前面已经分析了,下面我们直接分析加载,上面提过reader.loadBeanDefinitions(resource)
才是加载资源的真正实现,所以我们直接从该方法入手。
public int loadBeanDefinitions (Resource resource) throws BeanDefinitionStoreException { return loadBeanDefinitions(new EncodedResource (resource)); }
从指定的 xml
文件加载 BeanDefinition
,这里会先对 Resource
资源封装成 EncodedResource
。这里为什么需要将 Resource
封装成 EncodedResource
呢?主要是为了对 Resource
进行编码,保证内容读取的正确性。封装成 EncodedResource
后,调用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 <>(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()); } 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(); } } }
首先通过
resourcesCurrentlyBeingLoaded.get()
来获取已经加载过的资源,然后将 encodedResource
加入其中,如果 resourcesCurrentlyBeingLoaded
中已经存在该资源,则抛出 BeanDefinitionStoreException
异常。完成后从 encodedResource
获取封装的 Resource
资源并从 Resource
中获取相应的 InputStream
,最后将 InputStream
封装为 InputSource
调用 doLoadBeanDefinitions()
。
方法 doLoadBeanDefinitions()
为从 xml
文件中加载 BeanDefinition
的真正逻辑,如下:
protected int doLoadBeanDefinitions (InputSource inputSource, Resource resource) throws BeanDefinitionStoreException { try { Document doc = doLoadDocument(inputSource, resource); 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); } }
核心部分就是 try
块的两行代码。
调用 doLoadDocument()
方法,根据 xml
文件获取 Document
实例。
根据获取的 Document
实例注册 Bean
信息。 其实在
doLoadDocument()
方法内部还获取了 xml
文件的验证模式。如下:
protected Document doLoadDocument (InputSource inputSource, Resource resource) throws Exception { return this .documentLoader.loadDocument(inputSource, getEntityResolver(), this .errorHandler, getValidationModeForResource(resource), isNamespaceAware()); }
调用getValidationModeForResource()
获取指定资源(xml
)的验证模式。
所以 doLoadBeanDefinitions()
主要就是做了三件事情。
调用 getValidationModeForResource()
获取 xml
文件的验证模式
调用 loadDocument()
根据 xml
文件获取相应的 Document
实例。
调用 registerBeanDefinitions()
注册 Bean
实例。