什么是注解
-
所有寫過Java的都應(yīng)該見過見過@Override、@Deprecated這種“神奇的”語法吧,比如
@Override public String toString() { ... }再比如說Android里面的ButterKnife也是通過這種方式來“自動”的完成View的查找,再比如說Retrofit也用到了這種語法,當(dāng)我們用一些優(yōu)雅的第三方庫的時候,往往會用到注解來很方便的實現(xiàn)一些功能,模糊的去講,注解是可以用到parameters、field、method、class、annotation上的一種特殊的語法,比如說在Retrofit里面就體現(xiàn)的淋漓盡致
@GET("/get/") Call<Response<String>> getSomething(@Query String param);
注解之元注解
- 元注解就是Java提供的為我們自定義注解而提供的內(nèi)置注解,其實就相當(dāng)于Java中的關(guān)鍵字,他們是內(nèi)置的,有了這些關(guān)鍵字我們才能寫自己的代碼
- 元注解有四個:
- @Target
- @Retention
- @Inherited
- @Documented
@Target
Target也就是注解可以修飾的類型,所有的類型都在ElementType下面,比如ElementType.CLASS代表可以修飾類、ElementType.TYPE代表可以修飾任何類型、ElementType.ANNOTATION_TYPE代表這個注解可以修飾注解,其他的可以自行去查看文檔
-
提前先說一下,每一個注解都可以定義一個value的屬性,作為默認的注解值,另外在注解中如果使用數(shù)組類型,我們可以僅使用一個值,Java會自動把單個值包裝成一個數(shù)組對象,例如
@Target(ElementType.CLASS) public @interface MyAnnotation {}或者多個值
@Target({ElementType.CLASS, ElementType.Field}) public @interface MyAnnotation {}下面自定義還會細講一些
@Retention
- Retention表示了這個注解存在的時間,其所有類型在RetentionPolicy下,只有三種類型
- SOURCE: 表示只在源碼中存在,也就是說這個注解會在javac編譯時抹去,它的作用就是在編譯的時候做一些靜態(tài)檢查,比如@Override
- CLASS: 表示會在class文件中存在,但是會被VM忽略,也就是說無法通過反射動態(tài)獲取注解的值,這也是默認行為,我們可以從class文件中找到這些信息,也就意味著可以自定義類加載器,然后可以在原始IO中找到對應(yīng)信息(ClassLoader.findClass),只是通過class文件在永生區(qū)/元空間生成class對象的時候,classLoader對應(yīng)的jni方法defineClass(注:在Java8上響應(yīng)的方法是defineClass0、defineClass1、defineClass2,在Android上是defineClass)會忽略這樣的Annotation;同時,我們可以通過decompile獲得這些注解信息
- RUNTIME: 表示在運行時也會存在,也就是我們可以在代碼中動態(tài)獲取到這個注解和注解的值,當(dāng)然獲取的方式是通過反射
@Inherited
- 如果定義注解時使用了Inherited,那么當(dāng)我們使用這個注解去修飾類A的時候,即使我們不對A的子類使用注解,但是我們依舊能通過子類獲取到這個注解,也就是說@Inherited意味著自定義的注解是可以繼承的,當(dāng)我們嘗試從某個類拿Inherited的注解時,會不停的去它的superClass中尋找。但是記住這種可以繼承的特性只對類生效,也就是說Override的方法是不生效的
@Documented
- 如果使用@Documented修飾了某個自定義注解,那這個注解在生成Javadoc時會自動保留,即添加在對應(yīng)的方法/變量/…上
使用注解
基礎(chǔ)知識
- 在注解中只能使用方法,但是注解的方法在使用的時候和普通字段一樣
- 注解中的value字段有特殊含義,是默認賦值字段
- 定義注解時可以使用數(shù)組類型,而且當(dāng)使用時可以只穿入單個值,java會自動把它包裝成一個長度為1的數(shù)組
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.SOURCE)
public @interface MyAnnotation {
int[] value();
String name() default "";
}
public class Person {
@MyAnnotation(1)
int age;
@MyAnnotation(name="John")
String name;
}
編譯時注解
- 可以使用RetentionPolicy.SOURCE或者RetentionPolicy.CLASS來達到編譯時注解的目的
- 這種編譯期的注解主要在于自動生成代碼,比如Android上的ButterKnife
- 編譯期注解的優(yōu)點在于消耗的時間完全在編譯期,在運行時不需要額外的時間,但是缺點也是很明顯的,首先因為要自動生成代碼(元編程),編寫這種代碼其實還不如反射寫起來舒服(因為反射的時候好歹可以直接獲取到某個變量的值),其次就是很多情況下要求字段必須不能是private的,或者更確切的說生成的代碼的位置必須是可以訪問到注解所修飾的字段的,(參見ButterKnife),當(dāng)然你也可以把生成的代碼插入字段所在的源碼文件中,就不用管它是不是private了
- 栗子(我直接在Android Studio上做的實驗,Intellij Idea沒有找到怎么讓AbstractProcessor生效):
//MyProcessor: 繼承AbstractProcessor, 都是接口類型Processor, 這個類其實很好理解, 是java為我們提供
//的接口,當(dāng)javac編譯的時候會調(diào)用你配置好的Processor類去進行編譯期注解的處理, 不然你只定義Annotation誰
//知道應(yīng)該怎么處理它呢?所以我們必須要自己實現(xiàn)Processor,也叫注解處理器
//這個類做的事情其實是生成StaticAnnotation修飾的<變量名字, 類名>的json
@SupportedSourceVersion(SourceVersion.RELEASE_7)
public class MyProcessor extends AbstractProcessor {
@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
File file = new File("output.json");
System.out.println("------------");
try {
for (TypeElement element : annotations) {
System.out.print(element.getSimpleName() + " ");
}
Set<? extends Element> elements = roundEnv.getElementsAnnotatedWith(StaticAnnotation.class);
System.out.println(elements.size());
if (elements.size() == 0) return false;
Writer writer = new BufferedWriter(new FileWriter(file));
writer.append("{\n");
for (Element element : elements) {
System.out.println(element.getSimpleName());
System.out.println(element.getKind() == ElementKind.FIELD);
if (element.getKind() == ElementKind.FIELD) {
writer.append(element.getSimpleName());
writer.append(": ");
writer.append(element.getEnclosingElement().getSimpleName());
writer.append("\n");
}
}
writer.append("}\n");
writer.flush();
writer.close();
} catch (IOException e) {
e.printStackTrace();
}
return true;
}
//聲明可以處理的注解的類型,添加到set里的必須是全名
@Override
public Set<String> getSupportedAnnotationTypes() {
Set<String> set = new HashSet<>();
set.add(StaticAnnotation.class.getCanonicalName());
return set;
}
}
//自定義的注解
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.SOURCE)
public @interface StaticAnnotation {
int value();
}
//測試類
public class Student {
@StaticAnnotation(3)
String name;
@StaticAnnotation(2)
boolean isMale;
}
注:
- 在Android上使用時,AbstractProcessor在Android module下是獲取不到的,要建立一個java module,然后在main下面建立META-INF/services/javax.annotation.processing.Processor,再向里面添加對應(yīng)的Processor的全名
- @SupportedAnnotationTypes就是對getSupportAnnotationTypes()的簡寫,@SupportedSourceVersion對應(yīng)getSupportedSourceVersion()
- 我個人認為很多情況下使用RetentionPolicy.SOURCE就已經(jīng)足夠了,畢竟很多情況下我們只是生成完代碼之后就用不著注解了
運行時注解
- Retention對應(yīng)的就是RUNTIME,Retrofit就是使用的這種注解,運行時注解在VM中也會保留,所以我們可以通過反射方法拿到Annotation(Method、Field、Class都有g(shù)etAnnotation方法)
- 運行時的注解就不像編譯時的那么麻煩了,因為它不需要Processor這種"驅(qū)動",它的優(yōu)點就是靈活,缺點就是反射導(dǎo)致效率低,不過在網(wǎng)絡(luò)請求中,反射效率再怎么低都是要比網(wǎng)絡(luò)延遲要好很多的
- 栗子
public class TestClass {
@MyAnnotation(1)
private int value;
public static void main(String[] args) {
TestClass instance = new TestClass();
Class clazz = instance.getClass();
try {
Field field = clazz.getDeclaredField("value");
MyAnnotation annotation = field.getAnnotation(MyAnnotation.class);
Object o = field.get(instance);
o = annotation.name();
field.setInt(instance, annotation.name()[0]);
} catch (NoSuchFieldException | IllegalAccessException e) {
e.printStackTrace();
}
System.out.println(instance.value);
}
}
@Target({ElementType.METHOD, ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotation {
int[] name() default 0;
int[] value();
}
上述例子利用注解對相應(yīng)的實例變量進行了賦值,雖然反射也很煩,但是還是要比編譯時生成代碼寫起來要更舒服一些的(也許是我對反射比元編程更習(xí)慣一些吧)