有关Spring加载bean系列,今天这是最后一篇了,主要接上篇对于从Spring容器中获取Bean的一些细节实现的补充。
《Spring读书笔记——bean加载》——Spring如何加载消化一个xml配置文件
《Spring读书笔记——bean解析》——Spring如何将xml文件的各种标签转换为BeanDefinition并注册到Spring容器下
《Spring读书笔记——bean创建(上)》——概述Spring如何从容器中取出需要的那个Bean
从缓存中加载单例
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
Object singletonObject = this.singletonObjects.get(beanName);
if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
synchronized (this.singletonObjects) {
singletonObject = this.earlySingletonObjects.get(beanName);
if (singletonObject == null && allowEarlyReference) {
ObjectFactory singletonFactory = this.singletonFactories.get(beanName);
if (singletonFactory != null) {
singletonObject = singletonFactory.getObject();
this.earlySingletonObjects.put(beanName, singletonObject);
this.singletonFactories.remove(beanName);
}
}
}
}
return (singletonObject != NULL_OBJECT ? singletonObject : null);
}
在看这段代码之前,我们先了解下Spring对于单例bean出现循环依赖的解决方法。如果出现上面我们介绍的A->B->C->A的情况,那是不是说Spring就无能为力了,显然Spring没有那么弱。那么Spring是怎么做的?
鉴于单例bean的循环依赖问题,Spring创建bean的原则是不等bean创建完成就会将bean的ObjectFactory提前曝光加入到缓存中,一旦有某个bean创建时需要依赖这个bean了,那么就可以直接使用ObjectFactory。
简单说,创建bean的时候,就是打包快递发货,主管为了知道你今天要派发多少个包裹,为了节省大家时间以及以免统计漏掉的情况。你可以先拿出一个包裹箱子,上面写上要寄收件人、收货地址、联系方式等等,但是这时候还没有往里面打包真正的快递。
这里曝光的bean就相当于这个快递箱子。
好了,知道了这个原则之后,我们就好理解代码了。
首先从singletonObjects中获取实例,取不到则从earlySingletonObjects中获取,仍然取不到,我们还可以到singletonFactories中获取相应的ObjectFactory,在调用这个ObjectFactory的getObject方法来创建bean。
然后将其加入到earlySingletonObjects中,在将其从singletonFactories中删除。
想必,你已经被这些用来存储和删除的集合搞疯了,没关系,我们来理一下:
- singletonObjects
/** Cache of singleton objects: bean name --> bean instance */
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<String, Object>(64);
用于保存BeanName和创建bean实例之间的关系。
- singletonFactories:
/** Cache of singleton factories: bean name --> ObjectFactory */
private final Map<String, ObjectFactory> singletonFactories = new HashMap<String, ObjectFactory>(16);
用于保存BeanName和创建bean的工厂之间的关系
- earlySingletonObjects:
/** Cache of early singleton objects: bean name --> bean instance */
private final Map<String, Object> earlySingletonObjects = new HashMap<String, Object>(16);
用于保存BeanName和创建bean的工厂之间的关系,与singletonObjects的区别是当一个bean被放入这个集合后,可以用于其他bean做循环依赖检查
bean实例化
我们从缓存中拿到bean之后,就需要根据bean的不同类型做不同的处理,返回相应的bean,实现这个功能的就是getObjectForBeanInstance方法
protected Object getObjectForBeanInstance(
Object beanInstance, String name, String beanName, RootBeanDefinition mbd) {
// Don't let calling code try to dereference the factory if the bean isn't a factory.
if (BeanFactoryUtils.isFactoryDereference(name) && !(beanInstance instanceof FactoryBean)) {
throw new BeanIsNotAFactoryException(transformedBeanName(name), beanInstance.getClass());
}
// Now we have the bean instance, which may be a normal bean or a FactoryBean.
// If it's a FactoryBean, we use it to create a bean instance, unless the
// caller actually wants a reference to the factory.
if (!(beanInstance instanceof FactoryBean) || BeanFactoryUtils.isFactoryDereference(name)) {
return beanInstance;
}
Object object = null;
if (mbd == null) {
object = getCachedObjectForFactoryBean(beanName);
}
if (object == null) {
// Return bean instance from factory.
FactoryBean<?> factory = (FactoryBean<?>) beanInstance;
// Caches object obtained from FactoryBean if it is a singleton.
if (mbd == null && containsBeanDefinition(beanName)) {
mbd = getMergedLocalBeanDefinition(beanName);
}
boolean synthetic = (mbd != null && mbd.isSynthetic());
object = getObjectFromFactoryBean(factory, beanName, !synthetic);
}
return object;
}
- 首先检测指定的beanName是否是工厂bean相关,如果既不是工厂bean又是以"&"开头,则校验失败,抛出异常
- 如果这个bean不是工厂Bean(FactoryBean),那么就直接返回bean实例
- 剩下代码就是处理FactoryBean,我们顺着这样的顺序依次来到getObjectForBeanInstance->getObjectFromFactoryBean->doGetObjectFromFactoryBean
private Object doGetObjectFromFactoryBean(
final FactoryBean factory, final String beanName, final boolean shouldPostProcess)
throws BeanCreationException {
Object object;
try {
if (System.getSecurityManager() != null) {
AccessControlContext acc = getAccessControlContext();
try {
object = AccessController.doPrivileged(new PrivilegedExceptionAction<Object>() {
public Object run() throws Exception {
return factory.getObject();
}
}, acc);
}
catch (PrivilegedActionException pae) {
throw pae.getException();
}
}
else {
object = factory.getObject();
}
}
catch (FactoryBeanNotInitializedException ex) {
throw new BeanCurrentlyInCreationException(beanName, ex.toString());
}
catch (Throwable ex) {
throw new BeanCreationException(beanName, "FactoryBean threw exception on object creation", ex);
}
// Do not accept a null value for a FactoryBean that's not fully
// initialized yet: Many FactoryBeans just return null then.
if (object == null && isSingletonCurrentlyInCreation(beanName)) {
throw new BeanCurrentlyInCreationException(
beanName, "FactoryBean which is currently in creation returned null from getObject");
}
if (object != null && shouldPostProcess) {
try {
object = postProcessObjectFromFactoryBean(object, beanName);
}
catch (Throwable ex) {
throw new BeanCreationException(beanName, "Post-processing of the FactoryBean's object failed", ex);
}
}
return object;
}
这么长的代码,如果嫌累,就只看factory.getObject()这行就好,这诠释了FactoryBean的加载时通过factory.getObject的方式获取到对应的bean实例的。
如何创建单例bean
在上篇的doGetBean方法中,如果从缓存中加载不到,那么我们就需要老老实实的从头开始加载bean了,对于单例bean的加载就都在这里实现了
// Create bean instance.
if (mbd.isSingleton()) {
sharedInstance = getSingleton(beanName, new ObjectFactory<Object>() {
public Object getObject() throws BeansException {
try {
return createBean(beanName, mbd, args);
}
catch (BeansException ex) {
// Explicitly remove instance from singleton cache: It might have been put there
// eagerly by the creation process, to allow for circular reference resolution.
// Also remove any beans that received a temporary reference to the bean.
destroySingleton(beanName);
throw ex;
}
}
});
bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);
}
创建bean
我们从AbstractBeanFactory的createBean方法来到了AbstractAutowiredCapableBeanFactory的createbean方法,而真正的创建bean其实在doCreateBean方法中
protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final Object[] args) {
// Instantiate the bean.
BeanWrapper instanceWrapper = null;
if (mbd.isSingleton()) {
instanceWrapper = this.factoryBeanInstanceCache.remove(beanName);
}
if (instanceWrapper == null) {
instanceWrapper = createBeanInstance(beanName, mbd, args);
}
final Object bean = (instanceWrapper != null ? instanceWrapper.getWrappedInstance() : null);
Class beanType = (instanceWrapper != null ? instanceWrapper.getWrappedClass() : null);
// Allow post-processors to modify the merged bean definition.
synchronized (mbd.postProcessingLock) {
if (!mbd.postProcessed) {
applyMergedBeanDefinitionPostProcessors(mbd, beanType, beanName);
mbd.postProcessed = true;
}
}
// Eagerly cache singletons to be able to resolve circular references
// even when triggered by lifecycle interfaces like BeanFactoryAware.
boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences &&
isSingletonCurrentlyInCreation(beanName));
if (earlySingletonExposure) {
if (logger.isDebugEnabled()) {
logger.debug("Eagerly caching bean '" + beanName +
"' to allow for resolving potential circular references");
}
addSingletonFactory(beanName, new ObjectFactory() {
public Object getObject() throws BeansException {
return getEarlyBeanReference(beanName, mbd, bean);
}
});
}
// Initialize the bean instance.
Object exposedObject = bean;
try {
populateBean(beanName, mbd, instanceWrapper);
if (exposedObject != null) {
exposedObject = initializeBean(beanName, exposedObject, mbd);
}
}
catch (Throwable ex) {
if (ex instanceof BeanCreationException && beanName.equals(((BeanCreationException) ex).getBeanName())) {
throw (BeanCreationException) ex;
}
else {
throw new BeanCreationException(mbd.getResourceDescription(), beanName, "Initialization of bean failed", ex);
}
}
if (earlySingletonExposure) {
Object earlySingletonReference = getSingleton(beanName, false);
if (earlySingletonReference != null) {
if (exposedObject == bean) {
exposedObject = earlySingletonReference;
}
else if (!this.allowRawInjectionDespiteWrapping && hasDependentBean(beanName)) {
String[] dependentBeans = getDependentBeans(beanName);
Set<String> actualDependentBeans = new LinkedHashSet<String>(dependentBeans.length);
for (String dependentBean : dependentBeans) {
if (!removeSingletonIfCreatedForTypeCheckOnly(dependentBean)) {
actualDependentBeans.add(dependentBean);
}
}
if (!actualDependentBeans.isEmpty()) {
throw new BeanCurrentlyInCreationException(beanName,
"Bean with name '" + beanName + "' has been injected into other beans [" +
StringUtils.collectionToCommaDelimitedString(actualDependentBeans) +
"] in its raw version as part of a circular reference, but has eventually been " +
"wrapped. This means that said other beans do not use the final version of the " +
"bean. This is often the result of over-eager type matching - consider using " +
"'getBeanNamesOfType' with the 'allowEagerInit' flag turned off, for example.");
}
}
}
}
// Register bean as disposable.
try {
registerDisposableBeanIfNecessary(beanName, bean, mbd);
}
catch (BeanDefinitionValidationException ex) {
throw new BeanCreationException(mbd.getResourceDescription(), beanName, "Invalid destruction signature", ex);
}
return exposedObject;
}
- 如果是单例Bean,那么首先是从factoryBeanInstanceCache中清除该beanName对应的记录
- 实例化bean,将BeanDefinition转换为BeanWrapper对象
- bean合并后的处理
- 解决循环依赖问题
- 属性填充,将所有属性填充到bean的实例中
这个方法,本身不算长,但是层层深入就会发现其下面包罗了创建bean的诸多繁杂的细节(这块自己看的也是云里雾里,就不往下延伸扩展了)。
虽然对于Spring加载bean,我只写了四篇,但是其内部实现远比我表述的要复杂的多。
看源码确实很煎熬,对于目前看不懂的地方要么多看几遍,要么先跳过。阅读代码的过程中要懂得取舍,对于非重点部分比如日志或者异常处理可以先忽略,沿着一条主线往下看,最主要是先弄懂代码的只要意图。
Spring的bean加载代码量虽然巨大,但是思路还是比较清晰的,我们知道Spring如何加载xml然后解析xml,再到如何把xml的元素转为自己的BeanDefinition,最后又是如何取出对应的beanName然后返回一个bean实例供容器使用的。
网上有一位大神用一张图就把整个过程画出来了
注:图片来源http://blog.csdn.net/zghwaicsdn/article/details/50910384
如果您觉得阅读本文对您有帮助,请点一下“推荐”按钮,您的“推荐”将是我最大的写作动力!如果您想持续关注我的文章,请扫描二维码,关注JackieZheng的微信公众号,我会将我的文章推送给您,并和您一起分享我日常阅读过的优质文章。
Spring读书笔记——bean创建(下)的更多相关文章
-
Spring读书笔记——bean创建(上)
通过<Spring读书笔记--bean加载>和<Spring读书笔记--bean解析>,我们明白了两件事. Spring如何加载消化一个xml配置文件 Spring如何将xml ...
-
Spring读书笔记——bean解析
前情回顾 上篇<Spring读书笔记--bean加载>我们从代码角度介绍了有哪些类负责解析XML文件,又是如何一步步从XML格式脱变成我们熟悉的bean的,直到DefaultBeanDef ...
-
Spring读书笔记——bean加载
我们的日常开发几乎离不开Spring,他为我们的开发带来了很大的便捷,那么Spring框架是如何做到方便他人的呢.今天就来说说bean如何被加载加载. 我们在xml文件中写过太多类似这样的bean声明 ...
-
(转) Spring读书笔记-----Spring的Bean之配置依赖
前一篇博客介绍了Spring中的Bean的基本概念和作用域(Spring读书笔记-----Spring的Bean之Bean的基本概念),现在介绍Spring Bean的基本配置. 从开始我们知道Jav ...
-
Spring读书笔记-----Spring的Bean之Bean的基本概念
从前面我们知道Spring其实就是一个大型的工厂,而Spring容器中的Bean就是该工厂的产品.对于Spring容器能够生产那些产品,则取决于配置文件中配置. 对于我们而言,我们使用Spring框架 ...
-
(转)Spring读书笔记-----Spring的Bean之Bean的基本概念
从前面我们知道Spring其实就是一个大型的工厂,而Spring容器中的Bean就是该工厂的产品.对于Spring容器能够生产那些产品,则取决于配置文件中配置. 对于我们而言,我们使用Spring框架 ...
-
Spring读书笔记-----Spring的Bean之设置Bean值
[java] view plaincopyprint? Java实例的属性值可以有很多种数据类型.基本类型值.字符串类型.java实例甚至其他的Bean实例.java集合.数组等.所以Spring允许 ...
-
Spring 读书笔记-----使用Spring容器(一)
pring有两个核心接口:BeanFactory和ApplicationContext,其中ApplicationContext是BeanFactory的子接口.他们都可代表Spring容器,Spri ...
-
(转) Spring读书笔记-----部署我的第一个Spring项目
一.Spring介绍 Spring是一个轻量级的Java EE容器,它也是一种从实际需求出发,着眼于轻便,灵活,易于开发,易测试和易部署的轻量级开发框架.Spring它完成了大量开发中的通用步骤,留给 ...
随机推荐
-
Noise Contrastive Estimation
Notes from Notes on Noise Contrastive Estimation and Negative Sampling one sample: \[x_i \to [y_i^0, ...
-
PHP网页
1.安装YUM源 2.安装httpd与PHP yum install httpd -y yum install php -y 3.进入htmi文件中 cd /var/www/html/ 4.将自己编写 ...
-
关于leetcode中链表中两数据相加的程序说明
* Definition for singly-linked list. * struct ListNode { * int val; * struct ListNode *next; * }; */ ...
-
Quartus 编译错误
1.Error (10028): Can't resolve multiple constant drivers for net ** 这种错误一般是由于定义的reg寄存器在多个always中进行赋值 ...
-
HDOJ 2012 素数判定
Problem Description 对于表达式n^2+n+41,当n在(x,y)范围内取整数值时(包括x,y)(-39<=x < y<=50),判定该表达式的值是否都为素数. I ...
-
Spark2.0编译
Spark2.0编译 1 前言 Spark2.0正式版于今天正式发布,本文基于CDH5.0.2的Spark编译. 2 编译步骤 #2.1 下载源码 wget https://github.com/ap ...
-
HTML——超文本标记语言
HTML: 网站(站点) - 网页 1.HTML作为文件后缀名,可以把文件变为网页 2.HTML是一门编程语言的名字:超文本标记语言 3.超越了文字的范畴,除了文字还可以有图片.视频.音频.动画特效等 ...
-
Oracle中特殊的变量类型
1.%TYPE 允许用户动态地将数据库中某一列的数据类型与PL/SQL中某个变量关联.语法如下: variable_name table.column%TYPE 2.%ROWTYPE 允许用户定义 ...
-
转:winform 安装包(很详细)
winform安装项目.安装包的制作.部署 ① 1,解决方案—添加—新建项目—其他项目类型—安装和部署—安装项目,输入名称Setup,点“确定” 2,添加主输出:单击左侧“应用程序文件夹”,右侧右键— ...
-
.NET面试题系列(一)基本概念
什么是CLR CLR常用简写词语,CLR是公共语言运行库(Common Language Runtime)和Java虚拟机一样也是一个运行时环境,它负责资源管理(内存分配和垃圾收集等),并保证应用和底 ...