再谈JAVA注解和反射

最近在研究阿里开源的ARouter框架,碰到了较多注解的使用。结合ButterKnife开源框架的使用,越来越体会到熟练使用注解的重要性。故针对注解再次进行学习和总结,重要的是辅助代码来验证。

Annotation注解

Annontation是Java5开始引入的新特征,中文名称叫注解。它提供了一种安全的类似注释的机制,用来将任何的信息或元数据(metadata)与程序元素(类、方法、成员变量等)进行关联。为程序的元素(类、方法、成员变量)加上更直观更明了的说明,这些说明信息是与程序的业务逻辑无关,并且供指定的工具或框架使用。Annontation像一种修饰符一样,应用于包、类型、构造方法、方法、成员变量、参数及本地变量的声明语句中。

Java注解是附加在代码中的一些元信息,用于一些工具在编译、运行时进行解析和使用,起到说明、配置的功能。注解不会也不能影响代码的实际逻辑,仅仅起到辅助性的作用。包含在 java.lang.annotation 包中。

JDK内置注解

常用的标准注解有:

  1. @Override,主要用于在子类中覆盖父类中的方法
  2. @Deprecated,用来标志被弃用的代码,编译器会进行警告
  3. @SuppressWarnings,用于忽略编译器的警告信息。
    相信大家在日常工作中经常使用以上三种注解。

元注解

开发人员可以根据自己的需要自定义注解,这时需要用到@Retention, @Target, @Inherited, @Documented这几个元注解。

  1. @Rentention定义注解的保留级别。取值范围是RetentionPolicy类型,源码定义如下:
public enum RetentionPolicy {
    /**
     * Annotations are to be discarded by the compiler.
     */
    SOURCE,

    /**
     * Annotations are to be recorded in the class file by the compiler
     * but need not be retained by the VM at run time.  This is the default
     * behavior.
     */
    CLASS,

    /**
     * Annotations are to be recorded in the class file by the compiler and
     * retained by the VM at run time, so they may be read reflectively.
     *
     * @see java.lang.reflect.AnnotatedElement
     */
    RUNTIME
}

包括SOURCE:在代码编写阶段存在,编译时被丢弃。如@Override
CLASS:编译阶段注解保留在class文件中,VM运行时不需要保留。一般用来动态生成java代码,如ARouter,ButterKnife框架
RUNTIME:编译阶段注解保留在class文件中,VM运行时仍保留注解。一般与反射结合使用,如Retrofit。

  1. @Target定义注解可以用于什么对象。取值范围是ElementType类型,源码定义如下:
public enum ElementType {
    /** Class, interface (including annotation type), or enum declaration */
    TYPE,

    /** Field declaration (includes enum constants) */
    FIELD,

    /** Method declaration */
    METHOD,

    /** Formal parameter declaration */
    PARAMETER,

    /** Constructor declaration */
    CONSTRUCTOR,

    /** Local variable declaration */
    LOCAL_VARIABLE,

    /** Annotation type declaration */
    ANNOTATION_TYPE,

    /** Package declaration */
    PACKAGE,

    /**
     * Type parameter declaration
     *
     * @since 1.8
     */
    TYPE_PARAMETER,

    /**
     * Use of a type
     *
     * @since 1.8
     */
    TYPE_USE
}

其中常用的类型有TYPE:类、接口(包括注解)、枚举
FIELD:成员变量
METHOD:成员方法
PARAMETER:方法参数
ANNOTATION_TYPE:注解的注解

  1. @Documented标志注解将包含在Javadoc中

  2. @Inherited允许子类继承父类中的注解

关于每种元注解的具体定义,可以在Android Studio中方便的查看源码文件。reading the fuck code!

自定义注解

开发人员可以使用上面的元注解来定义自己的注解。示例如下:

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface RuntimeAnno {
    String value();

    boolean isAlive() default false;
}

使用@interface定义注解,类名即为注解名。自定义注解中只能定义方法,所有的方法均没有参数,也没有修饰符(默认是public&abstract修饰符),方法的返回值是基本类型、String、Classs、Annotation、Enum或者对应的一位数组。方法可以通过default设置默认值。

注解使用时,如果只有一个方法,则可直接“注解名(值)”来使用;如果有多个方法,则需要依次赋值“注解名(方法名=值,方法名=值...)”。如果方法使用default赋值,则不必在使用时显示赋值。

    @RuntimeAnno(value="11111")
    @RuntimeAnno("22222")
    @RuntimeAnno(value="33333", isAlive = true)

典型的注解定义如下图:


Override注解定义
ButterKnife BindView注解定义
ButterKnife OnClick注解定义

其中,ButterKnife的OnClick注解使用了自定义注解ListenerClass,具体定义可参考ButterKnife源码。

运行时(RUNTIME)注解的解析

以Method为例,参考Method的源码,可以看到有如下几个方法:

    /**
     * {@inheritDoc}
     * @throws NullPointerException  {@inheritDoc}
     * @since 1.5
     */
    public <T extends Annotation> T getAnnotation(Class<T> annotationClass) {
        return super.getAnnotation(annotationClass);
    }

    /**
     * {@inheritDoc}
     * @since 1.5
     */
    public Annotation[] getDeclaredAnnotations()  {
        return super.getDeclaredAnnotations();
    }

    /**
     * {@inheritDoc}
     * @since 1.5
     */
    @Override
    public Annotation[][] getParameterAnnotations() {
        // Android-changed: This is handled by Executable.
        return super.getParameterAnnotationsInternal();
    }

其中getAnnotation(AnnotationName.class) 可获取该 Target 某个 Annotation 的信息;因为一个 Target 可以被多个 Annotation 修饰.
getAnnotations() 则可获取该 Target 所有 Annotation. getParameterAnnotations可以获取方法的所有参数的注解,返回值是一个二维数组。

其他Target,如Filed,Class,获取Annotation的方法可参考java源码。

高版本的jdk还有isAnnotationPresent(AnnotationName.class) 方法,来判断Target 是否被某个 Annotation 修饰。

编译时(CLASS)注解解析

编译器解析编译时注解,需要做的是1、自定义类继承AbstractProcessor;2、重写process函数。具体使用方法,可参考网上的其他文档。

ButterKnife就是使用编译时注解,生成JAVA文件。

注解的作用

1、JDK标准注解,可用来生成JavaDoc,如@param @return;可用来进行代码检查,如@Override,@Deprecated。
2、目前较多框架使用注解,来自动生成代码或替代配置文件的功能。注解可以使编码更简洁,学习注解可以理解使用开源框架,甚至自定义框架来解决问题。
3、注解和反射配合使用,可以使用简洁的代码实现复杂的功能,笔者目前的网络库入参就采用了注解,动态代理和反射技术。可以参考反射注解与动态代理综合使用,该文档写的简单易懂。

参考文档:
注解Annotation实现原理与自定义注解例子
反射注解与动态代理综合使用
框架开发之Java注解的妙用

推荐阅读更多精彩内容

  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 157,643评论 24 688
  • 前言 现在在我们构建自己或公司的项目中,或多或少都会依赖几个流行比较屌的第三方库,比如:Butter Knife、...
    戴定康阅读 2,798评论 0 17
  • 时间的齿轮转到了2018年,回首往昔,不知道你的感受是满足呢?还是懊悔? 我想,大多数人都是悔不当初的,奈何被岁月...
    诺feiyang阅读 118评论 0 0
  • 互联网赚钱的套路,就是用内容把精准客户引导到个人微信上,然后用催眠系统,加上激活系统来快速变现成交。 微信我们称之...
    知聊阅读 175评论 0 0
  • 兑为口,为说,不同的语言,不同语气给对方的不一样的感觉,互相交流,互相赞叹,我们把向善的说出来。 兑还有喜悦的意思...
    菜问妈妈阅读 52评论 0 0