一、引言
先来看一个案例,来引出咱们今天的主题。
spring.xml 文件配置了两个bean
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd"
>
<bean id="user" class="com.kk.entity.User" scope="prototype"/>
<bean id="userService" class="com.kk.service.UserService" parent="user">
</bean>
</beans>
测试类
public static void main(String[] args) {
// xml配置,用这个容器启动
ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");
System.out.println(applicationContext.getBean("userService"));
System.out.println(applicationContext.getBean("userService"));
System.out.println(applicationContext.getBean("userService"));
}
打印结果
com.kk.service.UserService@62e136d3
com.kk.service.UserService@c8e4bb0
com.kk.service.UserService@6279cee3
需要思考问题:
1、通过结果我们可以很明显的看出,userService三个对象都是不同的,原因是因为在spring.xml中配置的userService,并且parent="user"。 而其中user的配置scope=prototype,所以导致userService也继承了该属性。
那么第一个需要思考的问题就是,在spring中是如何实现这个功能的?
2、当我们在xml中配置了一个bean,这些bean的信息在spring容器中是如何存储的?
二、什么是BeanDefinition
问题1:bean的信息在spring中是如何存储的?
org.springframework.beans.factory.config包下有一个BeanDefinition接口,在spring中,我们一般都简称bd。
那这个是做什么用的呢? 它里面就会有一些方法,是专门用来保存和获取我们所配置的bean信息。
比如我们看其中的一些方法:
public interface BeanDefinition extends AttributeAccessor, BeanMetadataElement {
/**
* parentName表示父BeanDefinition的名字
*/
void setParentName(@Nullable String parentName);
/**
* 返回父BeanDefinition的名字
*/
@Nullable
String getParentName();
/**
* 设置beanClass名字
* @param beanClassName
*/
void setBeanClassName(@Nullable String beanClassName);
/**
* 获取设置beanClass名字
* @return
*/
@Nullable
String getBeanClassName();
/**
* 设置bean的scope,是单例还是原型
* @param scope
*/
void setScope(@Nullable String scope);
/**
* 获取scope
* @return
*/
@Nullable
String getScope();
/**
* 设置是否懒加载
* @param lazyInit
*/
void setLazyInit(boolean lazyInit);
}
还有很多方法,小编就不一一展示了,小伙伴们可以去源码自行查看。
那么这些方法被谁实现了呢?
最开始,看到了org.springframework.beans.factory.support包中AbstractBeanDefinition提供了一些基础的实现逻辑,再这个基础之上然后又派生出两个不同的实现类。
GenericBeanDefinition 和 RootBeanDefinition有什么区别?
GenericBeanDefinition:在spring启动之后,会通过扫描的方式来进行获取程序员所定义的类信息,然后原封不动的保存到这个类型的BeanDefinition当中。
RootBeanDefinition:在有了原始信息的BeanDefinition之后,可能会由于某些bean会继承其他的bean信息,就会有一个合并的操作,合并之后的BeanDefinition就是RootBeanDefinition。
多说不练假把式,以userService为例,我们来看看在spring扫描之后,得到的BeanDefinition是什么样子的。
这个beanDefinitionMap是在DefaultListableBeanFactory中的一个属性,BeanFactory是spring的核心,其中就包括单例池等等一些重要属性。
通过图中所示,userService类型是GenericBeanDefinition的,并且scope为空,和我们在xml中配置一致。
三、BeanDefinition的合并操作
问题2:UserServcie是如何实现创建多个对象的?
在spring中,BeanDefinition合并之后的类型就变成了RootBeanDefinition,在DefaultListableBeanFactory中有个属性是mergedBeanDefinitions,专门用来保存合并之后的BeanDefinition。
在spring创建bean对象的时候,是根据合并后的BeanDefinition来进行创建的,在这里UserServcie的父类是User,User的scope是prototype,所以合并之后的UserServcie的BeanDefinition的scope也就是prototype。
所以在创建的时候,当然就不会是单例模式,最终就会创建多个。
合并的源码分析:
源码路径:org.springframework.beans.factory.support.AbstractBeanFactory中getMergedBeanDefinition()方法,主要逻辑都在这个里面。(小编也是一步一步找到这里的,步骤有点多,小编就直接跳到核心了)
// beanName = userService
// bd 原始的BeanDefinition 也就是GenericBeanDefinition这个类型的
// 第三个参数传入的null
protected RootBeanDefinition getMergedBeanDefinition(
String beanName, BeanDefinition bd, @Nullable BeanDefinition containingBd)
throws BeanDefinitionStoreException {
synchronized (this.mergedBeanDefinitions) {
RootBeanDefinition mbd = null;
RootBeanDefinition previous = null;
// Check with full lock now in order to enforce the same merged instance.
if (containingBd == null) {
mbd = this.mergedBeanDefinitions.get(beanName);
}
if (mbd == null || mbd.stale) {
previous = mbd;
// 如果bd的父bd为空
if (bd.getParentName() == null) {
// 如果ParentName就说明当前的bd是没有父类的
// 并且如果类型是RootBeanDefinition,直接clone
if (bd instanceof RootBeanDefinition) {
mbd = ((RootBeanDefinition) bd).cloneBeanDefinition();
}
else {
// 否则把当前bd属性(自己本身)作为基础,创建一个RootBeanDefinition的bd
mbd = new RootBeanDefinition(bd);
}
}
else {
// 否则,就说明当前bd它有父类
BeanDefinition pbd;
try {
// 先获取父bd的beanName
String parentBeanName = transformedBeanName(bd.getParentName());
if (!beanName.equals(parentBeanName)) {
// 递归调用,获取最终的父bd
// 这里递归调用的原因是因为,父bd自己本身也有父bd,所以要递归调用,一只获取到最顶层的bd
pbd = getMergedBeanDefinition(parentBeanName);
}
else {
BeanFactory parent = getParentBeanFactory();
if (parent instanceof ConfigurableBeanFactory) {
pbd = ((ConfigurableBeanFactory) parent).getMergedBeanDefinition(parentBeanName);
}
else {
throw new NoSuchBeanDefinitionException(parentBeanName,
"Parent name '" + parentBeanName + "' is equal to bean name '" + beanName +
"': cannot be resolved without an AbstractBeanFactory parent");
}
}
}
catch (NoSuchBeanDefinitionException ex) {
throw new BeanDefinitionStoreException(bd.getResourceDescription(), beanName,
"Could not resolve parent bean definition '" + bd.getParentName() + "'", ex);
}
// pbd 是找出最顶级的父bd
// 根据最顶级的父bd来创建一个RootBeanDefinition
mbd = new RootBeanDefinition(pbd);
// db 是传入的最原始的bd,也就是GenericBeanDefinition类型的
// 把bd的属性设置给mbd, 而mbd是基于pbd来的,pbd是parent,bd是当前bd,把当前bd的属性设置给pbd就是合并(合并属性)
mbd.overrideFrom(bd);
}
if (!StringUtils.hasLength(mbd.getScope())) {
mbd.setScope(SCOPE_SINGLETON);
}
if (containingBd != null && !containingBd.isSingleton() && mbd.isSingleton()) {
mbd.setScope(containingBd.getScope());
}
// 合并完成后就放入到mergedBeanDefinitions集合当中
if (containingBd == null && isCacheBeanMetadata()) {
this.mergedBeanDefinitions.put(beanName, mbd);
}
}
if (previous != null) {
copyRelevantMergedBeanDefinitionCaches(previous, mbd);
}
return mbd;
}
}
四、最后总结
问题1:bean的配置信息,在spring中如何存储的?
是由BeanDefinition存储的,bd又分为几种不同的类型。第一种GenericBeanDefinition 存储最原始的bean配置信息 ,第二种 RootBeanDefinition 合并之后的类型,如果当前bd有父bd,就会合并成rootbd类型,如果没有父bd,也会把自己本身封装一个root类型的bd。
问题2: 为什么userService会基础user的scope属性?
原因就是因为bd的合并操作,以父bd的配置信息为基础创建了root类型的bd,然后再把自己本身的属性覆盖父bd的属性。
没看懂的小伙伴,你细品、细品、细细品。