背景

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

  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 了。

mermaid 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 这件事情:

```java 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 反射了。