Annotation Processing 101

原文链接:annotation processing 101

本文主要描述如何写一个 注解处理器( Annotation Processor),包含两个部分:

  • 解释什么是 annotation processing,以及你能用它来做些什么;
  • 一步一步实现简单的注解处理器.

背景

需要澄清一件非常重要的事:我们并不是讨论 运行时(run time) 利用反射读取注解,而是 编译时(compile time) 发生的 annotation processing.

Annotation processing 是 javac 中的内置工具,可以在编译时扫描和处理注解。你可以为自己实现的注解注册自己的注解处理器。如果你对注解还不是很清楚的话,推荐你先看看官方文档 Annotations. Annotation processing 在 Java 5 面世,但直到 Java 6 官方才释出可用的 API。

用于某些注解的注解处理器以 java 代码(或编译后的字节码)为输入,生成文件(通常为 .java 文件)作为输出。这啥意思呢?这意味着你可以生成 java 代码!生成的 java 代码在一个 .java 文件中,它可以像其他手写的 java 文件一样被 javac 编译。

AbstractProcessor

先看下 Processor 的 API。每个 Processor 需要继承自 AbstractProcessor

package com.zac4j.processor;

public class MyProcessor extends AbstractProcessor {
  @Override
  public synchronized void init(ProcessingEnvironment env) { }
  @Override
  public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment env) { }
  @Override
  public Set<String> getSupportedAnnotationTypes() { }
  @Override
  public SourceVersion getSupportedSourceVersion() { }
}

参数说明:

Method Return Type Parameter Type Description
init void ProcessingEnvironment env 以 ProcessingEnvironment 为参数的 init() 方法可以被 annotation processing 工具调用,ProcessingEnvironment 提供一些有用的工具类,如 Elements, Types, Filer 等。
process boolean Set<? extends TypeElement> annotations, RoundEnvironment env process() 是每个处理器类的 main() 方法。这里需要写扫描,读取,处理注解以及生成 java 文件。RoundEnvironment 参数用于查询某些注解注释的元素。
getSupportedAnnotationTypes Set<String> - 指定注解处理器可以处理的注解类型
getSupportedSourceVersion SourceVersion - 指定使用的 Java 版本

在 Java 7 上可以使用注解代替复写 getSupportedAnnotationType()getSupportedSourceVersion()

@SupportedSourceVersion(SourceVersion.latestSupported())
@SupportedAnnotationTypes({
 // Set of full qualified annotation type names 
})
public class MyProcessor extends AbstractProcessor {
@Override
  public synchronized void init(ProcessingEnvironment env) { }
  @Override
  public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment env) { }
}

出于兼容性考虑,尤其对于 Android 平台,推荐使用复写而非注解。

接下来你需要知道的是注解处理器在它自己的 JVM 里运行。javac 启动完整的 JVM 来运行注解处理器。这意味着你可以像写其他任何 Java 程序那样,可以使用 Guava!或者是一些依赖注入工具,比如 Dagger!但即使是一个小的注解处理程序,你还是需要注意算法的效率以及设计模式的利用。

注册处理器

如何向 javac 注册 MyProcessor 呢?你需要提供一个 .jar 文件。一个包装(编译)了你的注解处理器的 .jar 文件,同时在 .jar 文件的 META-INF/services 下需要有 javax.annotation.processing.Processor 文件。文件目录结构类似:

MyProcessor.jar
├── com
│   └── zac4j
│       └── MyProcessor.class
└── META-INF
    └── services
        └── javax.annotation.processing.Processor

javax.annotation.processing.Processor 文件包含了一系列注解处理器:

com.zac4j.processor.FactoryProcessor

工厂模式 Factory Pattern

有这样一种场景:我们需要实现一个 Pizza Store,这个 Pizza Store 供应2种 Pizza ("Margherita" 和 "Calzone") 以及甜品 Tiramisu。先看下面这种实现:

public interface Meal {
  public float getPrice();
}

public class MargheritaPizza implements Meal {
  @Override public float getPrice() {
    return 6.0f;
  }
}

public class CalzonePizza implements Meal {
  @Override public float getPrice() {
    return 8.5f;
  }
}

public class Tiramisu implements Meal {
  @Override public float getPrice() {
    return 4.5f;
  }
}

Pizza Store 订购产品需要输入食品的名称:

public class PizzaStore {
  public Meal order(String mealName) {

    if (mealName == null) {
      throw new IllegalArgumentException("Name of the meal is null!");
    }

    if ("Margherita".equals(mealName)) {
      return new MargheritaPizza();
    }

    if ("Calzone".equals(mealName)) {
      return new CalzonePizza();
    }

    if ("Tiramisu".equals(mealName)) {
      return new Tiramisu();
    }

    throw new IllegalArgumentException("Unknown meal '" + mealName + "'");
  }

  public static void main(String... args) throws IOException {
    PizzaStore pizzaStore = new PizzaStore();
    Meal meal = pizzaStore.order(readConsole());
    System.out.println("Bill: ¥" + meal.getPrice());
  }
}

可以看到在 order() 方法里有很多 if 语句,每当我们添加一种食品就要添加一条 if 语句。不过,我们可以使用 annotation processing工厂类模式 生成这些 if 语句。Pizza Store 看起来会像这样:

public class PizzaStore {
  private MealFactory factory = new MealFactory();

  public Meal order(String mealName) {
    return factory.create(mealName);
  }

  public static void main(String... args) throws IOException {
    PizzaStore pizzaStore = new PizzaStore();
    Meal meal = pizzaStore.order(readConsole());
    System.out.println("Bill: ¥" + meal.getPrice());
  }
}

MealFactory 像这样:

public class MealFactory {

  public Meal create(String id) {
    if (id == null) {
      throw new IllegalArgumentException("id is null!");
    }

    if ("Calzone".equals(id)) {
      return new CalzonePizza();
    }

    if ("Tiramisu".equals(id)) {
      return new Tiramisu();
    }

    if ("Margherita".equals(id)) {
      return new MargheritaPizza();
    }

    throw new IllegalArgumentException("Unknown id = " + id);
  }
}

@Factory 注解

为了生成工厂类,我们需要一个 factory 注解和一个 processor。

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.CLASS)
public @interface Factory {
  // 工厂类的名称
  Class type();
  // 商品的标识符
  String id();
}

属于同一工厂类处理的食品,我们使用相同的 type()id()方法用于将 Calzone 映射到 CalzonePizza 类。添加注解后的各种食品:

@Factory(
    id = "Margherita",
    type = Meal.class
)
public class MargheritaPizza implements Meal {

  @Override public float getPrice() {
    return 6f;
  }
}
@Factory(
    id = "Calzone",
    type = Meal.class
)
public class CalzonePizza implements Meal {

  @Override public float getPrice() {
    return 8.5f;
  }
}
@Factory(
    id = "Tiramisu",
    type = Meal.class
)
public class Tiramisu implements Meal {

  @Override public float getPrice() {
    return 4.5f;
  }
}

在进行 processor 之前,我们需要约定如下规则:

  • 只有实现类可以添加 @Factory 注解,因为接口或者抽象类无法通过 new 操作符实例化。
  • 添加了 @Factory 注解的类必须提供一个(默认)参数为空的构造方法。
  • 添加了 @Factory 注解的类必须直接或间接继承定义的 type 类或接口(本例中为 Meal.class 接口)。
  • 添加了@Factory 注解,拥有相同的 type 类型的类将会被聚合到同一工厂类,生成的类将以 Factory 为后缀,本例中会生成 MealFactory 类。
  • 添加了@Factory 注解的类的 id 必须是唯一的 String 类型。

Processor

本例中所有的代码参见 Anna。我们先从 FactoryProcessor 类的 Skeleton 说起:

@AutoService(Processor.class)
public class FactoryProcessor extends AbstractProcessor {

  private Types typeUtils;
  private Elements elementUtils;
  private Filer filer;
  private Messager messager;
  private Map<String, FactoryGroupedClasses> factoryClasses = new LinkedHashMap<String, FactoryGroupedClasses>();

  @Override
  public synchronized void init(ProcessingEnvironment processingEnv) {
    super.init(processingEnv);
    typeUtils = processingEnv.getTypeUtils();
    elementUtils = processingEnv.getElementUtils();
    filer = processingEnv.getFiler();
    messager = processingEnv.getMessager();
  }

  @Override
  public Set<String> getSupportedAnnotationTypes() {
    Set<String> annotataions = new LinkedHashSet<String>();
    annotataions.add(Factory.class.getCanonicalName());
    return annotataions;
  }

  @Override
  public SourceVersion getSupportedSourceVersion() {
    return SourceVersion.latestSupported();
  }

  @Override
  public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
    ...
  }
}

第一行 @AutoService(Processor.class),我们使用了 Google 的 auto-service 帮助我们生成 META-INF/services/javax.annotation.processing.Processor 文件。

Elements 和 TypeMirrors

init() 方法,我们获取了这些引用:

  • Elements:与 Element 类相关的 utils 类。
  • Types:与 TypeMirror 类相关的 utils 类。
  • Filer:生成文件的帮助类

在 annotation processing 过程中,我们扫描的 java 源码的每个部分都是 Element,例如:

package com.zac4j;  // PackageElement

public class Foo {  // TypeElement
  private int a;  // VariableElement
  private Foo other;  // VariableElement
  public Foo() {}  // ExecutableElement
  public void setA(    // ExecutableElement
                   int newA  // TypeElement
                   ) {}
}

你需要改变以前看代码的方式,它们只是结构化的文本。对于 public class Foo 这样的 TypeElement 我们可以使用如下方式获取它的 Element:

TypeElement fooClass = ...;
for (Element e : fooClass.getEnclosedElements()) {
  Element parent = e.getEnclosingElement();  // parent == fooClass
}

TypeElement 只能获取当前这个类的 Element 信息,对于它的超类,我们需要通过 TypeMirror 获取。访问一个 ElementTypeMirror 可以通过 element.asType() 方法。

搜索 @Factory

我们来一步一步实现 process() 方法。首先我们获取检索到的使用 @Factory 注解的类:

@Override
public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
  // Iterate over all @Factory annotated elements
  for (Element annotatedElement : roundEnvironment.getElementsAnnotatedWith(Factory.class)) {
  ...
  }
}

roundEnvironment.getElementsAnnotatedWith(Factory.class) 返回使用 @Factory 注解的 Element 集合。Element 可以为类,方法,变量等。因此下一步我们检查 Element 是否为类:

@Override
public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
  // 遍历所有使用 @Factory 注解的元素
  for (Element annotatedElement : roundEnvironment.getElementsAnnotatedWith(Factory.class)) {
    // 检查 Element 是否为 class
    if (annotatedElement.getKind() != ElementKind.CLASS) {
      ...
    }
  }
}

我们之前提到过类属于 TypeElement。为何我们不使用 if (!(annotatedElement instanceof TypeElement))判断呢?因为接口也属于 TypeElement。因此 annotation processing 过程中需要避免使用 instanceof 关键字,而是用 ElementKindTypeKind

错误处理 Error Handling

init() 方法中,我们获取了 Messager 的引用。Messager 提供给注解处理器汇报错误信息、警告和其他通知的方式。它并不是 LoggerMessager 用于向使用注解处理器的第三方开发人员提供 Diagnostic 的写入,官方文档里有不同级别 Diagnostic 的说明。
最重要的 Diagnostic 是 Kind.ERROR,它的出现意味着我们的注解处理器遇到了错误,例如第三方开发人员误用了 @Factory 注解(在接口中使用 @Factory)。这种概念与传统的 Java 程序抛出的异常 Exception 有所不同,假如我们在 process() 方法中直接抛出异常,然后运行注解处理的 JVM 停止工作,javac 会打印出难以理解的包含 FactoryProcessorstacktrace,这时就需要使用我们的 Messager,它会打印出比较和谐的错误消息。
我们在 process() 方法中实现 error 消息处理:

public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
  ...
  for (Element annotatedElement : roundEnv.getElementsAnnotatedWith(Factory.class)) {
    // 检查当前类是否有 @Factory 注解
    if (annotatedElement.getKind() != ElementKind.CLASS) {
      error(annotatedElement, "Only classes can be annotated with @%s", 
        Factory.class.getSimpleName());
      return true; // 退出处理
    }
    ...
  }
}

private void error(Element e, String msg, Object... args) {
  messager.printMessage(
    Diagnostic.Kind.ERROR,
    String.format(msg, args),
    e);
}

需要注意的是打印错误信息 messager.printMessage(Diagnostic.Kind.ERROR) 并不会停止 processing 进程,我们需要返回 true,来中止 process()

数据模型 Data Model

创建 FactoryAnnotatedClass 存储使用 @Factory 注解的类的数据:

public class FactoryAnnotatedClass {

  private TypeElement annotatedClassElement;
  private String qualifiedSuperClassName;
  private String simpleTypeName;
  private String id;

  public FactoryAnnotatedClass(TypeElement classElement) {
    this.annotatedClassElement = classElement;
    // 获取当前类所有的 @Factory 注解信息
    Factory annotation = classElement.getAnnotation(Factory.class);
    id = annotation.id();

    if (Utils.isEmpty(id)) {
      // 异常将会被 FactoryProcessor::process 捕获
      throw new IllegalArgumentException(
          String.format("id() in @%s for class %s is null or empty! that's not allowed.",
              Factory.class.getSimpleName(), classElement.getQualifiedName().toString()));
    }

    // type 所指向的类被编译
    try {
      Class<?> clazz = annotation.type();
      qualifiedSuperClassName = clazz.getCanonicalName();
      simpleTypeName = clazz.getSimpleName();
      // 未被编译将会抛出包含类的异常
    } catch (MirroredTypeException e) {
      DeclaredType classTypeMirror = (DeclaredType) e.getTypeMirror();
      TypeElement classTypeElement = (TypeElement) classTypeMirror.asElement();
      qualifiedSuperClassName = classTypeElement.getQualifiedName().toString();
      simpleTypeName = classTypeElement.getSimpleName().toString();
    }
  }

  /**
   * 获取 @Factory 注解中定义的 id.
   */
  public String getId() {
    return id;
  }

  /**
   * 获取 @Factory 注解中定义的 type 类的 canonical 名称
   */
  public String getQualifiedFactoryGroupName() {
    return qualifiedSuperClassName;
  }

  /**
   * 获取 @Factory 注解中定义的 type 类的名称
   */
  public String getSimpleFactoryGroupName() {
    return simpleTypeName;
  }

  /**
   * 获取使用 @Factory 注解的类
   */
  public TypeElement getTypeElement() {
    return annotatedClassElement;
  }
}

比较有趣的部分是 try/catch 包裹的内容:

  try {
      Class<?> clazz = annotation.type();
      qualifiedSuperClassName = clazz.getCanonicalName();
      simpleTypeName = clazz.getSimpleName();
    } catch (MirroredTypeException e) {
      DeclaredType classTypeMirror = (DeclaredType) e.getTypeMirror();
      TypeElement classTypeElement = (TypeElement) classTypeMirror.asElement();
      qualifiedSuperClassName = classTypeElement.getQualifiedName().toString();
      simpleTypeName = classTypeElement.getSimpleName().toString();
    }
  }

因为 type 所指向内容的类型是 java.lang.Class。这意味着,它是一个 Class 对象。由于 annotation processing 跑在编译 java 源码之前,因此我们需要考虑两点:

  • 类已经被编译:这种场景是假如有一个第三方 .jar 文件包含了编译后的使用 @Factory 注解的类,这时我们可以直接访问这个类。
  • 类未被编译:这种场景当我们访问未被编译的类时会抛出 MirroredTypeException 异常,比较幸运的是这个异常包含了 TypeMirror,这个 TypeMirror 即表示未被编译的类,因为我们知道它的类型肯定是 Class(在 FactoryProcessor::process 检查过),因此我们将其 cast 为 DeclaredType 类型并通过 DeclaredType::asElement 方法访问我们的 Class 对象。

接下来我们需要另一个数据结构 FactoryGroupedClasses 用于集合所有的 FactoryAnnotatedClasses

public class FactoryGroupedClasses {

  /**
   * 生成的 Factory 类的后缀
   */
  public static final String SUFFIX = "Factory";

  private String qualifiedClassName;

  private Map<String, FactoryAnnotatedClass> itemsMap = new LinkedHashMap<>();

  public FactoryGroupedClasses(String qualifiedClassName) {
    this.qualifiedClassName = qualifiedClassName;
  }

  /**
   * 添加注解的类(CalzonePizza 等)到 Factory 中
   *
   * @throws ProcessingException 假如已经存在相同 id 的类
   */
  public void add(FactoryAnnotatedClass toInsert) throws ProcessingException {
    FactoryAnnotatedClass existing = itemsMap.get(toInsert.getId());
    if (existing != null) {
      throw new ProcessingException(toInsert.getTypeElement(),
          "Conflict: The class %s is annotated with @%s with id ='%s' but %s already uses the same id",
          toInsert.getTypeElement().getQualifiedName().toString(), Factory.class.getSimpleName(),
          toInsert.getId(), existing.getTypeElement().getQualifiedName().toString());
    }

    itemsMap.put(toInsert.getId(), toInsert);
  }

  void generateCode(Elements elementUtils, Filer filer) throws IOException {
    ...
  }
}

容器 itemsMap 用于将 @Factory.id 映射到相应的 FactoryAnnotatedClassgenerateCode() 方法将在生成工厂类代码时使用。

匹配规则 Matching Criteria

我们继续回到 process() 方法,现在检查注解类是否满足以下条件:

  • public class
  • 非抽象类,或接口
  • 有个 public 的无参构造方法
  @Override
  public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
    try {
      // Iterate over all @Factory annotated elements
      for (Element annotatedElement : roundEnvironment.getElementsAnnotatedWith(Factory.class)) {
        ...
        TypeElement typeElement = (TypeElement) annotatedElement;

        FactoryAnnotatedClass annotatedClass =
            new FactoryAnnotatedClass(typeElement); // throws IllegalArgumentException
        // 检查是否有合适的类
        checkValidClass(annotatedClass);
        ....
    } catch (IllegalArgumentException e) {
      error(typeElement, e.getMessage());
      return true;
    }
}

private void checkValidClass(FactoryAnnotatedClass item) {
    // 获取 TypeElement
    TypeElement classElement = item.getTypeElement();

    if (!classElement.getModifiers().contains(Modifier.PUBLIC)) {
      error(classElement, "The class %s is not public.",
          classElement.getQualifiedName().toString());
    }

    // 检查是否是抽象类
    if (classElement.getModifiers().contains(Modifier.ABSTRACT)) {
      error(classElement, "The class %s is abstract. You can't annotate abstract classes with @%",
          classElement.getQualifiedName().toString(), Factory.class.getSimpleName());
    }

    // 检查继承状态,类必须是 @Factory.type() 指定类的子类
    TypeElement superClassElement =
        elementUtils.getTypeElement(item.getQualifiedFactoryGroupName());
    if (superClassElement.getKind() == ElementKind.INTERFACE) {
      // 检查是否接口实现
      if (!classElement.getInterfaces().contains(superClassElement.asType())) {
        error(classElement, "The class %s annotated with @%s must implement the interface %s",
            classElement.getQualifiedName().toString(), Factory.class.getSimpleName(),
            item.getQualifiedFactoryGroupName());
      }
    } else {
      // 检查子类
      TypeElement currentClass = classElement;
      while (true) {
        TypeMirror superClassType = currentClass.getSuperclass();

        if (superClassType.getKind() == TypeKind.NONE) {
          // Basic class reached, so exist
          error(classElement, "The class %s annotated with @%s must inherit from %s",
              classElement.getQualifiedName().toString(), Factory.class.getSimpleName(),
              item.getQualifiedFactoryGroupName());
        }

        if (superClassType.toString().equals(item.getQualifiedFactoryGroupName())) {
          // Bingo! 发现正确继承的超类
          break;
        }

        // 检查下一轮继承树
        currentClass = (TypeElement) typeUtils.asElement(superClassType);
      }
    }

    // 检查是否有公共无参构造方法
    for (Element enclosed : classElement.getEnclosedElements()) {
      if (enclosed.getKind() == ElementKind.CONSTRUCTOR) {
        ExecutableElement constructorElement = (ExecutableElement) enclosed;
        if (constructorElement.getParameters().size() == 0 && constructorElement.getModifiers()
            .contains(Modifier.PUBLIC)) {
          // Bingo! 发现空的构造方法
          return;
        }
      }
    }

    // 未发现合适构造方法
    error(classElement, "The class %s must provide an public empty default constructor",
        classElement.getQualifiedName().toString());
  }

聚合注解类 Grouping The Annotated Classes

检查完注解类后,我们需要将 FactoryAnnotatedClass 添加到相应的 FactoryGroupedClasses:

  @Override
  public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
    try {
      // Iterate over all @Factory annotated elements
      for (Element annotatedElement : roundEnvironment.getElementsAnnotatedWith(Factory.class)) {
        ...
        TypeElement typeElement = (TypeElement) annotatedElement;

        FactoryAnnotatedClass annotatedClass =
            new FactoryAnnotatedClass(typeElement); // throws IllegalArgumentException

        checkValidClass(annotatedClass);
        
        // 添加注解类
        FactoryGroupedClasses factoryClass =
            factoryClasses.get(annotatedClass.getQualifiedFactoryGroupName());
        if (factoryClass == null) {
          String qualifiedGroupName = annotatedClass.getQualifiedFactoryGroupName();
          factoryClass = new FactoryGroupedClasses(qualifiedGroupName);
          factoryClasses.put(qualifiedGroupName, factoryClass);
        }
        factoryClass.add(annotatedClass);
      }

      // 生成代码
      for (FactoryGroupedClasses factoryClass : factoryClasses.values()) {
        factoryClass.generateCode(elementUtils, filer);
      }

      factoryClasses.clear();
    } catch (ProcessingException e) {
      error(e.getElement(), e.getMessage());
    } catch (IOException e) {
      error(null, e.getMessage());
    }

    return true;
}

生成代码 Code Generation

我们使用 Square 提供的 Java 文件生成工具 JavaPoet

void generateCode(Elements elementUtils, Filer filer) throws IOException {
    TypeElement superClassName = elementUtils.getTypeElement(qualifiedClassName);
    String factoryClassName = superClassName.getSimpleName() + SUFFIX;
    PackageElement pkg = elementUtils.getPackageOf(superClassName);
    String packageName = pkg.isUnnamed() ? null : pkg.getQualifiedName().toString();

    MethodSpec.Builder method = MethodSpec.methodBuilder("create")
        .addModifiers(Modifier.PUBLIC)
        .addParameter(String.class, "id")
        .returns(TypeName.get(superClassName.asType()));

    // check if id is null
    method.beginControlFlow("if (id == null)")
        .addStatement("throw new IllegalArgumentException($S)", "id is null")
        .endControlFlow();

    // generate items map
    for (FactoryAnnotatedClass item : itemsMap.values()) {
      method.beginControlFlow("if ($S.equals(id))", item.getId())
          .addStatement("return new $L()", item.getTypeElement().getQualifiedName().toString())
          .endControlFlow();
    }

    method.addStatement("throw new IllegalArgumentException($S + id)", "Unknown id = ");

    TypeSpec typeSpec = TypeSpec.classBuilder(factoryClassName).addMethod(method.build()).build();

    // Write file
    JavaFile.builder(packageName, typeSpec).build().writeTo(filer);
  }

处理过程

OK,上面的步骤都完成后,我们只需要 build 现在的 Android 工程,一切顺利的话,Annotation Processor 会自动帮我们生成 MealFactory 类,它的路径是:~projectName/app/build/generated/source/apt/debug/packageName/MealFactory,生成的代码像这样:

package com.zac4j.anna;

import java.lang.String;

class MealFactory {
  public Meal create(String id) {
    if (id == null) {
      throw new IllegalArgumentException("id is null");
    }
    if ("Calzone".equals(id)) {
      return new com.zac4j.anna.CalzonePizza();
    }
    if ("Margherita".equals(id)) {
      return new com.zac4j.anna.MargheritaPizza();
    }
    if ("Tiramisu".equals(id)) {
      return new com.zac4j.anna.Tiramisu();
    }
    throw new IllegalArgumentException("Unknown id = " + id);
  }
}

我们可以在 PizzaStore 直接使用它:

public class PizzaStore {

  private MealFactory factory = new MealFactory();

  public Meal order(String mealName) {
    return factory.create(mealName);
  }
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,271评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,275评论 2 380
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,151评论 0 336
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,550评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,553评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,559评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,924评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,580评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,826评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,578评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,661评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,363评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,940评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,926评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,156评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,872评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,391评论 2 342

推荐阅读更多精彩内容