先看一段熟悉的代码:
ClassPathResource resource = new ClassPathResource("bean.xml");DefaultListableBeanFactory factory = new DefaultListableBeanFactory();XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(factory);reader.loadBeanDefinitions(resource);
这段代码是 Spring 中编程式使用 IOC 容器,通过这四段简单的代码,我们可以初步判断 IOC 容器的使用过程。
整个过程就分为三个步骤:资源定位、装载、注册,如下:
<bean>
都对应着一个BeanDefinition对象。getBean()
向容器索要 Bean 时。当然我们可以通过设置预处理,即对某个 Bean 设置 lazyinit 属性,那么这个 Bean 的依赖注入就会在容器初始化的时候完成。资源定位在前面已经分析了,下面我们直接分析加载,上面提过 reader.loadBeanDefinitions(resource)
才是加载资源的真正实现,所以我们直接从该方法入手。
public int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException { return loadBeanDefinitions(new EncodedResource(resource)); }
从指定的 xml 文件加载 Bean Definition,这里会先对 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 { // 从 EncodedResource 获取封装的 Resource 并从 Resource 中获取其中的 InputStream 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 文件中加载 Bean Definition 的真正逻辑,如下:
protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource) throws BeanDefinitionStoreException { try { // 获取 Document 实例 Document doc = doLoadDocument(inputSource, resource); // 根据 Document 实例****注册 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); } }
核心部分就是 try 块的两行代码。
doLoadDocument()
方法,根据 xml 文件获取 Document 实例。其实在 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 实例。
END