Spring框架—SpringBean加载过程

论坛 期权论坛     
选择匿名的用户   2021-5-23 20:50   63   0
<blockquote>
<p>原文作者:<a href="https://www.jianshu.com/u/5e87e5d9ab47">RunAlgorithm</a></p>
<p>原文地址:<a href="https://www.jianshu.com/p/9ea61d204559">图文并茂,揭秘 Spring 的 Bean 的加载过程</a></p>
</blockquote>
<p> </p>
<h2>1. 概述</h2>
<p style="text-indent:33px;"><span style="color:#f33b45;">Spring 作为 Ioc 框架,实现了依赖注入,由一个中心化的 Bean 工厂来负责各个 Bean 的实例化和依赖管理</span>。各个 Bean 可以不需要关心各自的复杂的创建过程,达到了很好的解耦效果。我们对 Spring 的工作流进行一个粗略的概括,主要为两大环节:</p>
<ul><li><strong>解析</strong>:读 xml 配置,扫描类文件,从配置或者注解中获取 Bean 的定义信息,注册一些扩展功能。</li><li><strong>加载</strong>:通过解析完的定义信息获取 Bean 实例。</li></ul>
<figure class="image">
<img alt="" height="379" src="https://beijingoptbbs.oss-cn-beijing.aliyuncs.com/cs/5606289-27a11db8ddc5d82b94ebc62346cd35a7.png" width="1200">
<figcaption>
  Spring总体流程
</figcaption>
</figure>
<p> </p>
<p style="text-indent:33px;">我们假设所有的配置和扩展类都已经装载到了 ApplicationContext 中,然后具体的分析一下 Bean 的加载流程。思考一个问题,抛开 Spring 框架的实现,假设我们手头上已经有一套完整的 <span style="color:#f33b45;">Bean Definition Map</span>,然后指定一个 beanName 要进行实例化,需要关心什么?即使我们没有 Spring 框架,也需要了解这两方面的知识:</p>
<ul><li><strong>作用域:</strong>单例作用域或者原型作用域,单例的话需要全局实例化一次,原型每次创建都需要重新实例化。</li><li><strong>依赖关系</strong>:一个 Bean 如果有依赖,我们需要初始化依赖,然后进行关联。如果多个 Bean 之间存在着循环依赖,A 依赖 B,B 依赖 C,C 又依赖 A,需要解这种循环依赖问题。</li></ul>
<p style="text-indent:33px;"><span style="color:#f33b45;">Spring 进行了抽象和封装,使得作用域和依赖关系的配置对开发者透明</span>,我们只需要知道当初在配置里已经明确指定了它的生命周期和依赖了谁,至于是怎么实现的,依赖如何注入,托付给了 Spring 工厂来管理。Spring 只暴露了很简单的接口给调用者,比如 <code>getBean</code> :</p>
<pre class="blockcode"><code>ApplicationContext context &#61; new ClassPathXmlApplicationContext(&#34;hello.xml&#34;);
HelloBean helloBean &#61; (HelloBean) context.getBean(&#34;hello&#34;);
helloBean.sayHello();
</code></pre>
<p style="text-indent:33px;">那我们就从 <code>getBean</code> 方法作为入口,去理解 Spring 加载的流程是怎样的,以及内部对创建信息、作用域、依赖关系等等的处理细节。</p>
<h2>2. 总体流程</h2>
<p> </p>
<figure class="image">
<img alt="" height="1148" src="https://beijingoptbbs.oss-cn-beijing.aliyuncs.com/cs/5606289-4a4bc2a47d58d5293822e842ac37f4bb.png" width="1200">
<figcaption>
  Bean 加载流程图
</figcaption>
</figure>
<p style="text-indent:33px;">上面是跟踪了<span style="color:#f33b45;"> getBean 的调用链创建的流程图</span>,为了能够很好地理解 Bean 加载流程,省略一些异常、日志和分支处理和一些特殊条件的判断。从上面的流程图中,可以看到一个 Bean 加载会经历这么几个阶段(用绿色标记):</p>
<ul><li><strong>获取 BeanName</strong>:对传入的 name 进行解析,转化为可以从 Map 中获取到 BeanDefinition 的 bean name。</li><li><strong>合并 Bean 定义</strong>:对父类的定义进行合并和覆盖,如果父类还有父类,会进行递归合并,以获取完整的 Bean 定义信息。</li><li><strong>实例化</strong>:使用构造或者工厂方法创建 Bean 实例。</li><li><strong>属性填充</strong>:寻找并且注入依赖,依赖的 Bean 还会递归调用 <code>getBean</code> 方法获取。</li><li><strong>初始化</strong>:调用自定义的初始化方法。</li><li><strong>获取最终的 Bean</strong>:如果是 FactoryBean 需要调用 getObject 方法,如果需要类型转换调用 TypeConverter 进行转化。</li></ul>
<p style="text-indent:33px;">整个流程最为复杂的是对循环依赖的解决方案,后续会进行重点分析。</p>
<h2>3. 细节分析</h2>
<h3>3.1. 转化 BeanName</h3>
<p style="text-indent:33px;">而在我们<span style="color:#f33b45;">解析完配置后创建的 Map,使用的是 beanName 作为 key</span>。见 DefaultListableBeanFactory:</p>
<pre class="blockcode"><code class="language-java">/** Map of bean definition objects, keyed by bean name */
private final Map&lt;String, BeanDefinition&gt; beanDefinitionMap &#61; new ConcurrentHashMap&lt;String, BeanDefinition&gt;(256);
</code></pre>
<p><code>BeanFactory.getBean</code> 中传入的 name,有可能是这几
分享到 :
0 人收藏
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

积分:3875789
帖子:775174
精华:0
期权论坛 期权论坛
发布
内容

下载期权论坛手机APP