面试题:原型Bean在一个线程多次获取是否一样?

结论

对于原型Bean,IoC容器并没有进行缓存,所以每次调用getBean方法都是重新创建Bean,与线程无关。

代码测试

编写一个类,添加上@Scope注解,然后指定其value为prototype。创建注解驱动应用上下文,调用register方法来注册编写的类的class,通过应用上下文的getBean(Class)方法来获取应用上下文创建好的Bean实例。

package com.xxx.fame.prototype;

import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Scope;

@Scope(ConfigurableListableBeanFactory.SCOPE_PROTOTYPE)
public class PrototypeBeanDemo {

    public static void main(String[] args) {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
        context.register(PrototypeBeanDemo.class);
        context.refresh();
        PrototypeBeanDemo prototypeBeanDemo = context.getBean(PrototypeBeanDemo.class);
        PrototypeBeanDemo prototypeBeanDemo2 = context.getBean(PrototypeBeanDemo.class);
        System.out.println("第一次获取到的 PrototypeBeanDemo 实例为:" + prototypeBeanDemo);
        System.out.println("第二次获取到的 PrototypeBeanDemo 实例为:" + prototypeBeanDemo2);
    }
}

运行结果:
在这里插入图片描述
可以看到每次通过getBean(Class)方法获取到的实例都是不一样的。

之所以会出现这种问题,是因为IoC容器的doCreateBean方法所决定的。doCreateBean方法由Abstr-actBeanFactory类实现,在该方法中,首先根据beanName调用getSingleton方法(底层是去IoC容器中的一级缓存、二级缓存、三级缓存查找),因为原型Bean是不会被缓存的,所以这里获取到的为空。

接下来根据beanName去获取BeanDefinition数据,然后根据获取到的BeanDefinition的isSingleton方法来判断是否是单例的,由于我们指定Scope为原型(Prototype),这里判断失败,接下来调用BeanD-efinition的isPrototype方法来判断是否是一个原型Bean,判断成立。

首先调用beforePrototypeCreation方法来标记该beanName正在创建(因为Bean的创建是一个很耗时的操作,所以需要提前进行标记),接下来就调用createBean方法真正地进行Bean创建,在finally块中。看上去创建原型Bean和创建单例Bean没什么差别,最终都是调用createBean方法,问题就出在getSingleton方法上。

// AbstractBeanFactory#doGetBean
protected <T> T doGetBean(final String name, @Nullable final Class<T> requiredType,
			@Nullable final Object[] args, boolean typeCheckOnly) throws BeansException {

		final String beanName = transformedBeanName(name);
		Object bean;

		// Eagerly check singleton cache for manually registered singletons.
		Object sharedInstance = getSingleton(beanName);
		if (sharedInstance != null && args == null) {
			// 只保留和本次分析相关代码,省略其它代码...
		} else {
			// 只保留和本次分析相关代码,省略其它代码...
			try {
				final RootBeanDefinition mbd = getMergedLocalBeanDefinition(beanName);
				checkMergedBeanDefinition(mbd, beanName, args);
				// 只保留和本次分析相关代码,省略其它代码...
				// 创建单例Bean
				if (mbd.isSingleton()) {
					sharedInstance = getSingleton(beanName, () -> {
						try {
							return createBean(beanName, mbd, args);
						} catch (BeansException ex) {
							destroySingleton(beanName);
							throw ex;
						}
					});
					bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);
				} else if (mbd.isPrototype()) {
					// 创建原型Bean
					Object prototypeInstance = null;
					try {
						// 标记指定beanName正在创建
						beforePrototypeCreation(beanName);
						prototypeInstance = createBean(beanName, mbd, args);
					} finally {
						// 移除在beforePrototypeCreation方法中对beanName的标记
						afterPrototypeCreation(beanName);
					}
					bean = getObjectForBeanInstance(prototypeInstance, name, beanName, mbd);
				} 
				// 只保留和本次分析相关代码,省略其它代码...
		return (T) bean;
	}

getSingleton方法由DefaultSingletonBeanRegistry类定义并实现,在该实现中,首先对一级缓存-sin-gletonObject进行加锁,然后再次根据beanName从一级缓存中获取(典型的Double Check),如果获取到的结果还是等于null,调用beforeSingletonCreation方法来标记该beanName正在进行创建,在try代码块中调用传入的ObjectFactory实例的getObject方法,由于使用的是Lambda表达式,所以实际调用的还是createBean方法。

注意接下来的将newSingleton标志位设置为true,这点非常重要,因为在方法的最后就是根据判断该标志位是否为true,来决定要不要调用addSingleton方法。

// DefaultSingletonBeanRegistry#getSingleton
public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) {
   Assert.notNull(beanName, "Bean name must not be null");
   synchronized (this.singletonObjects) {
      Object singletonObject = this.singletonObjects.get(beanName);
	  // Double Check,双重检查
      if (singletonObject == null) {
         if (this.singletonsCurrentlyInDestruction) {
            throw new BeanCreationNotAllowedException(beanName,
                  "Singleton bean creation not allowed while singletons of this factory are in destruction " +
                  "(Do not request a bean from a BeanFactory in a destroy method implementation!)");
         }
         if (logger.isDebugEnabled()) {
            logger.debug("Creating shared instance of singleton bean '" + beanName + "'");
         }
		 // 调用 beforeSingletonCreation 方法标记该beanName 正在创建
         beforeSingletonCreation(beanName);
         boolean newSingleton = false;
         boolean recordSuppressedExceptions = (this.suppressedExceptions == null);
         if (recordSuppressedExceptions) {
            this.suppressedExceptions = new LinkedHashSet<>();
         }
         try {
			// 调用传入的ObjectFactory实例的getObject方法,这里使用的Lambda表达式,实际调用的是createBean方法
            singletonObject = singletonFactory.getObject();
			// 注意这里将newSingleton标志位设置为true,这点非常重要,因为IoC容器就是根据该
			// 标志位来决定是否将bean添加到一级缓存中。
            newSingleton = true;
         } catch (IllegalStateException ex) {
            // Has the singleton object implicitly appeared in the meantime ->
            // if yes, proceed with it since the exception indicates that state.
            singletonObject = this.singletonObjects.get(beanName);
            if (singletonObject == null) {
               throw ex;
            }
         } catch (BeanCreationException ex) {
            if (recordSuppressedExceptions) {
               for (Exception suppressedException : this.suppressedExceptions) {
                  ex.addRelatedCause(suppressedException);
               }
            }
            throw ex;
         } finally {
            if (recordSuppressedExceptions) {
               this.suppressedExceptions = null;
            }
            afterSingletonCreation(beanName);
         }
		// 如果newSingleton标志位为true,则调用addSingleton方法
         if (newSingleton) {
            addSingleton(beanName, singletonObject);
         }
      }
      return singletonObject;
   }
}

addSinglton方法同样由DefaultSingletonBeanRegistry定义并实现,该实现非常简单,就是将创建好的bean实例保存到singletonObjects中,并从singletonFactories(三级缓存)以及earlySingletonObject-s(二级缓存)中移除,最后在registeredSingletons集合也保存一份beanName。

// DefaultSingletonBeanRegistry#addSingleton
protected void addSingleton(String beanName, Object singletonObject) {
   synchronized (this.singletonObjects) {
      this.singletonObjects.put(beanName, singletonObject);
      this.singletonFactories.remove(beanName);
      this.earlySingletonObjects.remove(beanName);
      this.registeredSingletons.add(beanName);
   }
}

总结

在一个线程内多次获取原型Bean,每次返回的Bean实例都是不同的,之所以这样是因为对于原型Bean,IoC容器是不会进行存储的,和线程没有任何关系。

这样的设计也是合理的,因为每次请求原型Bean时IoC容器都需要创建,如果还保留其引用,那么因为强引用而导致垃圾回收器无法进行GC(可达性分析算法),从而导致内存泄漏,另外既然每次请求都需要创建,IoC容器保存对象引用的意义又何在呢?

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值