8、Mini-spring 增加bean的prototype作用域

89 阅读4分钟

通过前面几节的学习,我们对整个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的生命周期,与上一节不同的点是销毁方法的执行。

image.png