[TOC]
Java 注解:注解處理器獲取泛型真實(shí)類型
注解 annotation 是 Java 中的一大特性概说,是插入代碼中的元數(shù)據(jù)淹遵。注解的使用能夠大大簡(jiǎn)化代碼的編寫,所以在很多框架中得到了使用,比如 Web 框架 Spring 中的 @Service畅姊、@Resource 注解,比如參數(shù)校驗(yàn)框架 hibernate-validator 中的 @NotNull 等注解吹由。
如何定義注解
定義注解需要用到元注解 meta annotation若未,即描述注解的注解。元注解有以下幾類:
- @Target:描述了注解所修飾的對(duì)象范圍倾鲫,比如:類粗合、方法、字段乌昔、以及注解本身等等隙疚。
- @Retention:定義注解被保留的時(shí)間長(zhǎng)短,有三種:源文件磕道、class 文件供屉、運(yùn)行時(shí)。
- @Documented:表示含有該注解類型的元素(帶有注釋的)會(huì)通過javadoc或類似工具進(jìn)行文檔化溺蕉。
- @Inherited:表示注解類型能被自動(dòng)繼承伶丐。
Java 中使用 Class 來定義類,使用 enum 定義枚舉疯特,使用 @interface 來定義注解哗魂。具體定義格式如下:
public @interface 注解名 { 定義體 }
其中的定義體里可以寫若干方法來表示配置,方法的名稱就是參數(shù)的名稱辙芍,返回值類型就是參數(shù)的類型啡彬,可以通過 default 來聲明參數(shù)的默認(rèn)值。下面給出一個(gè)注解的示例:
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface AutoParse {
String key();
int type() default 0;
}
該注解 AutoParse 可以用于描述 FIELD 即類中字段故硅,在運(yùn)行時(shí)仍然能夠獲取庶灿,不可被繼承(沒有使用 Inherited 注解描述)。
獲取注解信息
注解通常用來描述類吃衅、方法等往踢,那如何獲取一個(gè)方法或類上的注解呢?可以通過 Class 類或 Method 類對(duì)應(yīng)的 getAnnotation 方法獲扰遣恪:
public final class Class<T> implements java.io.Serializable,
GenericDeclaration,
Type,
AnnotatedElement {
public Annotation[] getAnnotations();
public <A extends Annotation> A getAnnotation(Class<A> annotationClass);
public <A extends Annotation> A getDeclaredAnnotation(Class<A> annotationClass);
public Annotation[] getDeclaredAnnotations();
}
public final class Method extends Executable {
public <T extends Annotation> T getAnnotation(Class<T> annotationClass);
public Annotation[] getDeclaredAnnotations();
// 返回方法參數(shù)的注解
public Annotation[][] getParameterAnnotations();
}
getDeclaredAnnotations 方法只返回直接存在于此元素上的注解峻呕,不會(huì)返回繼承的注解利职;而 getAnnotation 則返回全部注解,包含繼承的瘦癌。
注:Field 也有類似的方法可以獲取注解猪贪。
注解處理器
注解處理器 annotation processor 是 javac 內(nèi)置的編譯時(shí)掃描和處理注解的工具,比較常見的用法是在編譯時(shí)獲取注解信息讯私,動(dòng)態(tài)生成 Java 文件热押。
AbstractProcessor
想要自定義注解處理器需要繼承實(shí)現(xiàn) AbstractProcessor 類:
- init 方法入?yún)⑹黔h(huán)境變量 ProcessingEnvironment,從中可以拿到報(bào)告錯(cuò)誤信息的 Messager斤寇,用于生成代碼文件的 Filer桶癣。
- process 抽象方法,是我們需要實(shí)現(xiàn)的娘锁,用于處理注解信息的方法牙寞,生成類文件的代碼放這里。
public abstract class AbstractProcessor implements Processor {
public synchronized void init(ProcessingEnvironment processingEnv) {
if (initialized)
throw new IllegalStateException("Cannot call init more than once.");
Objects.requireNonNull(processingEnv, "Tool provided null ProcessingEnvironment");
this.processingEnv = processingEnv;
initialized = true;
}
public abstract boolean process(Set<? extends TypeElement> annotations,
RoundEnvironment roundEnv);
}
示例:注解處理器獲取泛型信息
Java 中的泛型有一個(gè)類型擦除的概念:
Java中的泛型基本上都是在編譯器這個(gè)層次來實(shí)現(xiàn)的莫秆。在生成的Java字節(jié)碼中是不包含泛型中的類型信息的间雀。使用泛型的時(shí)候加上的類型參數(shù),會(huì)在編譯器在編譯的時(shí)候去掉馏锡。這個(gè)過程就稱為類型擦除雷蹂。
那么如何在運(yùn)行時(shí)獲取某對(duì)象的某個(gè) Field 的真實(shí)類型信息呢?既然是在編譯期間擦除的杯道,那么我們就可以注解處理器在編譯期間獲取泛型的真實(shí)類型信息。
以下給出一個(gè)處理上面的 @AutoParse 注解的注解處理器示例责蝠,該注解處理器獲取了注解所描述字段的類型信息党巾,并將這些信息寫入了一個(gè)類文件中:
- @SupportedAnnotationTypes:指明要處理的注解。
- @SupportedSourceVersion(SourceVersion.RELEASE_7):指明適合的 Java 版本霜医。
- 繼承 process 方法齿拂,做真正的處理工作。
- Filer 用于創(chuàng)建新的 Java 類肴敛。
- RoundEnvironment 類包含了被注解所描述的 Field 的真實(shí)類型信息署海。
@SupportedAnnotationTypes({ "com.albon.arith.annotation.procecssor.AutoParseField" })
@SupportedSourceVersion(SourceVersion.RELEASE_8)
public class AutoParseFieldProcessor extends AbstractProcessor {
private Filer filer;
@Override
public synchronized void init(ProcessingEnvironment processingEnv) {
super.init(processingEnv);
filer = processingEnv.getFiler();
}
@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
Map<String, String> fieldTypeMap = Maps.newHashMap();
for (Element elem : roundEnv.getElementsAnnotatedWith(AutoParseField.class)) {
AutoParseField annotation = elem.getAnnotation(AutoParseField.class);
String message = System.currentTimeMillis() + " - annotation found in " + elem.getSimpleName()
+ " with key " + annotation.key() + " kind " + elem.getKind() + " class " + elem.getClass()
+ " asType " + elem.asType() + "\n\tgetEnclosingElement().getSimpleName() "
+ elem.getEnclosingElement().getSimpleName() + "\n\tgetEnclosingElement().asType() "
+ elem.getEnclosingElement().asType();
fieldTypeMap.put(elem.getEnclosingElement().asType().toString() + "#" + elem.getSimpleName(),
elem.asType().toString());
processingEnv.getMessager().printMessage(Diagnostic.Kind.NOTE, message);
}
if (fieldTypeMap.isEmpty()) {
return true;
}
Writer writer = null;
try {
JavaFileObject jfo = filer
.createSourceFile("com.albon.arith.annotation.service.AutoParseFieldInfo");
writer = jfo.openWriter();
writer.write("package com.albon.arith.annotation.service;\n" +
"\n" +
"import com.google.common.collect.Maps;\n" +
"\n" +
"import java.util.Map;\n" +
"\n" +
"public class AutoParseFieldInfo {\n" +
"\n" +
" // key: classpath#fieldName, value: fieldType\n" +
" public static final Map<String, String> FIELD_TYPE_MAP = Maps.newHashMap();\n" +
"\n" +
" static {\n");
for (Map.Entry<String, String> entry : fieldTypeMap.entrySet()) {
writer.write(" FIELD_TYPE_MAP.put(\"" +
entry.getKey() +
"\", \"" +
entry.getValue() +
"\");\n");
}
writer.write(" }\n" +
"\n" +
" public static void main(String[] args) {\n" +
" System.out.println(FIELD_TYPE_MAP);\n" +
" }\n" +
"}\n");
} catch (Exception e) {
e.printStackTrace();
} finally {
if (writer != null) {
try {
writer.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
return true; // no further processing of this annotation type
}
}
我們寫了一個(gè)使用 @AutoParse 注解描述的 SimplePoJo 類:
public class SimplePoJo {
@AutoParse(key = "first")
private Integer first;
@AutoParse(key = "second")
private String second;
@AutoParse(key = "third")
private List<String> third;
}
還需要在配置文件中指定使用該注解處理器,請(qǐng)?jiān)?resources 文件夾下新建 META-INF/services 文件夾医男,再新建文件 javax.annotation.processing.Processor砸狞,文件內(nèi)容如下:
com.albon.arith.annotation.procecssor.AutoParseFieldProcessor
代碼完成之后,使用 mvn clean package 進(jìn)行編譯镀梭,編譯后可以看到經(jīng)由注解處理器生成的 AutoParseFieldInfo.java 文件刀森,其內(nèi)容如下所示:
public class AutoParseFieldInfo {
// key: classpath#fieldName, value: fieldType
public static final Map<String, String> FIELD_TYPE_MAP = Maps.newHashMap();
static { FIELD_TYPE_MAP.put("com.albon.arith.annotation.service.SimplePoJo#third", "java.util.List<java.lang.String>");
FIELD_TYPE_MAP.put("com.albon.arith.annotation.service.SimplePoJo#second", "java.lang.String");
FIELD_TYPE_MAP.put("com.albon.arith.annotation.service.SimplePoJo#first", "java.lang.Integer");
}
public static void main(String[] args) {
System.out.println(FIELD_TYPE_MAP);
}
}
示例代碼地址
完整的示例代碼請(qǐng)看 GitHub: annotation-processor。