【转】公共技术点之 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 框架。更多见:Android 开源项目汇总

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()); 
    }
}

上面的运行结果是多少? 10

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 = “trinea.cn+android@gmail.com”, 
        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 "trinea@gmail.com"; 
    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,具体原理分析请见 Android 开源项目实现原理解析

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) 函数时会到之前编译时生成的类中去找。 这里原理都只介绍 Annotation,具体原理分析请见Android 开源项目实现原理解析

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 的构造函数如上,运行时,得到所有行信息并存储起来用来构件表信息。
这里原理都只介绍 Annotation,具体原理分析请见 Android 开源项目实现原理解析

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,761评论 5 460
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,953评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,998评论 0 320
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,248评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,130评论 4 356
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,145评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,550评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,236评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,510评论 1 291
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,601评论 2 310
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,376评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,247评论 3 313
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,613评论 3 299
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,911评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,191评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,532评论 2 342
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,739评论 2 335

推荐阅读更多精彩内容