01 Prototype Pattern

背景

在写代码时,我们也许会碰到这些情景:

  1. 在进行一次数据库查询后,我们成功地创建出了一个实体,而此时,我们需要从同一个数据源以同样方法取得另一个实体,如果已知其他人并未对数据源进行任何写入操作,那么此时最好的办法就是不经过数据源,我们直接复制已经查询到的数据。这样一来就能避免再次占用昂贵的数据库链接资源,并避免高耗时的数据库查询请求。
  2. 或者,在千辛万苦经过了多次递归反射后,我们成功分析了一个类,并得到了它的解析结果对应的对象。这时如果我们需要去使用这个解析对应的对象,最好的办法就是将这个对象作为一个原型,直接复制其中的各个属性,从而跳过极其耗时的 Java 反射过程,快速生成解析结果对象。
  3. 又或者,作为一个程序员,我们在对 Excel 表的其中一个格子进行了各种 style 的装饰,虽然我们很享受这个美化的过程,但是在美化结束后,如果需要我们去对第二个格子,或是另一个 Excel 文件中的另一个相同 style 的格子重新赋值一次,我们会感到相当痛苦。这个时候最好的方法就是去通过之前已经美化完成的格子复制一次,之后只需稍微改一下格子中的文字就好啦。

因此,Prototype Pattern 就应运而生了,作为创建模式之一,它可以省去创建过程中的一些昂贵的消耗,并以简单无脑的复制(clone)代替。

分析

下面是它的 UML 图:

Prototype UML

它本质上是进行了一次克隆的操作,借用已经有的实体,进行一次克隆,从而创建出一个一模一样的实体。UML 图非常简单,但是平常我们一般不会将其单独使用,而往往作为一种辅助工具,实现快速无误地创建一个新对象的作用。

例子

在 Spring Boot 中,我们有一个类叫做 BeanDefinition,这个类所对应的对象,都和 Bean 的创建有关,可以说是一个非常核心的类了。例如:

  • 在 Spring 的核心方法 org.springframework.beans.factory.support.AbstractBeanFactory#doGetBean 中,当我们需要去新创建一个 Bean 对象供程序使用的时候,我们首先需要解析 class,生成对应的 BeanDefinition 对象 mbd (RootBeanDefinition) 。

    对于那些不了解 mdb 的小伙伴们:mdb 是一个 BeanDefinition 对象,其中包含了许多在配置时期所需要指定的该 Bean 的属性 - 例如 isPrototype, isSingleton, resolvedTargetType 等等等等。

    而 mdb 将会参加下面这个重要的流水线:

    我们通过 xml 文件或者 Java annotation 去设置这个 Bean 的一些特性,比如它对应的 class,它是否是单例。之后我们会创建一个 MutablePropertyValues 对象,将之前的配置变成一个 key-value 的 map。而这个 map 中,key 是 Bean Definition 中的属性的 Java 字段名称,value 是我们所指定的值。再成功生成 MutablePropertyValues 之后,我们通过 new RootBeanDefinition(className, null, mutablePropertyValues) 的方式生成 beanDefinition,并将它注入到ApplicationContext 里。这样一来,下次需要 Bean 的时候,我们就只需要去执行 applicationContext.getBean(className) 就可以让 beanFactory 这个工厂根据 beanDefinition 的定义来生成对应的 bean Object 了。

    flowchart LR
      1(xml/Java annotation) -->|文件解析和赋值| 2(MutablePropertyValues - a map)
      2 -->|构造 BeanDefinition 实例| 3(beanDefinition)
      3 -->|注入容器| 4(application context)
      4 -->|getBean| 5(bean Object)
    

    在这之中,由于 xml/Java annotation 解析涉及到相当耗时的文件解析和 Java 反射过程,且在后面的 MutablePropertyValues -> beanDefinition 的过程中也需要使用 Java 反射技术去对 beanDefinition 进行赋值操作。因此在 beanDefinition 生成之前的耗时是非常可观的。聪明的你可能发现了,这是可以避免的,因为我们的配置文件并不会改变。当然 Spring 也不笨,在它的源码中,对于每个类,它只会生成一次 beanDefinition 对象,在那之后,它都会使用 org.springframework.beans.factory.support.RootBeanDefinition#RootBeanDefinition(org.springframework.beans.factory.support.RootBeanDefinition) 这个方法将 RootBeanDefinition 复制一遍,进行使用。

    有人可能就问了,这个是一个 constructor 方法呀,我们讲的 Prototype Pattern 不应该是使用 clone 来实现的吗?别急,请你仔细看看这个方法的实现,它其实就是干了 clone 这件事情:

    public class RootBeanDefinition extends AbstractBeanDefinition {
        ...
        public RootBeanDefinition(RootBeanDefinition original) {
        super(original);
        this.decoratedDefinition = original.decoratedDefinition;
        this.qualifiedElement = original.qualifiedElement;
        this.allowCaching = original.allowCaching;
        this.isFactoryMethodUnique = original.isFactoryMethodUnique;
        this.targetType = original.targetType;
        this.factoryMethodToIntrospect = original.factoryMethodToIntrospect;
      }
        ...
    }
    
    public abstract class AbstractBeanDefinition extends BeanMetadataAttributeAccessor implements BeanDefinition, Cloneable {
        ...
        protected AbstractBeanDefinition(BeanDefinition original) {
        setParentName(original.getParentName());
        setBeanClassName(original.getBeanClassName());
        setScope(original.getScope());
        setAbstract(original.isAbstract());
        setFactoryBeanName(original.getFactoryBeanName());
        setFactoryMethodName(original.getFactoryMethodName());
        setRole(original.getRole());
        setSource(original.getSource());
        copyAttributesFrom(original);
    
        if (original instanceof AbstractBeanDefinition) {
          AbstractBeanDefinition originalAbd = (AbstractBeanDefinition) original;
          if (originalAbd.hasBeanClass()) {
            setBeanClass(originalAbd.getBeanClass());
          }
          if (originalAbd.hasConstructorArgumentValues()) {
            setConstructorArgumentValues(new ConstructorArgumentValues(original.getConstructorArgumentValues()));
          }
          if (originalAbd.hasPropertyValues()) {
            setPropertyValues(new MutablePropertyValues(original.getPropertyValues()));
          }
          if (originalAbd.hasMethodOverrides()) {
            setMethodOverrides(new MethodOverrides(originalAbd.getMethodOverrides()));
          }
          Boolean lazyInit = originalAbd.getLazyInit();
          if (lazyInit != null) {
            setLazyInit(lazyInit);
          }
          setAutowireMode(originalAbd.getAutowireMode());
          setDependencyCheck(originalAbd.getDependencyCheck());
          setDependsOn(originalAbd.getDependsOn());
          setAutowireCandidate(originalAbd.isAutowireCandidate());
          setPrimary(originalAbd.isPrimary());
          copyQualifiersFrom(originalAbd);
          setInstanceSupplier(originalAbd.getInstanceSupplier());
          setNonPublicAccessAllowed(originalAbd.isNonPublicAccessAllowed());
          setLenientConstructorResolution(originalAbd.isLenientConstructorResolution());
          setInitMethodName(originalAbd.getInitMethodName());
          setEnforceInitMethod(originalAbd.isEnforceInitMethod());
          setDestroyMethodName(originalAbd.getDestroyMethodName());
          setEnforceDestroyMethod(originalAbd.isEnforceDestroyMethod());
          setSynthetic(originalAbd.isSynthetic());
          setResource(originalAbd.getResource());
        }
        else {
          setConstructorArgumentValues(new ConstructorArgumentValues(original.getConstructorArgumentValues()));
          setPropertyValues(new MutablePropertyValues(original.getPropertyValues()));
          setLazyInit(original.isLazyInit());
          setResourceDescription(original.getResourceDescription());
        }
      }
        ...
    }
    

    可见,这么一来,开销确实小了很多,不需要解析文件,也不需要 Java 反射了。