在之前的文章中说过lazy、scope对spring ioc容器的初始化影响(Spring中Lazy、Scope注解对IOC容器Bean初始化的影响分析),但是没有具体深入的说,而是通过表面数据判断。另外就是之前也写过spring ioc容器初始化的源码分析(Spring IOC容器初始化基础过程源码了解),这里算是前两篇文章的续集,也是对其补充和做一个总结性的结论。
1. 情景简介
这里就直接进入主题,分别使用@Lazy
和@Scope("prototype")
来修饰两个bean,不会再去做其他的解释,如果这些有不太清晰的,之前博客有写过(点这里),文章内简称博客一。另外在说源码的时候,不会对内部代码全说且加注释,主在理解主干逻辑。这个源码分析会结合之前的博客一起(点这里),文章内简称博客二。
2. 源码分析
2.1 实例代码
配置类中创建bean的方法:
//配置类
@Configuration
public class AnnotationInit {
// 单例非懒加载bean
@Bean("singletonBean")
public Person person() {
return new Person("战斗猿singleton", 29);
}
//非单例bean
@Bean("prototypeBean")
@Scope("prototype")
public Person prototypePerson() {
return new Person("战斗猿prototype", 30);
}
//单例懒加载bean
@Bean("lazyBean")
@Lazy
public Person lazyPerson() {
return new Person("战斗猿lazy", 31);
}
}
测试类中初始化IOC容器代码:
@Test
public void testContext() {
ApplicationContext app = new AnnotationConfigApplicationContext(AnnotationInit.class);
Object lazyBean = app.getBean("prototypeBean");
System.out.println(lazyBean);
}
在通过ApplicationContext
的getBean
方法中获取bean实例,bean信息的存储都是在beanFactory
中,来看看IOC完成初始化后beanFactory
的信息。
beanFacotry
内的成员变量信息:
从这个信息可以看出来,被定义到IOC容器中的实例个数是12个,但是真正已经被创建的却只有10个。
beanDefinitionMap
内bean的定义信息:
在配置类中需要创建的bean都定义到了beanFactory
中的beanDefinitionMap
集合内。
alreadyCreated
内创建完成的bean实例信息:
这里只有单例的bean创建完成并加载进去了,另外的懒加载和非单例bean并没有初始化并加载进来。
2.2 getBean()源码
这里getBean源码在博客二中都说过,这里直接进入内部的doGetBean()
方法,在AbstractBeanFactory
类中。
protected <T> T doGetBean(
final String name, final Class<T> requiredType, final Object[] args, boolean typeCheckOnly)
throws BeansException {
final String beanName = transformedBeanName(name);
Object bean;
// Eagerly check singleton cache for manually registered singletons.
// 代码已:这里是根据beanName获取bean实例
Object sharedInstance = getSingleton(beanName);
if (sharedInstance != null && args == null) {
// ……
} else {
// ……
if (!typeCheckOnly) {
markBeanAsCreated(beanName);
}
try {
// 获取bean的定义信息
final RootBeanDefinition mbd = getMergedLocalBeanDefinition(beanName);
checkMergedBeanDefinition(mbd, beanName, args);
// ……
// Create bean instance.
// 创建bean,会做是否为单例bean的判断
if (mbd.isSingleton()) {
sharedInstance = getSingleton(beanName, new ObjectFactory<Object>() {
@Override
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);
} else if (mbd.isPrototype()) { // 非单例创建逻辑
// It's a prototype -> create a new instance.
Object prototypeInstance = null;
try {
beforePrototypeCreation(beanName);
prototypeInstance = createBean(beanName, mbd, args);
}
finally {
afterPrototypeCreation(beanName);
}
bean = getObjectForBeanInstance(prototypeInstance, name, beanName, mbd);
}else {
// ……
}
}
catch (BeansException ex) {
cleanupAfterBeanCreationFailure(beanName);
throw ex;
}
}
// ……
return (T) bean;
}
mbd.isSingleton()
对是否为单例做了判断,当是单例bean的时候,执行的流程就是创建bean,然后将bean加入的IOC容器中,在博客二中都说明清楚了。这里需要说明的是,在博客二中我们说的是只做单例非懒加载bean的创建。
//判断逻辑,spring4源代码DefaultListableBeanFactory类中733行,在博客二中也有对这里进行说明
if(!bd.isAbstract() && bd.isSingleton() && !bd.isLazyInit()){
……
}
当这里通过主动调用getBean
,如果单例bean没有创建,还是走到同样的单例bean创建代码块。可见只要是单例bean的创建,不论是懒加载还是非懒加载,最终都会通过同样的逻辑创建并放到IOC容器中,只是时机不同,一个是在容器初始化的时候,一个是在bean被需要使用获取的时候。
到这里@Lazy
修饰的bean创建时机就很好理解啦。
同样是上面的代码,mbd.isPrototype()
做了是否为非单例的判断,如果判断是单例走的就是这一块代码,代码的内部就不去详细的说了,这里只是做bean的创建,然后将bean返回,并没有将bean存储到容器中。
到这里@Scope("prototype")
修饰的bean创建时机就很好理解啦。
3. 最终总结
3.1 总结项
- 在
ApplictionContext
的构造方法中,通过refresh()
方法,做IOC整体的初始化,另外将单例非懒加载bean的创建并加入到IOC容器中。 - 在使用
getBean
方法获取单例懒加载bean的时候,和非懒加载bean的逻辑相同,最终创建好的bean也会被放到IOC容器中。 - 在使用
getBean
方法获取非单例bean的时候,只会通过另外的逻辑创建bean,并将bean返回给调用方,最终创建好的bean是不会放到IOC容器中的,每次获取都是新创建的bean。
3.2 图文解释
用图来解释一下,也许会更清晰一点。
非单例是虚线部分,看出来,就是绕过了一个getSingleton
方法的封装,而是直接调用createBean
,为什么绕过,因为在getSingleton
封装了向IOC容器里面加入bean的逻辑,而非单例,是每次创建都是最新的。