[Brain hole] Use annotation to generate reflection constant pool

question

Reflection is an inescapable tool for every Java development. In many cases, you can use reflection to write very clean code, but you will pay two costs:

  • performance issues
  • Reflection literals and corresponding classes need to be maintained and are prone to bugs

The former is inevitable, while the latter can be maintained through apt.

In the past, I used apt/javassist to reduce reflections. Later, I did the first dex weight loss and found that some reflections are necessary. Suddenly I found that apt can not only kill the reflection, but also make the reflection better maintained.
Because all classes that need to be called by reflection cannot be confused, the final ClassName and MethodName can be obtained at compile time, and apt can be used as an automatic constant pool.

code

Annotation, hit the class/method that needs to be added to the constant pool (field should also support it, if it is not used temporarily, it will be skipped decisively)

@Retention(RetentionPolicy.SOURCE)
@Target({ElementType.METHOD, ElementType.TYPE})
public @interface ReflectConstant {
  String value() default "";
}

Processor

@SupportedAnnotationTypes({"ReflectConstant"})
@SupportedSourceVersion(SourceVersion.RELEASE_8)
@SupportedOptions(ConstantProcessor.CLASS_NAME)
public class ConstantProcessor extends AbstractProcessor {
  public static final String CLASS_NAME = "ConstantClassName";
  public static String DEFAULT_NAME = "ClassConstants";

  private boolean mHasProcessed;
  private Filer mFiler;
  private Elements mUtils;
  private Types mTypes;
  private String mPackage;
  private String mClassName;
  private Messager mMessager;

  @Override
  public synchronized void init(ProcessingEnvironment processingEnv) {
    super.init(processingEnv);
    mFiler = processingEnv.getFiler();
    mUtils = processingEnv.getElementUtils();
    mTypes = processingEnv.getTypeUtils();
    mMessager = processingEnv.getMessager();
    String fullName = processingEnv.getOptions().get(CLASS_NAME);
    if (fullName == null || fullName.isEmpty()) {
      fullName = DEFAULT_NAME;
    }

    int lastDot = fullName.lastIndexOf('.');
    mClassName = fullName.substring(lastDot + 1);
    mPackage = fullName.substring(0, lastDot);
  }

  @Override
  public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnv) {
    if (mHasProcessed) {
      return false;
    }

    final Map<String, String> classMapping = new HashMap<>();
    final Map<String, String> methodMapping = new HashMap<>();

    for (Element element : roundEnv.getElementsAnnotatedWith(ReflectConstant.class)) {
      if (element == null) {
        continue;
      }
      if (element.getKind() == ElementKind.CLASS) {
        processClass(element, classMapping);
      } else if (element.getKind() == ElementKind.METHOD) {
        processMethod(element, methodMapping);
      }
    }

    TypeSpec.Builder builder = buildClass(classMapping, methodMapping);
    writeClass(mPackage, mClassName, builder);
    mHasProcessed = true;
    return false;
  }

  private TypeSpec.Builder buildClass(Map<String, String> classMapping,
      Map<String, String> methodMapping) {
    AnnotationSpec generated = AnnotationSpec.builder(Generated.class)
        .addMember("value", "$S",
            "com.smile.gifshow.annotation.plugin.processing.ConstantProcessor")
        .build();
    TypeSpec.Builder constantWrapper =
        TypeSpec.classBuilder(mClassName)
            .addModifiers(Modifier.PUBLIC, Modifier.FINAL)
            .addAnnotation(generated);

    TypeSpec.Builder classConstant =
        TypeSpec.classBuilder("Class")
            .addModifiers(Modifier.PUBLIC, Modifier.FINAL, Modifier.STATIC);
    TypeSpec.Builder methodConstant =
        TypeSpec.classBuilder("Method")
            .addModifiers(Modifier.PUBLIC, Modifier.FINAL, Modifier.STATIC);

    for (Map.Entry<String, String> clazz : classMapping.entrySet()) {
      FieldSpec fieldSpec = FieldSpec.builder(String.class, clazz.getKey(), Modifier.PUBLIC,
          Modifier.FINAL, Modifier.STATIC)
          .initializer("\"$L\"", clazz.getValue()).build();
      classConstant.addField(fieldSpec);
    }

    for (Map.Entry<String, String> method : methodMapping.entrySet()) {
      FieldSpec fieldSpec = FieldSpec.builder(String.class, method.getKey(), Modifier.PUBLIC,
          Modifier.FINAL, Modifier.STATIC)
          .initializer("\"$L\"", method.getValue()).build();
      methodConstant.addField(fieldSpec);
    }

    return constantWrapper
        .addType(classConstant.build())
        .addType(methodConstant.build());
  }

  private void processMethod(Element element, Map<String, String> methodMapping) {
    ReflectConstant reflectConstant = element.getAnnotation(ReflectConstant.class);
    String key = reflectConstant.value();
    String fullName = element.getSimpleName().toString();
    if (key.isEmpty()) {
      key = fullName.toUpperCase();
    }
    methodMapping.put(key, fullName);
  }

  private void processClass(Element element, Map<String, String> classMapping) {
    ClassName className = (ClassName) ClassName.get(element.asType());
    ReflectConstant reflectConstant = element.getAnnotation(ReflectConstant.class);
    String key = reflectConstant.value();
    if (key.isEmpty()) {
      key = className.simpleName().toUpperCase();
    }
    if (classMapping.containsKey(key)) {
      mMessager.printMessage(Diagnostic.Kind.ERROR, "有相同的常量名 " + key, element);
    }
    classMapping.put(key, className.reflectionName());
  }

  private void writeClass(String pkg, String name, TypeSpec.Builder type) {
    try {
      Writer writer = mFiler.createSourceFile(pkg + "." + name).openWriter();
      JavaFile.builder(pkg, type.build()).build().writeTo(writer);
      writer.flush();
      writer.close();
    } catch (IOException e) {
      e.printStackTrace();
    }
  }
}

Summarize

Use stable (if you use value to specify a constant name) constants during reflection. No matter how the reflection target code is modified, the external stability can be maintained. If the constant name is not specified, the modification of the target class will lead to the compilation failure (the constant cannot be found), which is equivalent to the compilation verification with reflection.
The downside is that magic apt doesn't support using generated values ​​in annotations. That is to say, if there are two apts, the first one generates constants, and the second one uses the generated constants, AnnotationTypeMismatchException will be thrown directly . This weak and magical setting makes the code in apt ugly. Difficult to maintain. bibibibibi

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=325916361&siteId=291194637