[脑洞]使用annotation生成反射常量池

问题

反射是每个Java开发都躲不开的工具,很多时候可以用反射把代码写的非常整洁,但会付出两个代价:

  • 性能问题
  • 反射用字面量和对应的类需要维护,容易出现bug

前者是不可避免的,而后者可以通过apt来维护。

以前都是用apt/javassist减少反射,后面做首个dex减肥发现,有的反射是必须的。突然发现,apt不仅能干掉反射,也能让反射更好维护。
因为所有需要被反射调用的类都不能被混淆,所以编译期就可以拿到最终的ClassName和MethodName,apt可以用来做自动的常量池。

代码

Annotation,打到需要加到常量池的类/方法上(field也应该支持,暂时用不到就果断略过了)

@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();
    }
  }
}

总结

反射的时候使用稳定(如果使用value指定常量名)的常量。不管反射目标代码如何修改,都可以保持外部的稳定。而如果未指定常量名,如何目标类的修改都会导致编译不过(找不到常量),相当于有了反射的编译校验。
缺点是神奇的apt并不支持在annotation中用生成的值。也就是说,如果有两个apt, 第一个生成常量,第二个apt里使用到了生成的常量,会直接抛AnnotationTypeMismatchException,这个弱鸡及神奇的设定,导致apt中的代码还是很丑,很难维护。bibibibibi

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值