Java 註解 Annotation

不少開源庫都用到了註解的方式來簡化代碼提高開發效率。
本文簡單介紹下 Annotation 示例、概念及作用、分類、自定義、解析,並對幾個 Android 開源庫 Annotation 原理進行簡析

1. Annotation 示例

Override Annotation

@Override
public void onCreate(Bundle savedInstanceState);

Retrofit Annotation

@GET("/users/{username}")
User getUser(@Path("username") String username);

Butter Knife Annotation

@InjectView(R.id.user) EditText username;

ActiveAndroid Annotation

@Column(name = “Name") public String name;

Retrofit 爲符合 RESTful 規範的網絡請求框架
Butter Knife 爲 View 及事件等依賴注入框架
Active Android 爲 ORM 框架

2. Annotation 概念及作用

2.1 概念

An annotation is a form of metadata, that can be added to Java source code. Classes, methods, variables, parameters and packages may be annotated. Annotations have no direct effect on the operation of the code they annotate.

能夠添加到 Java 源代碼的語法元數據。類、方法、變量、參數、包都可以被註解,可用來將信息元數據與程序元素進行關聯。Annotation 中文常譯爲“註解”。

2.2 作用

a. 標記,用於告訴編譯器一些信息
b. 編譯時動態處理,如動態生成代碼
c. 運行時動態處理,如得到註解信息
這裏的三個作用實際對應着後面自定義 Annotation 時說的 @Retention 三種值分別表示的 Annotation

public class Person {

    private int    id;
    private String name;

    public Person(int id, String name) {
        this.id = id;
        this.name = name;
    }

    public boolean equals(Person person) {
        return person.id == id;
    }

    public int hashCode() {
        return id;
    }

    public static void main(String[] args) {

        Set<Person> set = new HashSet<Person>();
        for (int i = 0; i < 10; i++) {
            set.add(new Person(i, "Jim"));
        }
        System.out.println(set.size());
    }
}

上面的運行結果是多少?

3. Annotation 分類

3.1 標準 Annotation,Override, Deprecated, SuppressWarnings

標準 Annotation 是指 Java 自帶的幾個 Annotation,上面三個分別表示重寫函數,不鼓勵使用(有更好方式、使用有風險或已不在維護),忽略某項 Warning

3.2 元 Annotation,@Retention, @Target, @Inherited, @Documented

元 Annotation 是指用來定義 Annotation 的 Annotation,在後面 Annotation 自定義部分會詳細介紹含義

3.3 自定義 Annotation

自定義 Annotation 表示自己根據需要定義的 Annotation,定義時需要用到上面的元 Annotation
這裏是一種分類而已,也可以根據作用域分爲源碼時、編譯時、運行時 Annotation,後面在自定義 Annotation 時會具體介紹

4. Annotation 自定義

4.1 調用

public class App {

    @MethodInfo(
        author = “[email protected]”,
        date = "2014/02/14",
        version = 2)
    public String getAppName() {
        return "trinea";
    }
}

這裏是調用自定義 Annotation——MethodInfo 的示例。
MethodInfo Annotation 作用爲給方法添加相關信息,包括 author、date、version。

4.2 定義

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@Inherited
public @interface MethodInfo {

    String author() default "[email protected]";

    String date();

    int version() default 1;
}

這裏是 MethodInfo 的實現部分
(1). 通過 @interface 定義,註解名即爲自定義註解名
(2). 註解配置參數名爲註解類的方法名,且:
a. 所有方法沒有方法體,沒有參數沒有修飾符,實際只允許 public & abstract 修飾符,默認爲 public,不允許拋異常
b. 方法返回值只能是基本類型,String, Class, annotation, enumeration 或者是他們的一維數組
c. 若只有一個默認屬性,可直接用 value() 函數。一個屬性都沒有表示該 Annotation 爲 Mark Annotation
(3). 可以加 default 表示默認值

4.3 元 Annotation

@Documented 是否會保存到 Javadoc 文檔中
@Retention 保留時間,可選值 SOURCE(源碼時),CLASS(編譯時),RUNTIME(運行時),默認爲 CLASS,SOURCE 大都爲 Mark Annotation,這類 Annotation 大都用來校驗,比如 Override, SuppressWarnings
@Target 可以用來修飾哪些程序元素,如 TYPE, METHOD, CONSTRUCTOR, FIELD, PARAMETER 等,未標註則表示可修飾所有
@Inherited 是否可以被繼承,默認爲 false

5. Annotation 解析

5.1 運行時 Annotation 解析

(1) 運行時 Annotation 指 @Retention 爲 RUNTIME 的 Annotation,可手動調用下面常用 API 解析

method.getAnnotation(AnnotationName.class);
method.getAnnotations();
method.isAnnotationPresent(AnnotationName.class);

其他 @Target 如 Field,Class 方法類似
getAnnotation(AnnotationName.class) 表示得到該 Target 某個 Annotation 的信息,因爲一個 Target 可以被多個 Annotation 修飾
getAnnotations() 則表示得到該 Target 所有 Annotation
isAnnotationPresent(AnnotationName.class) 表示該 Target 是否被某個 Annotation 修飾
(2) 解析示例如下:

public static void main(String[] args) {
    try {
        Class cls = Class.forName("cn.trinea.java.test.annotation.App");
        for (Method method : cls.getMethods()) {
            MethodInfo methodInfo = method.getAnnotation(
MethodInfo.class);
            if (methodInfo != null) {
                System.out.println("method name:" + method.getName());
                System.out.println("method author:" + methodInfo.author());
                System.out.println("method version:" + methodInfo.version());
                System.out.println("method date:" + methodInfo.date());
            }
        }
    } catch (ClassNotFoundException e) {
        e.printStackTrace();
    }
}

以之前自定義的 MethodInfo 爲例,利用 Target(這裏是 Method)getAnnotation 函數得到 Annotation 信息,然後就可以調用 Annotation 的方法得到響應屬性值

5.2 編譯時 Annotation 解析

(1) 編譯時 Annotation 指 @Retention 爲 CLASS 的 Annotation,甴編譯器自動解析。需要做的
a. 自定義類集成自 AbstractProcessor
b. 重寫其中的 process 函數
這塊很多同學不理解,實際是編譯器在編譯時自動查找所有繼承自 AbstractProcessor 的類,然後調用他們的 process 方法去處理
(2) 假設 MethodInfo 的 @Retention 爲 CLASS,解析示例如下:

@SupportedAnnotationTypes({ "cn.trinea.java.test.annotation.MethodInfo" })
public class MethodInfoProcessor extends AbstractProcessor {

    @Override
    public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment env) {
        HashMap<String, String> map = new HashMap<String, String>();
        for (TypeElement te : annotations) {
            for (Element element : env.getElementsAnnotatedWith(te)) {
                MethodInfo methodInfo = element.getAnnotation(MethodInfo.class);
                map.put(element.getEnclosingElement().toString(), methodInfo.author());
            }
        }
        return false;
    }
}

SupportedAnnotationTypes 表示這個 Processor 要處理的 Annotation 名字。
process 函數中參數 annotations 表示待處理的 Annotations,參數 env 表示當前或是之前的運行環境
process 函數返回值表示這組 annotations 是否被這個 Processor 接受,如果接受後續子的 rocessor 不會再對這個 Annotations 進行處理

6. 幾個 Android 開源庫 Annotation 原理簡析

6.1 Annotation — Retrofit

(1) 調用

@GET("/users/{username}")
User getUser(@Path("username") String username);

(2) 定義

@Documented
@Target(METHOD)
@Retention(RUNTIME)
@RestMethod("GET")
public @interface GET {
  String value();
}

從定義可看出 Retrofit 的 Get Annotation 是運行時 Annotation,並且只能用於修飾 Method
(3) 原理

private void parseMethodAnnotations() {
    for (Annotation methodAnnotation : method.getAnnotations()) {
    Class<? extends Annotation> annotationType = methodAnnotation.annotationType();
    RestMethod methodInfo = null;

    for (Annotation innerAnnotation : annotationType.getAnnotations()) {
        if (RestMethod.class == innerAnnotation.annotationType()) {
            methodInfo = (RestMethod) innerAnnotation;
            break;
        }
    }
    ……
    }
}   

RestMethodInfo.java 的 parseMethodAnnotations 方法如上,會檢查每個方法的每個 Annotation, 看是否被 RestMethod 這個 Annotation 修飾的 Annotation 修飾,這個有點繞,就是是否被 GET、DELETE、POST、PUT、HEAD、PATCH 這些 Annotation 修飾,然後得到 Annotation 信息,在對接口進行動態代理時會掉用到這些 Annotation 信息從而完成調用。

Retrofit 原理涉及到動態代理,這裏原理都只介紹 Annotation

6.2 Annotation — Butter Knife

(1) 調用

@InjectView(R.id.user) 
EditText username;

(2) 定義

@Retention(CLASS) 
@Target(FIELD)
public @interface InjectView {
  int value();
}

可看出 Butter Knife 的 InjectView Annotation 是編譯時 Annotation,並且只能用於修飾屬性
(3) 原理

@Override 
public boolean process(Set<? extends TypeElement> elements, RoundEnvironment env) {
    Map<TypeElement, ViewInjector> targetClassMap = findAndParseTargets(env);

    for (Map.Entry<TypeElement, ViewInjector> entry : targetClassMap.entrySet()) {
        TypeElement typeElement = entry.getKey();
        ViewInjector viewInjector = entry.getValue();

        try {
            JavaFileObject jfo = filer.createSourceFile(viewInjector.getFqcn(), typeElement);
            Writer writer = jfo.openWriter();
            writer.write(viewInjector.brewJava());
            writer.flush();
            writer.close();
        } catch (IOException e) {
            error(typeElement, "Unable to write injector for type %s: %s", typeElement, e.getMessage());
        }
    }

    return true;
}

ButterKnifeProcessor.java 的 process 方法如上,編譯時,在此方法中過濾 InjectView 這個 Annotation 到 targetClassMap 後,會根據 targetClassMap 中元素生成不同的 class 文件到最終的 APK 中,然後在運行時調用 ButterKnife.inject(x) 函數時會到之前編譯時生成的類中去找。

6.3 Annotation — ActiveAndroid

(1) 調用

@Column(name = “Name") 
public String name;

(2) 定義

@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Column {
  ……
}

可看出 ActiveAndroid 的 Column Annotation 是運行時 Annotation,並且只能用於修飾屬性。
(3) 原理

Field idField = getIdField(type);
mColumnNames.put(idField, mIdName);

List<Field> fields = new LinkedList<Field>(ReflectionUtils.getDeclaredColumnFields(type));
Collections.reverse(fields);

for (Field field : fields) {
    if (field.isAnnotationPresent(Column.class)) {
        final Column columnAnnotation = field.getAnnotation(Column.class);
        String columnName = columnAnnotation.name();
        if (TextUtils.isEmpty(columnName)) {
            columnName = field.getName();
        }

        mColumnNames.put(field, columnName);
    }
}

TableInfo.java 的構造函數如上,運行時,得到所有行信息並存儲起來用來構件表信息。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章