根据注解的作用时间@Retention
可将注解分为三类:
RetentionPolicy.SOURCE
此类注解只在编译过程中保留,不写入Class文件内。所以此类注解中的信息可以被编译器使用,这一过程通常需要借助注解处理器Annotation Processor
。
编译器触发注解处理器对注解中的信息进行读取并执行相关操作,常见的操作是自动生成代码(如:butterknife),或者时进行额外的类型检查等。
在另一个类中,为类的域自动生成一个同名的域,并赋值为注解中的value值。
注解名: @Code
例如:
//:Tmp.java public class Tmp { @Code(value= "field1Value") public String field1 ; @Code(value = "field2Value") public String field2; }
经过编译器处理后,自动生成一个
Tmpcode.java
文件
//:Tmpcode.java public class Tmpcode { private final String field1 = "field1Value"; private final String field2 = "field2Value"; }
强烈推荐《Java注解处理器》,考虑到注解处理器中可能使用第三方库并导致某些问题,所以这里将注解和注解处理器分开成两个Module
。将注解置于lib_annotations
将注解处理器置于lib_compiler
中。
新建Module,并命名为lib_annotations
,创建Code.java声明注解;
//:Code.java
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.SOURCE)
public @interface Code {
String value() default "";
}
public class Tmp {
@Code(value = "field1Value")
public String field1 ;
@Code(value = "field2Value")
public String field2;
}
在正式开始书写注解处理前,需要在lib_compiler
引入两个依赖包:
compile 'com.google.auto.service:auto-service:1.0-rc2'
compile 'com.squareup:javapoet:1.9.0'
- com.google.auto.service:auto-service:1.0-rc2
此依赖下含有注解@AutoService,当使用@AutoService(Processor.class)注解自定义的注解处理器时,编译时自动在META-INF/services下生成javax.annotation.processing.Processor文件,文件中含有所有自定义注解处理器的全名,供外部程序装载jar包时寻找处理器的具体实现类,完成装载,当然也可以手动建立文件结构, 可参考这里#注册你的处理器。
- com.squareup:javapoet:1.9.0
编写
.java
文件的工具类,如果不使用此工具类,也可手动使用Filer
中提供的方法书写或者使用PrintWriter
,具体可参考《JAVA编程思想》(此书中涉及的两个构建处理器的类均已淘汰,现在使用AbstractProcessor
即可)。
自定义注解处理器的骨架通常如下:
//
@AutoService(Processor.class)
public class RandomProcessor extends AbstractProcessor{
@Override
public synchronized void init(ProcessingEnvironment processingEnvironment) {
super.init(processingEnvironment);
}
@Override
public SourceVersion getSupportedSourceVersion() {
return super.getSupportedSourceVersion();
}
@Override
public Set<String> getSupportedAnnotationTypes() {
return super.getSupportedAnnotationTypes();
}
@Override
public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
return false;
}
}
init(ProcessingEnvironment env)
: 每一个注解处理器类都必须有一个空的构造函数。然而,这里有一个特殊的init()
方法,它会被注解处理工具调用,并通过传入的ProcessingEnviroment
参数可获取Types
、Elements
、Filer
、
Messager
。getSupportedSourceVersion()
:用来指定你使用的Java版本。通常这里返回SourceVersion.latestSupported()getSupportedAnnotationTypes()
:返回需要此注解处理器处理的注解类全面(Class#getSimpleName()
)的Set
集合。process(Set extends TypeElement> annotations, RoundEnvironment env)
: 这相当于每个处理器的主函数main()。你在这里写你的扫描、评估和处理注解的代码,以及生成Java文件。输入参数RoundEnviroment,可以让你查询出包含你需要的注解条目。
从ProcessingEnvironment
中获得工具,指定java版本,指定需要处理的注解后Processor
应该是这种的:
//:CodeAnnotationProcessor.java
@AutoService(Processor.class)
public class CodeAnnotationProcessor extends AbstractProcessor{
private Types typeUtils;
private Elements elementUtils;
private Filer filer;
private Messager messager;
private Map<String, CodeAnnotatedFieldGroup> codeGroups;
@Override
public synchronized void init(ProcessingEnvironment processingEnvironment) {
super.init(processingEnvironment);
typeUtils = processingEnvironment.getTypeUtils();
elementUtils = processingEnvironment.getElementUtils();
filer = processingEnvironment.getFiler();
messager = processingEnvironment.getMessager();
}
@Override
public Set<String> getSupportedAnnotationTypes() {
Set<String> annotations = new LinkedHashSet<String>();
annotations.add(Code.class.getCanonicalName());
return annotations;
}
@Override
public SourceVersion getSupportedSourceVersion() {
return SourceVersion.latestSupported();
}
@Override
public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
return false;
}
}
接下来,便是在process
中,从传入的参数中获取每条注解的信息,并将注解中信息写成入新的.java
文件中。此时需要考虑到,注解@Code
可能并不只在一个.java
源文件中有使用,且一个.java
文件中也会存在多条@Code
注解。我们是基于面向对象编程,所以考虑将每条注解的信息(注解体中的信息和被注解Element
的信息)保存在一个自定义的CodeAnnonatedField
对象当中,使用另一个自定对象CodeAnnonatedField
来管理注解在同一个.java
源文件下的所有@Code
注解。最后,使用一个Map
来维护所有的CodeAnnotatedFieldGroup
,其中String
为CodeAnnotatedFieldGroup
所对应的被注解的.java
源文件的全名(TypeElement.getQualifiedName().toString()
);
记录注解条目及被注解Element
信息的CodeAnnotatedField
类如下:
//:CodeAnnotatedField.java
public class CodeAnnotatedField {
private String name; //存储被注解域的名字
private String value; //存储注解条目中的value值
public CodeAnnotatedField(Element element){
Code annotation = element.getAnnotation(Code.class);
name = element.getSimpleName().toString();
value = annotation.value();
}
public String getName() {
return name;
}
public String getValue() {
return value;
}
}
public class CodeAnnotatedFieldGroup {
private static final String TAG = "CodeAnnotatedFieldGroup";
private final String SUFFIX = "code";
private String qualifiedClassName;
private Map<String, CodeAnnotatedField> itemMap = new LinkedHashMap<String, CodeAnnotatedField>();
public CodeAnnotatedFieldGroup(String qualifiedClassName) {
this.qualifiedClassName = qualifiedClassName;
}
public void add(CodeAnnotatedField codeAnnotatedField){
CodeAnnotatedField existing = itemMap.get(codeAnnotatedField.getName());
if (existing != null){
throw new IllegalArgumentException(String.format("Error: same id = %s", existing.getName()));
}
itemMap.put(codeAnnotatedField.getName(), codeAnnotatedField);
}
public void generateCode(Elements elementUtils, Filer filer) throws IOException {
TypeElement superClass = elementUtils.getTypeElement(qualifiedClassName);
String codeQualifiedClassName = superClass.getQualifiedName().toString() + SUFFIX;
System.out.println(codeQualifiedClassName);
String codeClassSimpleName = superClass.getSimpleName() + SUFFIX;
PackageElement packageElement = elementUtils.getPackageOf(superClass);
String packageName = packageElement.isUnnamed()? null: packageElement.getQualifiedName().toString();
List<FieldSpec> fieldSpecs = new ArrayList<>();
for (CodeAnnotatedField c : itemMap.values()){
FieldSpec fieldSpec = FieldSpec.builder(String.class, c.getName())
.addModifiers(Modifier.PRIVATE, Modifier.FINAL)
.initializer("$S", c.getValue())
.build();
System.out.println(c.getName());
fieldSpecs.add(fieldSpec);
}
TypeSpec codeJava = TypeSpec.classBuilder(codeClassSimpleName)
.addModifiers(Modifier.PUBLIC)
.addFields(fieldSpecs)
.build();
System.out.println(packageName);
JavaFile.builder(packageName, codeJava).build().writeTo(filer);
}
}
最终的Processor
如下:
@AutoService(Processor.class)
public class CodeAnnotationProcessor extends AbstractProcessor{
private Types typeUtils;
private Elements elementUtils;
private Filer filer;
private Messager messager;
private Map<String, CodeAnnotatedFieldGroup> codeGroups;
@Override
public synchronized void init(ProcessingEnvironment processingEnvironment) {
super.init(processingEnvironment);
typeUtils = processingEnvironment.getTypeUtils();
elementUtils = processingEnvironment.getElementUtils();
filer = processingEnvironment.getFiler();
messager = processingEnvironment.getMessager();
}
@Override
public Set<String> getSupportedAnnotationTypes() {
Set<String> annotations = new LinkedHashSet<String>();
annotations.add(Code.class.getCanonicalName());
annotations.add(RandomString.class.getCanonicalName());
return annotations;
}
@Override
public SourceVersion getSupportedSourceVersion() {
return SourceVersion.latestSupported();
}
@Override
public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
codeGroups = new LinkedHashMap<String, CodeAnnotatedFieldGroup>();
for (Element annotatedElement: roundEnvironment.getElementsAnnotatedWith(Code.class)){
if (annotatedElement.getKind() != ElementKind.FIELD){
throw new IllegalArgumentException("classes can be only annotated field");
}
CodeAnnotatedField code = new CodeAnnotatedField(annotatedElement);
TypeElement typeElement = (TypeElement) annotatedElement.getEnclosingElement();//获取类Element
CodeAnnotatedFieldGroup codeGroup = codeGroups.get(typeElement.getQualifiedName().toString() );
//为每个类单独建立一个CodeAnnotatedFieldGroup来维护其中的所有注解信息
if (codeGroup == null){
codeGroup = new CodeAnnotatedFieldGroup(typeElement.getQualifiedName().toString());
codeGroups.put(typeElement.getQualifiedName().toString(),codeGroup);
}
codeGroup.add(code);
}
for (CodeAnnotatedFieldGroup cls : codeGroups.values()){
try {
cls.generateCode(elementUtils, filer);
} catch (IOException e) {
e.printStackTrace();
}
}
return true;
}
}
运行:重新Build
即可。
调试:注解处理器是运行它自己的虚拟机JVM中,javac启动一个完整Java虚拟机来运行注解处理器,所以注解处理器的debug跟普通的代码debug有点不同,需要执行compileDebugJavaWithJavac
,在当前工程路径下输入命令:
gradlew --no-daemon -Dorg.gradle.debug=true :app:clean :app:compileDebugJavaWithJavac
并在Edit Configurations中新添加一个远程配置(remote),名字随意,端口为5005。
然后点击debug按钮,就可以连接上远程调试器进行Annotation的调试了。演示DEMO
中间的校验过程望读者自己深入学习,此文抛砖引玉。
RetentionPolicy.CLASS
此类注解在编译中会保留并会写入class文件中,但并不会被加载到JVM中。所以在运行时无法通过反射获取此类注解的信息,但在编译时,注解是可见的。
这里很重要的一点是编译多个Java文件时的情况:假如要编译A.java源码文件和B.class文件,其中A类依赖B类,并且B类上有些注解希望让A.java编译时能看到,那么B.class里就必须要持有这些注解信息才行。同时我们可能不需要让它在运行时对反射可见(例如说为了减少运行时元数据的大小之类),所以会选择CLASS而不是RUNTIME。–知乎:RednaxelaFX
此注解的使用并不常见,通常用RetentionPolicy.CLASS
、RentionPolicy.RUNTIME
RetentionPolicy.RUNTIME
由编译器记录在类文件中,并且会在运行时同所在的.class
文件一起加载到JVM中,所以通常以反射的形式读取该类注解中的信息。演示DEMO
反射输出读取注解信息并不算复杂。
//:AnnotationTest.java
@Target({ElementType.METHOD, ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface AnnotationTest {
public String name() default "null";
}
//:DeclareSpace.java
public class DeclareSpace {
@AnnotationTest(name = "field1")
public String field1;
@AnnotationTest(name = "field2")
private int field2;
public int method1() {
return 0;
}
@AnnotationTest(name = "method2")
private void method2() {
return;
}
@AnnotationTest(name = "method3")
private String method3(){
return null;
}
@AnnotationTest
private short method4(){
return 0;
}
}
public class MyProcessor {
public static void printTestMethod(Class<?> cl){
for (Method method : cl.getDeclaredMethods()){
AnnotationTest annotationTest = method.getAnnotation(AnnotationTest.class);
if (annotationTest != null){
System.out.println(annotationTest.name());
}
}
for (Field field : cl.getDeclaredFields()){
AnnotationTest annotationTest = field.getAnnotation(AnnotationTest.class);
if (annotationTest != null){
System.out.println(annotationTest.name());
}
}
}
public static void main(String[] args) {
printTestMethod(DeclareSpace.class);
}
}
//:output~
method2
null
method3
field1
field2
此文参考了众多网上优秀资料,但笔者水平有限,只能理解至此。
MENU
Java注解处理器
使用Android注解处理器,解放劳动生产力
java annotation 中 SOURCE 和 CLASS 的区别