加载BeanDefinition
1 2 3 4 5 6 7 8 ClassPathResource resource = new ClassPathResource("bean.xml" ) ; DefaultListableBeanFactory factory = new DefaultListableBeanFactory() ; XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(factory ) ; reader.loadBeanDefinitions(resource ) ;
这段代码是 Spring 中编程式使用 IoC 容器,IoC 容器的使用过程如下:
资源定位、装载、注册。
资源定位: IOC容器定位资源上一片《Spring统一资源加载策略》。
装载: 装载就是 BeanDefinition 的载入,BeanDefinitionReader 读取、解析 Resource 资源,也就是将用户定义的 Bean 表示成 IoC 容器的内部数据结构:BeanDefinition 。
在 IoC 容器内部维护着一个 BeanDefinition Map 的数据结构。
在配置文件中每一个都对应着一个 BeanDefinition 对象。
注册: 向 IoC 容器注册在第二步解析好的 BeanDefinition,其实就是将BeanDefinition注入到一个HashMap容器中,这个过程是通过 BeanDefinitionRegistry 接口来实现的,IoC 容器就是通过这个 HashMap 来维护这些 BeanDefinition 的。
在这里需要注意的一点是这个过程并没有完成依赖注入(Bean 创建),Bean 创建是发生在应用第一次调用 getBean(...)
方法,向容器索要 Bean 时。
当然我们可以通过设置预处理,即对某个 Bean 设置 lazyinit = false
属性,那么这个 Bean 的依赖注入就会在容器初始化的时候完成。
loadBeanDefinitions 装载,通过reader.loadBeanDefinitions(resource);
1 2 3 public int loadBeanDefinitions (Resource resource) throws BeanDefinitionStoreException { return loadBeanDefinitions(new EncodedResource (resource)); }
从指定的 xml 文件加载 BeanDefinition ,这里会先对 Resource 资源封装成 org.springframework.core.io.support.EncodedResource
对象。这里为什么需要将 Resource 封装成 EncodedResource 呢?主要是为了对 Resource 进行编码,保证内容读取的正确性。
然后,再调用 #loadBeanDefinitions(EncodedResource encodedResource)
方法,执行真正的逻辑实现。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 private final ThreadLocal<Set<EncodedResource>> resourcesCurrentlyBeingLoaded = new NamedThreadLocal <Set<EncodedResource>>("XML bean definition resources currently being loaded" ){ @Override protected Set<EncodedResource> initialValue () { return new HashSet <>(4 ); } };public int loadBeanDefinitions (EncodedResource encodedResource) throws BeanDefinitionStoreException { Assert.notNull(encodedResource, "EncodedResource must not be null" ); if (logger.isTraceEnabled()) { logger.trace("Loading XML bean definitions from " + encodedResource); } Set<EncodedResource> currentResources = this .resourcesCurrentlyBeingLoaded.get(); if (!currentResources.add(encodedResource)) { throw new BeanDefinitionStoreException ( "Detected cyclic loading of " + encodedResource + " - check your import definitions!" ); } try (InputStream inputStream = encodedResource.getResource().getInputStream()) { InputSource inputSource = new InputSource (inputStream); if (encodedResource.getEncoding() != null ) { inputSource.setEncoding(encodedResource.getEncoding()); } return doLoadBeanDefinitions(inputSource, encodedResource.getResource()); } catch (IOException ex) { throw new BeanDefinitionStoreException ( "IOException parsing XML document from " + encodedResource.getResource(), ex); } finally { currentResources.remove(encodedResource); if (currentResources.isEmpty()) { this .resourcesCurrentlyBeingLoaded.remove(); } } }
<1>
处,通过 resourcesCurrentlyBeingLoaded.get()
代码,来获取已经加载过的资源,然后将 encodedResource
加入其中,如果 resourcesCurrentlyBeingLoaded
中已经存在该资源,则抛出 BeanDefinitionStoreException 异常。
为什么需要这么做呢?答案在 "Detected cyclic loading"
,避免一个 EncodedResource 在加载时,还没加载完成,又加载自身,从而导致死循环 。
也因此,在 <3>
处,当一个 EncodedResource 加载完成后,需要从缓存中剔除。
<2>
处理,从 encodedResource
获取封装的 Resource 资源,并从 Resource 中获取相应的 InputStream ,然后将 InputStream 封装为 InputSource ,最后调用 #doLoadBeanDefinitions(InputSource inputSource, Resource resource)
方法,执行加载 Bean Definition 的真正逻辑。
doLoadBeanDefinitions 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 protected int doLoadBeanDefinitions (InputSource inputSource, Resource resource) throws BeanDefinitionStoreException { try { Document doc = doLoadDocument(inputSource, resource); int count = registerBeanDefinitions(doc, resource); if (logger.isDebugEnabled()) { logger.debug("Loaded " + count + " bean definitions from " + resource); } return count; } 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); } }
在 <1>
处,调用 #doLoadDocument(InputSource inputSource, Resource resource)
方法,根据 xml 文件,获取 Document 实例。
在 <2>
处,调用 #registerBeanDefinitions(Document doc, Resource resource)
方法,根据获取的 Document 实例,注册 Bean 信息。
doLoadDocument 1 2 3 protected Document doLoadDocument (InputSource inputSource, Resource resource) throws Exception { return this .documentLoader.loadDocument(inputSource, getEntityResolver(), this .errorHandler,getValidationModeForResource(resource), isNamespaceAware()); }
调用 #getValidationModeForResource(Resource resource)
方法,获取指定资源(xml)的验证模式 。看下一篇
调用 DocumentLoader#loadDocument(InputSource inputSource, EntityResolver entityResolver, ErrorHandler errorHandler, int validationMode, boolean namespaceAware)
方法,获取 XML Document 实例。
registerBeanDefinitions 看注册BeanDefinitions