参考小傅哥的教程:第10章:对象作用域和FactoryBean | 小傅哥 bugstack 虫洞栈
本期目标是加入对象作用域与FactoryBean。
首先对这两部分进行一个初步理解:
对象作用域:分为singleton和prototype,即单例模式和原型模式。
FactoryBean:注意是FactoryBean,不是BeanFactory。从字面上理解,这是一个承担工厂作用的Bean。而工厂是做什么的,返回一个我们需要的对象。因此,FactoryBean就像是一层中介、一层包装,将我们所需要的类的工厂交给Spring管理,再通过工厂来获取所需要的类。
大家可能会问,为什么这么干,这么干不是有病吗?我也是这么想的,直到我参考了一个博主的文章究竟FactoryBean是什么?深入理解Spring的工厂神器-CSDN博客
目前,我们创建Bean的方式还是通过XML设置配置文件,之后Spring读取相关配置文件,从而创建Bean。但是如果我们所需要的Bean特别复杂,XML配置文件就不好写。因此,为了解决这个问题,我们可以做一个FactoryBean,在FactoryBean之中进行我们所需要的对象的复杂初始化操作。最后再通过FactoryBean中的get方法得到我们所需要的Bean对象。
实现
Bean的作用范围
BeanDefinition中包含了对象的所有元数据,因此我们需要在BeanDefiniton添加字段,用来表征我们需要这个对象的单例模式还是原型模式。
public class BeanDefinition { String SCOPE_SINGLETON = ConfigurableBeanFactory.SCOPE_SINGLETON; String SCOPE_PROTOTYPE = ConfigurableBeanFactory.SCOPE_PROTOTYPE; private Class beanClass; private PropertyValues propertyValues; private String initMethodName; private String destroyMethodName; private String scope = SCOPE_SINGLETON; private boolean singleton = true; private boolean prototype = false; //在xml注册Bean定义时,通过scope字段来判断是单例还是原型 public void setScope(String scope) { this.scope = scope; this.singleton = SCOPE_SINGLETON.equals(scope); this.prototype = SCOPE_PROTOTYPE.equals(scope); } // ...get/set }这一块首先定义了两个常量String,用于表示singleton和prototype。之后定义scope字段,和boolean类型的singleton和prototype。注意,scope需要在XML配置文件中设置,有且仅有两个值:singleton和prototype。
之后,使用XML方式读取配置信息时,还需要补充上对scope字段的读取,并设置BeanDefinition的相关字段值。这里就不做展示了,具体可以参考文章开头给出的小傅哥的教程。
创建和修改对象时判断单例
单例模式和原型模式的区别就在于是否存放到内存中,如果是原型模式那么就不会存放到内存中,每次获取都重新创建对象,另外非 Singleton 类型的 Bean 不需要执行销毁方法。
public abstract class AbstractAutowireCapableBeanFactory extends AbstractBeanFactory implements AutowireCapableBeanFactory { private InstantiationStrategy instantiationStrategy = new CglibSubclassingInstantiationStrategy(); @Override protected Object createBean(String beanName, BeanDefinition beanDefinition, Object[] args) throws BeansException { Object bean = null; try { bean = createBeanInstance(beanDefinition, beanName, args); // 给 Bean 填充属性 applyPropertyValues(beanName, bean, beanDefinition); // 执行 Bean 的初始化方法和 BeanPostProcessor 的前置和后置处理方法 bean = initializeBean(beanName, bean, beanDefinition); } catch (Exception e) { throw new BeansException("Instantiation of bean failed", e); } // 注册实现了 DisposableBean 接口的 Bean 对象 registerDisposableBeanIfNecessary(beanName, bean, beanDefinition); // 判断 SCOPE_SINGLETON、SCOPE_PROTOTYPE if (beanDefinition.isSingleton()) { addSingleton(beanName, bean); } return bean; } protected void registerDisposableBeanIfNecessary(String beanName, Object bean, BeanDefinition beanDefinition) { // 非 Singleton 类型的 Bean 不执行销毁方法 if (!beanDefinition.isSingleton()) return; if (bean instanceof DisposableBean || StrUtil.isNotEmpty(beanDefinition.getDestroyMethodName())) { registerDisposableBean(beanName, new DisposableBeanAdapter(bean, beanName, beanDefinition)); } } // ... 其他功能 }注意,这一部分的代码修改了两处。
1. 第一处是,判断该对象是否为单例模式,如果是单例则加入单例池。这里也很好理解,加入单例池之后,之后还要使用这个对象就直接从单例池中获取了。如果是原型模式,则不加入单例池,每次需要就重新创建。
2. 第二处是,原型模式下不执行销毁方法。
至此,就完成了Bean的作用范围功能。接下来需要完成FactoryBean功能。
定义FactoryBean接口
public interface FactoryBean<T> { /** 获取对象 */ T getObject() throws Exception; /** 获取对象类型 */ Class<?> getObjectType(); /** 判断单例 */ boolean isSingleton(); }注意,具体方法的实现交给用户,用户觉得哪个类比较复杂,需要使用FactoryBean包装,就去实现这个接口,实现这3个方法。
实现FactoryBean注册服务
/** * 支持 FactoryBean 的注册中心抽象基类。 * 在 DefaultSingletonBeanRegistry 的基础上,额外缓存由 FactoryBean 创建的单例对象。 */ public abstract class FactoryBeanRegistrySupport extends DefaultSingletonBeanRegistry { /** * 缓存由 FactoryBean 创建的单例对象: * key = FactoryBean 的 beanName * value = FactoryBean.getObject() 返回的对象(可能为 NULL_OBJECT 标记) * 注意这个是FactoryBean的缓存池 */ private final Map<String, Object> factoryBeanObjectCache = new ConcurrentHashMap<String, Object>(); /** * 从缓存中快速获取指定 FactoryBean 生成的对象。 * @param beanName FactoryBean 的 beanName * @return 缓存中的对象;若缓存的是 NULL_OBJECT 标记则返回 null */ protected Object getCachedObjectForFactoryBean(String beanName) { Object object = this.factoryBeanObjectCache.get(beanName); return (object != NULL_OBJECT ? object : null); } /** * 获取 FactoryBean 创建的对象(考虑单例/原型作用域)。 * 单例对象会被缓存,后续调用直接取缓存;原型每次新建。 * @param factory FactoryBean 实例 * @param beanName FactoryBean 的 beanName * @return FactoryBean.getObject() 得到的对象 */ protected Object getObjectFromFactoryBean(FactoryBean factory, String beanName) { if (factory.isSingleton()) { // 单例:先查缓存 Object object = this.factoryBeanObjectCache.get(beanName); if (object == null) { // 缓存未命中,真正创建 object = doGetObjectFromFactoryBean(factory, beanName); // 放入缓存:若创建结果为 null,用 NULL_OBJECT 标记占位(ConcurrentHashMap 不允许 null) this.factoryBeanObjectCache.put(beanName, (object != null ? object : NULL_OBJECT)); } // 将 NULL_OBJECT 标记还原为 null 返回 return (object != NULL_OBJECT ? object : null); } else { // 原型:每次都新建,不缓存 return doGetObjectFromFactoryBean(factory, beanName); } } /** * 真正调用 FactoryBean.getObject() 创建对象。 * 任何异常都被包装为 BeansException 向上抛出。 */ private Object doGetObjectFromFactoryBean(final FactoryBean factory, final String beanName) { try { return factory.getObject(); } catch (Exception e) { throw new BeansException("FactoryBean threw exception on object[" + beanName + "] creation", e); } } }这一部分代码的核心是建立了一个缓存池factoryBeanObjectCache。注意,由FactoryBean创建的单例对象实际上是交给FactoryBean管理的,将单例对象存在了缓存池factoryBeanObjectCache中。这一块的思路就类似于查询缓存,如果缓存中有我们所需要的对象就直接返回,没有的话就创建一个对象,再把这个对象加入缓存池当中。
而由于这里使用的ConcurrentHashMap,不允许使用null作为key,因此我们人为定义一个NULL_OBJECT,将其等同于null。
拓展AbstractBeanFactory创建对象逻辑
public abstract class AbstractBeanFactory extends FactoryBeanRegistrySupport implements ConfigurableBeanFactory { // 继承自DefaultSingletonBeanRegistry类, 用于实现单例 // 更新后: 继承自FactoryBeanRegistrySupport, 兼容FactoryBean // 实现BeanFactory接口, 用于实现getBean方法 // 采用模板模式, 每一个类仅关注自己的实现 /** * 由于有两个重载的getBean方法,因此将共同部分抽出来形成一个方法 * 具体逻辑也很简单:尝试从单例Map中获取对象,获取不到再考虑创建 * @param name * @param args * @return * @param <T> */ protected <T> T doGetBean(final String name, final Object[] args) { Object sharedInstance = getSingleton(name); // 单例池中有这个对象 if (sharedInstance != null) { // 根据这个对象是否是FactoryBean类型做进一步判断 return (T) getObjectForBeanInstance(sharedInstance, name); } // 单例池中没有这个对象, 就创建 BeanDefinition beanDefinition = getBeanDefinition(name); // 如果对象非单例, createBean之后并不会加入单例池, 那么每一次去单例池中都查不到这个对象, 每一次都会新建 Object bean = createBean(name, beanDefinition, args); return (T) getObjectForBeanInstance(bean, name); } private Object getObjectForBeanInstance(Object beanInstance, String beanName) { if (!(beanInstance instanceof FactoryBean)) { // 如果不是 FactoryBean, 直接返回该对象 return beanInstance; } // 如果是 FactoryBean,则需要调用 FactoryBean#getObject // 该方法内部调用getObject Object object = getCachedObjectForFactoryBean(beanName); if (object == null) { FactoryBean<?> factoryBean = (FactoryBean<?>) beanInstance; object = getObjectFromFactoryBean(factoryBean, beanName); } return object; } // ... }分析这一块代码之前,我们首先需要明确:
FactoryBean本质上就是一个Bean,它会被Spring管理,它会存在于单例池中(如果是单例模式),因此加入FactoryBean后,我们getBean就需要判断一下,先看看单例池中有没有。
单例池中有,执行getObjectForBeanInstance方法,先判断单例池中放的是FactoryBean对象还是我们所需要的对象,如果对象对应的类没有实现FactoryBean接口,那么单例池中获取到的就是我们需要的Bean,直接返回即可。如果对象对应的类实现了FactoryBean接口,那么单例池中拿到的就是FactoryBean对象,我再从FactoryBean的缓存池中当中拿我们真正所需要的对象。
单例池中没有,我们就先根据BeanDefiniton创建出Bean(这里创建Bean会在方法内部判断是否为单例),之后就是同样的操作,看这个Bean是不是实现了FactoryBean接口,然后根据结果不同,进行不同的操作。
总结
FactoryBean 本身会被注册到BeanFactory 的一级单例池(singletonObjects)里,由 DefaultSingletonBeanRegistry 正常管理;
FactoryBean 的 getObject() 创建出来的对象才只被factoryBeanObjectCache这张表缓存,不会进入一级单例池。
原型(prototype)作用域的 Bean 永远不会进入任何“池”