问题
反射是每个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