最近在回看Java核心技术看到了编译时注解生成Java类并编译,手动尝试了一下
这里模仿ButterKnife写个简单的Dome记录一下,再开安卓项目比较麻烦,这里直接以字符串赋值为例
编写对应注解
@Retention(RetentionPolicy.SOURCE)//保留到源码即可
@Target(ElementType.FIELD)
public @interface Value {
String value() default "";
}
编写对应处理器
@SupportedAnnotationTypes("com.sk.annotations.Value")//支持的注解
@SupportedSourceVersion(SourceVersion.RELEASE_8)//支持的Java版本
public class ValueProcessor extends AbstractProcessor {
@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
Map<TypeElement, List<FieldInfo>> map = getFieldMap(annotations, roundEnv);
createClassFiles(map);
return true;//返回是否处理完毕 若处理完毕不再需要其他注解处理器处理
}
private void createClassFiles(Map<TypeElement, List<FieldInfo>> map) {
for (TypeElement element : map.keySet()) {
createClassFile(element, map.get(element));
}
}
//生成对应的类文件 这里可以考虑使用模板引擎生成
private void createClassFile(TypeElement element, List<FieldInfo> fieldInfos) {
Messager messager = processingEnv.getMessager();
String fullName = element.getQualifiedName().toString();
int index = fullName.lastIndexOf('.');
String packageName = fullName.substring(0, index);
String name = fullName.substring(index + 1);
String className = "Bind_" + name;
try {
JavaFileObject file = processingEnv.getFiler().createSourceFile(packageName + "." + className);
Writer writer = file.openWriter();
writer.write("package " + packageName + ";\n" +
"\n" +
"import com.sk.interfaces.ValueSetter;\n" +
"\n" +
"public class " + className + " implements ValueSetter<" + name + "> {\n" +
"\n" +
" @Override\n" +
" public void applyObject(" + name + " target) {\n");
for (FieldInfo fieldInfo : fieldInfos) {
writer.write("\n\ntarget." + fieldInfo.getName() + "=\"" + fieldInfo.getValue() + "\";\n");
}
writer.write(" }\n" +
"\n" +
"}");
writer.close();
messager.printMessage(Diagnostic.Kind.NOTE, "成功生成:" + fullName);
} catch (IOException e) {
e.printStackTrace();
}
}
private Map<TypeElement, List<FieldInfo>> getFieldMap(Set<? extends TypeElement> annotations,
RoundEnvironment roundEnv) {
// Messager messager = processingEnv.getMessager();
Map<TypeElement, List<FieldInfo>> map = new HashMap<>();
for (TypeElement annotation : annotations) {//根据注解获取对应元素
Set<? extends Element> elements = roundEnv.getElementsAnnotatedWith(annotation);
for (Element element : elements) {//处理每个注解元素
Value value = element.getAnnotation(Value.class);
TypeElement clazz = (TypeElement) element.getEnclosingElement();
String name = element.getSimpleName().toString();
if (!map.containsKey(clazz)) map.put(clazz, new ArrayList<>());
map.get(clazz).add(new FieldInfo(name, value.value()));
}
}
return map;
}
}
对应启动类
public class BindTool {
public static void bind(Object object){
Class<?> clazz = object.getClass();
String fullName = clazz.getName();
int index = fullName.lastIndexOf('.');
try {//根据类名找对应的绑定类进行绑定
Class<ValueSetter> bindClass = (Class<ValueSetter>) Class.forName(fullName.substring(0, index) + ".Bind_"
+ fullName.substring(index + 1));
ValueSetter valueSetter = bindClass.newInstance();
valueSetter.applyObject(object);
} catch (Exception e) {
e.printStackTrace();
}
}
}
一些工具类省略了,最后会放上测试项目地址
配置注解处理器
配置Maven不注解处理该项目防止死循环
<build>
<plugins>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.0</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
<!-- Disable annotation processing for ourselves.-->
<compilerArgument>-proc:none</compilerArgument>
</configuration>
</plugin>
</plugins>
</build>
测试结果
这里实际是在编译时生成新的类并编译,并不是像lombok一样直接修改源文件
Dome地址:https://gitee.com/shaokang123/annotation-test