原文出自:http://cmsblogs.com
前面 13 篇博文从源码层次分析了 IOC 整个初始化过程,这篇就这些内容做一个总结将其连贯起来。
在前文提过,IOC 容器的初始化过程分为三步骤:Resource 定位、BeanDefinition 的载入和解析,BeanDefinition 注册。
- Resource 定位。我们一般用外部资源来描述 Bean 对象,所以在初始化 IOC 容器的第一步就是需要定位这个外部资源。
- BeanDefinition 的载入和解析。装载就是 BeanDefinition 的载入。BeanDefinitionReader 读取、解析 Resource 资源,也就是将用户定义的 Bean 表示成 IOC 容器的内部数据结构:BeanDefinition。在 IOC 容器内部维护着一个 BeanDefinition Map 的数据结构,在配置文件中每一个都对应着一个BeanDefinition对象。
- BeanDefinition 注册。向IOC容器注册在第二步解析好的 BeanDefinition,这个过程是通过 BeanDefinitionRegistery 接口来实现的。在 IOC 容器内部其实是将第二个过程解析得到的 BeanDefinition 注入到一个 HashMap 容器中,IOC 容器就是通过这个 HashMap 来维护这些 BeanDefinition 的。在这里需要注意的一点是这个过程并没有完成依赖注入,依赖注册是发生在应用第一次调用
getBean()
向容器索要 Bean 时。当然我们可以通过设置预处理,即对某个 Bean 设置 lazyinit 属性,那么这个 Bean 的依赖注入就会在容器初始化的时候完成。
还记得在博客【死磕 Spring】----- IOC 之 加载 Bean 中提供的一段代码吗?这里我们同样也以这段代码作为我们研究 IOC 初始化过程的开端,如下:
ClassPathResource resource = new ClassPathResource("bean.xml"); DefaultListableBeanFactory factory = new DefaultListableBeanFactory(); XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(factory); reader.loadBeanDefinitions(resource);
刚刚开始的时候可能对上面这几行代码不知道什么意思,现在应该就一目了然了。
ClassPathResource resource = new ClassPathResource("bean.xml");
: 根据 Xml 配置文件创建 Resource 资源对象。ClassPathResource 是 Resource 接口的子类,bean.xml 文件中的内容是我们定义的 Bean 信息。DefaultListableBeanFactory factory = new DefaultListableBeanFactory();
创建一个 BeanFactory。DefaultListableBeanFactory 是 BeanFactory 的一个子类,BeanFactory 作为一个接口,其实它本身是不具有独立使用的功能的,而 DefaultListableBeanFactory 则是真正可以独立使用的 IOC 容器,它是整个 Spring IOC 的始祖,在后续会有专门的文章来分析它。XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(factory);
:创建 XmlBeanDefinitionReader 读取器,用于载入 BeanDefinition 。reader.loadBeanDefinitions(resource);
:开启 Bean 的载入和注册进程,完成后的 Bean 放置在 IOC 容器中。
Resource 定位
Spring 为了解决资源定位的问题,提供了两个接口:Resource、ResourceLoader,其中 Resource 接口是 Spring 统一资源的抽象接口,ResourceLoader 则是 Spring 资源加载的统一抽象。关于Resource、ResourceLoader 的更多知识请关注【死磕 Spring】----- IOC 之 Spring 统一资源加载策略
Resource 资源的定位需要 Resource 和 ResourceLoader 两个接口互相配合,在上面那段代码中 new ClassPathResource("bean.xml")
为我们定义了资源,那么 ResourceLoader 则是在什么时候初始化的呢?看 XmlBeanDefinitionReader 构造方法:
public XmlBeanDefinitionReader(BeanDefinitionRegistry registry) {
super(registry);
}
- 1
- 2
- 3
直接调用父类 AbstractBeanDefinitionReader :
protected AbstractBeanDefinitionReader(BeanDefinitionRegistry registry) { Assert.notNull(registry, "BeanDefinitionRegistry must not be null"); this.registry = registry;
<span class="token comment">// Determine ResourceLoader to use.</span> <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">.</span>registry <span class="token keyword">instanceof</span> <span class="token class-name">ResourceLoader</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">this</span><span class="token punctuation">.</span>resourceLoader <span class="token operator">=</span> <span class="token punctuation">(</span>ResourceLoader<span class="token punctuation">)</span> <span class="token keyword">this</span><span class="token punctuation">.</span>registry<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">else</span> <span class="token punctuation">{</span> <span class="token keyword">this</span><span class="token punctuation">.</span>resourceLoader <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">PathMatchingResourcePatternResolver</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token comment">// Inherit Environment if possible</span> <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">.</span>registry <span class="token keyword">instanceof</span> <span class="token class-name">EnvironmentCapable</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">this</span><span class="token punctuation">.</span>environment <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token punctuation">(</span>EnvironmentCapable<span class="token punctuation">)</span> <span class="token keyword">this</span><span class="token punctuation">.</span>registry<span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">getEnvironment</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">else</span> <span class="token punctuation">{</span> <span class="token keyword">this</span><span class="token punctuation">.</span>environment <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">StandardEnvironment</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span>
核心在于设置 resourceLoader 这段,如果设置了 ResourceLoader 则用设置的,否则使用 PathMatchingResourcePatternResolver ,该类是一个集大成者的 ResourceLoader。
BeanDefinition 的载入和解析
reader.loadBeanDefinitions(resource);
开启 BeanDefinition 的解析过程。如下:
public int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException { return loadBeanDefinitions(new EncodedResource(resource)); }
在这个方法会将资源 resource 包装成一个 EncodedResource 实例对象,然后调用
loadBeanDefinitions()
方法,而将 Resource 封装成 EncodedResource 主要是为了对 Resource 进行编码,保证内容读取的正确性。public int loadBeanDefinitions(EncodedResource encodedResource) throws BeanDefinitionStoreException { // 省略一些代码 try { // 将资源文件转为 InputStream 的 IO 流 InputStream inputStream = encodedResource.getResource().getInputStream(); try { // 从 InputStream 中得到 XML 的解析源 InputSource inputSource = new InputSource(inputStream); if (encodedResource.getEncoding() != null) { inputSource.setEncoding(encodedResource.getEncoding()); }
<span class="token comment">// 具体的读取过程</span> <span class="token keyword">return</span> <span class="token function">doLoadBeanDefinitions</span><span class="token punctuation">(</span>inputSource<span class="token punctuation">,</span> encodedResource<span class="token punctuation">.</span><span class="token function">getResource</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">finally</span> <span class="token punctuation">{</span> inputStream<span class="token punctuation">.</span><span class="token function">close</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token comment">// 省略一些代码</span> <span class="token punctuation">}</span>
从 encodedResource 源中获取 xml 的解析源,调用 doLoadBeanDefinitions()
执行具体的解析过程。
protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource) throws BeanDefinitionStoreException { try { Document doc = doLoadDocument(inputSource, resource); return registerBeanDefinitions(doc, resource); } // 省略很多catch代码
在该方法中主要做两件事:1、根据 xml 解析源获取相应的 Document 对象,2、调用
registerBeanDefinitions()
开启 BeanDefinition 的解析注册过程。转换为 Document 对象
调用
doLoadDocument()
会将 Bean 定义的资源转换为 Document 对象。protected Document doLoadDocument(InputSource inputSource, Resource resource) throws Exception { return this.documentLoader.loadDocument(inputSource, getEntityResolver(), this.errorHandler, getValidationModeForResource(resource), isNamespaceAware()); }
loadDocument()
方法接受五个参数:
- inputSource:加载 Document 的 Resource 源
- entityResolver:解析文件的解析器
- errorHandler:处理加载 Document 对象的过程的错误
- validationMode:验证模式
- namespaceAware:命名空间支持。如果要提供对 XML 名称空间的支持,则为true
对于这五个参数,有两个参数需要重点关注下:entityResolver、validationMode。这两个参数分别在 【死磕Spring】----- IOC 之 获取 Document 对象、【死磕Spring】----- IOC 之 获取验证模型 中有详细的讲述。
loadDocument()
在类 DefaultDocumentLoader 中提供了实现,如下:
public Document loadDocument(InputSource inputSource, EntityResolver entityResolver, ErrorHandler errorHandler, int validationMode, boolean namespaceAware) throws Exception { // 创建文件解析工厂 DocumentBuilderFactory factory = createDocumentBuilderFactory(validationMode, namespaceAware); if (logger.isDebugEnabled()) { logger.debug("Using JAXP provider [" + factory.getClass().getName() + "]"); } // 创建文档解析器 DocumentBuilder builder = createDocumentBuilder(factory, entityResolver, errorHandler); // 解析 Spring 的 Bean 定义资源 return builder.parse(inputSource); }
这到这里,就已经将定义的 Bean 资源文件,载入并转换为 Document 对象了,那么下一步就是如何将其解析为 Spring IOC 管理的 Bean 对象并将其注册到容器中。这个过程有方法
registerBeanDefinitions()
实现。如下:public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException { // 创建 BeanDefinitionDocumentReader 来对 xml 格式的BeanDefinition 解析 BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader(); // 获得容器中注册的Bean数量 int countBefore = getRegistry().getBeanDefinitionCount();
<span class="token comment">// 解析过程入口,这里使用了委派模式,BeanDefinitionDocumentReader只是个接口,</span> <span class="token comment">// 具体的解析实现过程有实现类DefaultBeanDefinitionDocumentReader完成</span> documentReader<span class="token punctuation">.</span><span class="token function">registerBeanDefinitions</span><span class="token punctuation">(</span>doc<span class="token punctuation">,</span> <span class="token function">createReaderContext</span><span class="token punctuation">(</span>resource<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">return</span> <span class="token function">getRegistry</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">getBeanDefinitionCount</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">-</span> countBefore<span class="token punctuation">;</span> <span class="token punctuation">}</span>
首先创建 BeanDefinition 的解析器 BeanDefinitionDocumentReader,然后调用 documentReader.registerBeanDefinitions()
开启解析过程,这里使用的是委派模式,具体的实现由子类 DefaultBeanDefinitionDocumentReader 完成。
public void registerBeanDefinitions(Document doc, XmlReaderContext readerContext) { // 获得XML描述符 this.readerContext = readerContext; logger.debug("Loading bean definitions");
<span class="token comment">// 获得Document的根元素</span> Element root <span class="token operator">=</span> doc<span class="token punctuation">.</span><span class="token function">getDocumentElement</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// 解析根元素</span> <span class="token function">doRegisterBeanDefinitions</span><span class="token punctuation">(</span>root<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span>
对 Document 对象的解析
从 Document 对象中获取根元素 root,然后调用 doRegisterBeanDefinitions()
开启真正的解析过程。
protected void doRegisterBeanDefinitions(Element root) { BeanDefinitionParserDelegate parent = this.delegate; this.delegate = createDelegate(getReaderContext(), root, parent);
<span class="token comment">// 省略部分代码</span> <span class="token function">preProcessXml</span><span class="token punctuation">(</span>root<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token function">parseBeanDefinitions</span><span class="token punctuation">(</span>root<span class="token punctuation">,</span> <span class="token keyword">this</span><span class="token punctuation">.</span>delegate<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token function">postProcessXml</span><span class="token punctuation">(</span>root<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">this</span><span class="token punctuation">.</span>delegate <span class="token operator">=</span> parent<span class="token punctuation">;</span> <span class="token punctuation">}</span>
preProcessXml()
、postProcessXml()
为前置、后置增强处理,目前 Spring 中都是空实现, parseBeanDefinitions()
是对根元素 root 的解析注册过程。
protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) { // Bean定义的Document对象使用了Spring默认的XML命名空间 if (delegate.isDefaultNamespace(root)) { // 获取Bean定义的Document对象根元素的所有子节点 NodeList nl = root.getChildNodes(); for (int i = 0; i < nl.getLength(); i++) { Node node = nl.item(i); // 获得Document节点是XML元素节点 if (node instanceof Element) { Element ele = (Element) node; // Bean定义的Document的元素节点使用的是Spring默认的XML命名空间 if (delegate.isDefaultNamespace(ele)) { // 使用Spring的Bean规则解析元素节点(默认解析规则) parseDefaultElement(ele, delegate); } else { // 没有使用Spring默认的XML命名空间,则使用用户自定义的解析规则解析元素节点 delegate.parseCustomElement(ele); } } } } else { // Document 的根节点没有使用Spring默认的命名空间,则使用用户自定义的解析规则解析 delegate.parseCustomElement(root); } }
迭代 root 元素的所有子节点,对其进行判断,若节点为默认命名空间,则ID调用
parseDefaultElement()
开启默认标签的解析注册过程,否则调用parseCustomElement()
开启自定义标签的解析注册过程。标签解析
若定义的元素节点使用的是 Spring 默认命名空间,则调用
parseDefaultElement()
进行默认标签解析,如下:private void parseDefaultElement(Element ele, BeanDefinitionParserDelegate delegate) { // 如果元素节点是<Import>导入元素,进行导入解析 if (delegate.nodeNameEquals(ele, IMPORT_ELEMENT)) { importBeanDefinitionResource(ele); } // 如果元素节点是<Alias>别名元素,进行别名解析 else if (delegate.nodeNameEquals(ele, ALIAS_ELEMENT)) { processAliasRegistration(ele); } // 如果元素节点<Bean>元素,则进行Bean解析注册 else if (delegate.nodeNameEquals(ele, BEAN_ELEMENT)) { processBeanDefinition(ele, delegate); }
<span class="token comment">// // 如果元素节点<Beans>元素,则进行Beans解析</span> <span class="token keyword">else</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>delegate<span class="token punctuation">.</span><span class="token function">nodeNameEquals</span><span class="token punctuation">(</span>ele<span class="token punctuation">,</span> NESTED_BEANS_ELEMENT<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token comment">// recurse</span> <span class="token function">doRegisterBeanDefinitions</span><span class="token punctuation">(</span>ele<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span>
对四大标签:import、alias、bean、beans 进行解析,其中 bean 标签的解析为核心工作。关于各个标签的解析过程见如下文章:
- 【死磕Spring】----- IOC 之解析Bean:解析 import 标签
- 【死磕 Spring】—– IOC 之解析 bean 标签:开启解析进程
- 【死磕 Spring】—– IOC 之解析 bean 标签:BeanDefinition)
- 【死磕 Spring】—– IOC 之解析 bean 标签:meta、lookup-method、replace-method)
- 【死磕 Spring】—– IOC 之解析 bean 标签:constructor-arg、property 子元素
- 【死磕 Spring】—– IOC 之解析 bean 标签:解析自定义标签
对于默认标签则由 parseCustomElement()
负责解析。
public BeanDefinition parseCustomElement(Element ele) { return parseCustomElement(ele, null); }
<span class="token keyword">public</span> BeanDefinition <span class="token function">parseCustomElement</span><span class="token punctuation">(</span>Element ele<span class="token punctuation">,</span> <span class="token annotation punctuation">@Nullable</span> BeanDefinition containingBd<span class="token punctuation">)</span> <span class="token punctuation">{</span> String namespaceUri <span class="token operator">=</span> <span class="token function">getNamespaceURI</span><span class="token punctuation">(</span>ele<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>namespaceUri <span class="token operator">==</span> null<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">return</span> null<span class="token punctuation">;</span> <span class="token punctuation">}</span> NamespaceHandler handler <span class="token operator">=</span> <span class="token keyword">this</span><span class="token punctuation">.</span>readerContext<span class="token punctuation">.</span><span class="token function">getNamespaceHandlerResolver</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">resolve</span><span class="token punctuation">(</span>namespaceUri<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>handler <span class="token operator">==</span> null<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token function">error</span><span class="token punctuation">(</span><span class="token string">"Unable to locate Spring NamespaceHandler for XML schema namespace ["</span> <span class="token operator">+</span> namespaceUri <span class="token operator">+</span> <span class="token string">"]"</span><span class="token punctuation">,</span> ele<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">return</span> null<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">return</span> handler<span class="token punctuation">.</span><span class="token function">parse</span><span class="token punctuation">(</span>ele<span class="token punctuation">,</span> <span class="token keyword">new</span> <span class="token class-name">ParserContext</span><span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">.</span>readerContext<span class="token punctuation">,</span> <span class="token keyword">this</span><span class="token punctuation">,</span> containingBd<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span>
获取节点的 namespaceUri,然后根据该 namespaceuri 获取相对应的 Handler,调用 Handler 的 parse()
方法即完成自定义标签的解析和注入。想了解更多参考:【死磕Spring】----- IOC 之解析自定义标签
注册 BeanDefinition
经过上面的解析,则将 Document 对象里面的 Bean 标签解析成了一个个的 BeanDefinition ,下一步则是将这些 BeanDefinition 注册到 IOC 容器中。动作的触发是在解析 Bean 标签完成后,如下:
protected void processBeanDefinition(Element ele, BeanDefinitionParserDelegate delegate) {
BeanDefinitionHolder bdHolder = delegate.parseBeanDefinitionElement(ele);
if (bdHolder != null) {
bdHolder = delegate.decorateBeanDefinitionIfRequired(ele, bdHolder);
try {
// Register the final decorated instance.
BeanDefinitionReaderUtils.registerBeanDefinition(bdHolder, getReaderContext().getRegistry());
}
catch (BeanDefinitionStoreException ex) {
getReaderContext().error("Failed to register bean definition with name '" +
bdHolder.getBeanName() + "'", ele, ex);
}
// Send registration event.
getReaderContext().fireComponentRegistered(new BeanComponentDefinition(bdHolder));
}
}
调用 BeanDefinitionReaderUtils.registerBeanDefinition()
注册,其实这里面也是调用 BeanDefinitionRegistry 的 registerBeanDefinition()
来注册 BeanDefinition ,不过最终的实现是在 DefaultListableBeanFactory 中实现,如下:
@Override public void registerBeanDefinition(String beanName, BeanDefinition beanDefinition) throws BeanDefinitionStoreException {
<span class="token comment">// 省略一堆校验</span> BeanDefinition oldBeanDefinition<span class="token punctuation">;</span> oldBeanDefinition <span class="token operator">=</span> <span class="token keyword">this</span><span class="token punctuation">.</span>beanDefinitionMap<span class="token punctuation">.</span><span class="token function">get</span><span class="token punctuation">(</span>beanName<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// 省略一堆 if</span> <span class="token keyword">this</span><span class="token punctuation">.</span>beanDefinitionMap<span class="token punctuation">.</span><span class="token function">put</span><span class="token punctuation">(</span>beanName<span class="token punctuation">,</span> beanDefinition<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">else</span> <span class="token punctuation">{</span> <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token function">hasBeanCreationStarted</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token comment">// Cannot modify startup-time collection elements anymore (for stable iteration)</span> <span class="token keyword">synchronized</span> <span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">.</span>beanDefinitionMap<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">this</span><span class="token punctuation">.</span>beanDefinitionMap<span class="token punctuation">.</span><span class="token function">put</span><span class="token punctuation">(</span>beanName<span class="token punctuation">,</span> beanDefinition<span class="token punctuation">)</span><span class="token punctuation">;</span> List<span class="token generics function"><span class="token punctuation"><</span>String<span class="token punctuation">></span></span> updatedDefinitions <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">ArrayList</span><span class="token operator"><</span><span class="token operator">></span><span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">.</span>beanDefinitionNames<span class="token punctuation">.</span><span class="token function">size</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">+</span> <span class="token number">1</span><span class="token punctuation">)</span><span class="token punctuation">;</span> updatedDefinitions<span class="token punctuation">.</span><span class="token function">addAll</span><span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">.</span>beanDefinitionNames<span class="token punctuation">)</span><span class="token punctuation">;</span> updatedDefinitions<span class="token punctuation">.</span><span class="token function">add</span><span class="token punctuation">(</span>beanName<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">this</span><span class="token punctuation">.</span>beanDefinitionNames <span class="token operator">=</span> updatedDefinitions<span class="token punctuation">;</span> <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">.</span>manualSingletonNames<span class="token punctuation">.</span><span class="token function">contains</span><span class="token punctuation">(</span>beanName<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> Set<span class="token generics function"><span class="token punctuation"><</span>String<span class="token punctuation">></span></span> updatedSingletons <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">LinkedHashSet</span><span class="token operator"><</span><span class="token operator">></span><span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">.</span>manualSingletonNames<span class="token punctuation">)</span><span class="token punctuation">;</span> updatedSingletons<span class="token punctuation">.</span><span class="token function">remove</span><span class="token punctuation">(</span>beanName<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">this</span><span class="token punctuation">.</span>manualSingletonNames <span class="token operator">=</span> updatedSingletons<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token keyword">else</span> <span class="token punctuation">{</span> <span class="token comment">// Still in startup registration phase</span> <span class="token keyword">this</span><span class="token punctuation">.</span>beanDefinitionMap<span class="token punctuation">.</span><span class="token function">put</span><span class="token punctuation">(</span>beanName<span class="token punctuation">,</span> beanDefinition<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">this</span><span class="token punctuation">.</span>beanDefinitionNames<span class="token punctuation">.</span><span class="token function">add</span><span class="token punctuation">(</span>beanName<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">this</span><span class="token punctuation">.</span>manualSingletonNames<span class="token punctuation">.</span><span class="token function">remove</span><span class="token punctuation">(</span>beanName<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">this</span><span class="token punctuation">.</span>frozenBeanDefinitionNames <span class="token operator">=</span> null<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>oldBeanDefinition <span class="token operator">!=</span> null <span class="token operator">||</span> <span class="token function">containsSingleton</span><span class="token punctuation">(</span>beanName<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token function">resetBeanDefinition</span><span class="token punctuation">(</span>beanName<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span>
这段代码最核心的部分是这句 this.beanDefinitionMap.put(beanName, beanDefinition)
,所以注册过程也不是那么的高大上,就是利用一个 Map 的集合对象来存放,key 是 beanName,value 是 BeanDefinition。
至此,整个 IOC 的初始化过程就已经完成了,从 Bean 资源的定位,转换为 Document 对象,接着对其进行解析,最后注册到 IOC 容器中,都已经完美地完成了。现在 IOC 容器中已经建立了整个 Bean 的配置信息,这些 Bean 可以被检索、使用、维护,他们是控制反转的基础,是后面注入 Bean 的依赖。最后用一张流程图来结束这篇总结之文。
更多阅读:
- 【死磕 Spring】----- IOC 之深入理解 Spring IoC
- 【死磕 Spring】----- IOC 之 Spring 统一资源加载策略
- 【死磕 Spring】----- IOC 之 加载 Bean
- 【死磕 Spring】----- IOC 之 获取验证模型
- 【死磕 Spring】----- IOC 之 获取 Document 对象
- 【死磕 Spring】----- IOC 之 注册 BeanDefinition
- 【死磕Spring】----- IOC 之解析Bean:解析 import 标签
- 【死磕 Spring】—– IOC 之解析 bean 标签:开启解析进程
- 【死磕 Spring】—– IOC 之解析 bean 标签:BeanDefinition)
- 【死磕 Spring】—– IOC 之解析 bean 标签:meta、lookup-method、replace-method)
- 【死磕 Spring】—– IOC 之解析 bean 标签:constructor-arg、property 子元素
- 【死磕 Spring】—– IOC 之解析 bean 标签:解析自定义标签
- 【死磕 Spring】----- IOC 之注册解析的 BeanDefinition
- 【死磕 Spring】----- IOC 之解析自定义标签
</div>
<link href="https://csdnimg.cn/release/phoenix/mdeditor/markdown_views-778f64ae39.css" rel="stylesheet">
</div>
原文出自:http://cmsblogs.com
前面 13 篇博文从源码层次分析了 IOC 整个初始化过程,这篇就这些内容做一个总结将其连贯起来。
在前文提过,IOC 容器的初始化过程分为三步骤:Resource 定位、BeanDefinition 的载入和解析,BeanDefinition 注册。
- Resource 定位。我们一般用外部资源来描述 Bean 对象,所以在初始化 IOC 容器的第一步就是需要定位这个外部资源。
- BeanDefinition 的载入和解析。装载就是 BeanDefinition 的载入。BeanDefinitionReader 读取、解析 Resource 资源,也就是将用户定义的 Bean 表示成 IOC 容器的内部数据结构:BeanDefinition。在 IOC 容器内部维护着一个 BeanDefinition Map 的数据结构,在配置文件中每一个都对应着一个BeanDefinition对象。
- BeanDefinition 注册。向IOC容器注册在第二步解析好的 BeanDefinition,这个过程是通过 BeanDefinitionRegistery 接口来实现的。在 IOC 容器内部其实是将第二个过程解析得到的 BeanDefinition 注入到一个 HashMap 容器中,IOC 容器就是通过这个 HashMap 来维护这些 BeanDefinition 的。在这里需要注意的一点是这个过程并没有完成依赖注入,依赖注册是发生在应用第一次调用
getBean()
向容器索要 Bean 时。当然我们可以通过设置预处理,即对某个 Bean 设置 lazyinit 属性,那么这个 Bean 的依赖注入就会在容器初始化的时候完成。
还记得在博客【死磕 Spring】----- IOC 之 加载 Bean 中提供的一段代码吗?这里我们同样也以这段代码作为我们研究 IOC 初始化过程的开端,如下:
ClassPathResource resource = new ClassPathResource("bean.xml"); DefaultListableBeanFactory factory = new DefaultListableBeanFactory(); XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(factory); reader.loadBeanDefinitions(resource);
刚刚开始的时候可能对上面这几行代码不知道什么意思,现在应该就一目了然了。
ClassPathResource resource = new ClassPathResource("bean.xml");
: 根据 Xml 配置文件创建 Resource 资源对象。ClassPathResource 是 Resource 接口的子类,bean.xml 文件中的内容是我们定义的 Bean 信息。DefaultListableBeanFactory factory = new DefaultListableBeanFactory();
创建一个 BeanFactory。DefaultListableBeanFactory 是 BeanFactory 的一个子类,BeanFactory 作为一个接口,其实它本身是不具有独立使用的功能的,而 DefaultListableBeanFactory 则是真正可以独立使用的 IOC 容器,它是整个 Spring IOC 的始祖,在后续会有专门的文章来分析它。XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(factory);
:创建 XmlBeanDefinitionReader 读取器,用于载入 BeanDefinition 。reader.loadBeanDefinitions(resource);
:开启 Bean 的载入和注册进程,完成后的 Bean 放置在 IOC 容器中。
Resource 定位
Spring 为了解决资源定位的问题,提供了两个接口:Resource、ResourceLoader,其中 Resource 接口是 Spring 统一资源的抽象接口,ResourceLoader 则是 Spring 资源加载的统一抽象。关于Resource、ResourceLoader 的更多知识请关注【死磕 Spring】----- IOC 之 Spring 统一资源加载策略
Resource 资源的定位需要 Resource 和 ResourceLoader 两个接口互相配合,在上面那段代码中 new ClassPathResource("bean.xml")
为我们定义了资源,那么 ResourceLoader 则是在什么时候初始化的呢?看 XmlBeanDefinitionReader 构造方法:
public XmlBeanDefinitionReader(BeanDefinitionRegistry registry) {
super(registry);
}
- 1
- 2
- 3
直接调用父类 AbstractBeanDefinitionReader :
protected AbstractBeanDefinitionReader(BeanDefinitionRegistry registry) { Assert.notNull(registry, "BeanDefinitionRegistry must not be null"); this.registry = registry;
<span class="token comment">// Determine ResourceLoader to use.</span> <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">.</span>registry <span class="token keyword">instanceof</span> <span class="token class-name">ResourceLoader</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">this</span><span class="token punctuation">.</span>resourceLoader <span class="token operator">=</span> <span class="token punctuation">(</span>ResourceLoader<span class="token punctuation">)</span> <span class="token keyword">this</span><span class="token punctuation">.</span>registry<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">else</span> <span class="token punctuation">{</span> <span class="token keyword">this</span><span class="token punctuation">.</span>resourceLoader <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">PathMatchingResourcePatternResolver</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token comment">// Inherit Environment if possible</span> <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">.</span>registry <span class="token keyword">instanceof</span> <span class="token class-name">EnvironmentCapable</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">this</span><span class="token punctuation">.</span>environment <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token punctuation">(</span>EnvironmentCapable<span class="token punctuation">)</span> <span class="token keyword">this</span><span class="token punctuation">.</span>registry<span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">getEnvironment</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">else</span> <span class="token punctuation">{</span> <span class="token keyword">this</span><span class="token punctuation">.</span>environment <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">StandardEnvironment</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span>