通过前面几节的学习,我们对整个bean的生命周期已经有了很清楚的认识,spring中的bean默认是单例的,即ApplicationContext内只创建一次Bean,后面调用返回的都是同一个对象。我们前面实现的内容就是默认的singleton单例模式,这节我们来实现prototype类型的bean作用域。
Bean的作用域
首先来复习下Bean的作用域
1、Singleton 单例;IOC容器仅创建一个Bean实例
2、Prototype 原型、非单例;每次返回的都是一个新的实例
3、request 仅对HTTP请求产生作用,每次HTTP请求都会创建一个新的Bean
4、session 仅用于Http Session,同一个Session共享一个Bean实例,不同Session使用不同的实例。
5、global-session 仅用于Http Session,global即所有的,与session不同的是,所有Session共享一个Bean实例。
前两种作用域是最常用的,后面三种则必须在Web环境下才可以使用。
实现
其实很熟悉前面的代码的话,实现起来很简单,因为单例模式为什么只会创建一次呢,就是我们在创建完Bean后放入了singletonObjects单例容器中,也就是一个map中,下一次再去取的时候每次都从这个map中取,那prototype就是不放入map中,每次getBean都去新建。另外一个不同点就是单例的bean会由spring帮我们执行销毁方法,而prototype类型的bean则交由我们用户去处理,spring不会执行销毁方法。
知道思路后,我们开始实现。首先在BeanDefinition中添加描述bean的作用域的字段scope,并且默认是单例singleton的。
package org.springframework.beans.factory.config;
import org.springframework.beans.PropertyValues;
/**
* BeanDefinition实例保存bean的信息,包括class类型、方法构造参数、bean属性、bean的scope等,此处简化只包含class类型和bean属性
* (省略部分内容)
*/
public class BeanDefinition {
public static String SCOPE_SINGLETON = "singleton";
public static String SCOPE_PROTOTYPE = "prototype";
private String scope = SCOPE_SINGLETON;
private boolean singleton = true;
private boolean prototype = false;
public BeanDefinition(Class beanClass) {
this(beanClass, null);
}
public void setScope(String scope) {
this.scope = scope;
this.singleton = SCOPE_SINGLETON.equals(scope);
this.prototype = SCOPE_PROTOTYPE.equals(scope);
}
public boolean isSingleton() {
return this.singleton;
}
public boolean isPrototype() {
return this.prototype;
}
}
首先我们要修改的地方就是AbstractApplicationContext的refersh()方法,里面最后提前实例化单例Bean,添加判断条件,当是单例的时候才去调用getBean()方法。
接着就是修改doCreateBean()方法,前面的逻辑都不变,在最后创建完Bean后,如果是单例的,才放入singletonObjects中。这样在获取Bean的时候,每次都是新创建Bean。
@Override
public void preInstantiateSingletons() throws BeansException {
beanDefinitionMap.forEach((beanName, beanDefinition) -> {
if(beanDefinition.isSingleton()){
getBean(beanName);
}
});
}
然后我们来完善销毁方法的实现,前面说思路的时候已经提到过了,prototype类型的bean不会执行销毁方法,那么还记得前面讲过销毁方法的内容吧。实现起来也很容易,在doCreateBean()这个方法里面的registerDisposableBeanIfNecessary(beanName, bean, beanDefinition)方法里去做修改,这个方法就是将有销毁方法的bean注册到一个map中,最后再把所有的有销毁方法的bean执行他们的销毁方法,那么我们就在这个方法上去过滤就好了,只有单例的我们才去注册销毁方法,新修改的内容如下:
protected void registerDisposableBeanIfNecessary(String beanName, Object bean, BeanDefinition beanDefinition) {
//只有singleton类型bean会执行销毁方法
if (beanDefinition.isSingleton()) {
if (bean instanceof DisposableBean || StrUtil.isNotEmpty(beanDefinition.getDestroyMethodName())) {
registerDisposableBean(beanName, new DisposableBeanAdapter(bean, beanName, beanDefinition));
}
}
}
测试:
创建propotype作用域的xml对象。
<beans>
<bean id="car" class="org.springframework.test.ioc.bean.Car" scope="prototype">
<property name="brand" value="porsche"/>
</bean>
</beans>
测试方法如下。我们来简单分析下,首先ApplicationContext加载配置文件,因为不是单例的Bean,所以ApplicationContext在加载配置文件的时候Bean没有被创建,接着调用getBean()方法获取到了car1对象,然后再次调用getBean()方法,由于getSingleton(name)会获取不到对象,所以会再次调用createBean()方法,所以这时候的car1,car2是两个不同的对象。
同样的,我们还可以验证下singleton的作用域,将xml文件中的scope修改为singleton,重新运行Test方法,就会收到报错的提醒。
@Test
public void testPrototype() throws Exception {
ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("classpath:prototype-bean.xml");
Car car1 = applicationContext.getBean("car", Car.class);
Car car2 = applicationContext.getBean("car", Car.class);
assertThat(car1 != car2).isTrue();
}
getBean()方法
@Override
public Object getBean(String name) throws BeansException {
Object bean = getSingleton(name);
if (bean != null) {
return bean;
}
BeanDefinition beanDefinition = getBeanDefinition(name);
return createBean(name, beanDefinition);
}
Bean生命周期
好,我们现在再来总结下bean的生命周期,与上一节不同的点是销毁方法的执行。