神奇的Annotation

来源:互联网 发布:淘宝打假赚钱 编辑:程序博客网 时间:2024/05/21 18:42

什么是元数据(metadata)

元数据由metadata译来,所谓的元数据就是“关于数据的数据”,更通俗的说就是描述数据的数据,对数据及信息资源的描述性信息.比如说一个文本文件,有创建时间,创建人,文件大小等数据,这都可以理解为是元数据.

在java中,元数据以标签的形式存在java代码中,它的存在并不影响程序代码的编译和执行,通常它被用来生成其它的文件或运行时知道被运行代码的描述信息。java当中的javadoc和注解都属于元数据.

什么是注解(Annotation)?

注解是从java 5.0开始加入,可以用于标注包,类,方法,变量等.比如我们常见的@Override,再或者Android源码中的@hide,@systemApi,@privateApi等

对于@Override,多数人往往都是知其然而不知其所以然,今天我就来聊聊Annotation背后的秘密,开始正文.

元注解

元注解就是定义注解的注解,是java提供给我们用于定义注解的基本注解.在java.lang.annotation包中我们可以看到目前元注解共有以下几个:

  1. @Retention
  2. @Target
  3. @Inherited
  4. @Documented
  5. @interface

下面我们将集合@Override注解来解释着5个基本注解的用法.

@interface

@interface是java中用于声明注解类的关键字.使用该注解表示将自动继承java.lang.annotation.Annotation类,该过程交给编译器完成.

因此我们想要定义一个注解只需要如下做即可,以@Override注解为例

public @interface Override {}

需要注意:在定义注解时,不能继承其他注解或接口.

@Retention

@Retention:该注解用于定义注解保留策略,即定义的注解类在什么时候存在(源码阶段 or 编译后 or 运行阶段).该注解接受以下几个参数: RetentionPolicy.SOURCE,RetentionPolicy.CLASS,RetentionPolicy.RUNTIME ,其具体使用及含义如下:

注解保留策略含义@Retention(RetentionPolicy.SOURCE)注解仅在源码中保留,class文件中不存在@Retention(RetentionPolicy.CLASS)注解在源码和class文件中都存在,但运行时不存在,即运行时无法获得,该策略也是默认的保留策略@Retention(RetentionPolicy.RUNTIME)注解在源码,class文件中存在且运行时可以通过反射机制获取到

来看一下@Override注解的保留策略:

@Retention(RetentionPolicy.SOURCE)public @interface Override {}

这表明@Override注解只在源码阶段存在,javac在编译过程中去去掉该注解.

@Target

该注解用于定义注解的作用目标,即注解可以用在什么地方,比如是用于方法上还是用于字段上,该注解接受以下参数:

作用目标含义@Target(ElementType.TYPE)用于接口(注解本质上也是接口),类,枚举@Target(ElementType.FIELD)用于字段,枚举常量@Target(ElementType.METHOD)用于方法@Target(ElementType.PARAMETER)用于方法参数@Target(ElementType.CONSTRUCTOR)用于构造参数@Target(ElementType.LOCAL_VARIABLE)用于局部变量@Target(ElementType.ANNOTATION_TYPE)用于注解@Target(ElementType.PACKAGE)用于包

以@Override为例,不难看出其作用目标为方法:

@Target(ElementType.METHOD)public @interface Override {}

到现在,通过@interface,@Retention,@Target已经可以完整的定义一个注解,来看@Override完整定义:

@Target(ElementType.METHOD)@Retention(RetentionPolicy.SOURCE)public @interface Override {}

@Inherited

默认情况下,我们自定义的注解用在父类上不会被子类所继承.如果想让子类也继承父类的注解,即注解在子类也生效,需要在自定义注解时设置@Inherited.一般情况下该注解用的比较少.

@Documented

该注解用于描述其它类型的annotation应该被javadoc文档化,出现在api doc中.

比如使用该注解的@Target会出出现在api说明中.

@Documented@Retention(RetentionPolicy.RUNTIME)@Target(ElementType.ANNOTATION_TYPE)public @interface Target {    ElementType[] value();}

借助@Interface,@Target,@Retention,@Inherited,@Documented这五个元注解,我们就可以自定义注解了,其中前三个注解是任何一个注解都必备具备的.

你以为下面会直接来将如何自定义注解嘛?不,你错了,我们还是来聊聊java自带的几个注解.

系统注解

java设计者已经为我们自定义了几个常用的注解,我们称之为系统注解,主要是这三个:

系统注解含义@Override用于修饰方法,表示此方法重写了父类方法@Deprecated用于修饰方法,表示此方法已经过时@SuppressWarnnings该注解用于告诉编译器忽视某类编译警告

如果你已经完全知道这三者的用途,跳过这一小节,直接往下看.

@Override

它用作标注方法,说明被标注的方法重写了父类的方法,其功能类似断言.如果在一个没有重写父类方法的方法上使用该注解,java编译器将会以一个编译错误提示:

@Deprecated

当某个类型或者成员使用该注解时意味着

编译器不推荐开发者使用被标记的元素.另外,该注解具有”传递性”,子类中重写该注解标记的方法,尽管子类中的该方法未使用该注解,但编译器仍然报警.

public class SimpleCalculator {    @Deprecated    public int add(int x, int y) {        return x+y;    }}public class MultiplCalculator extends SimpleCalculator {    // 重写SimpleCalculator中方法,但不使用@Deprecated    public int add(int x, int y) {        return  Math.abs(x)+Math.abs(y);    }}//test codepublic class Main {    public static void main(String[] args) {        new SimpleCalculator().add(3, 4);        new MultiplCalculator().add(3,5);    }}

对于像new SimpleCalculator().add(3,4)这种直接调用的,Idea会直接提示,而像第二种则不是直接提示:

但是在编译过程中,编译器都会警告:

需要注意@Deprecated和@deprecated这两者的区别,前者被javac识别和处理,而后者则是被javadoc工具识别和处理.因此当我们需要在源码标记某个方法已经过时应该使用@Deprecated,如果需要在文档中说明则使用@deprecated,因此可以这么:

public class SimpleCalculator {    /**     * @param x     * @param y     * @return     *      * @deprecated deprecated As of version 1.1,     * replace by <code>SimpleCalculator.add(double x,double y)</code>     */    @Deprecated    public int add(int x, int y) {        return x+y;    }    public double add(double x,double y) {        return x+y;    }}

@SuppressWarnning

该注解被用于有选择的关闭编译器对类,方法,成员变量即变量初始化的警告.该注解可接受以下参数:

参数含义deprecated使用已过时类,方法,变量unchecked执行了未检查的转告时的警告,如使用集合是为使用泛型来制定集合保存时的类型fallthrough使用switch,但是没有break时path类路径,源文件路径等有不存在的路径serial可序列化的类上缺少serialVersionUID定义时的警告finally任何finally字句不能正常完成时的警告all以上所有情况的警告

滋溜一下,我们飞过了2016年,不,是看完了上一节.继续往下飞.

自定义注解

了解完系统注解之后,现在我们就可以自己来定义注解了,通过上面@Override的实例,不难看出定义注解的格式如下:

public @interface 注解名 {定义体}

定义体就是方法的集合,每个方法实则是声明了一个配置参数.方法的名称作为配置参数的名称,方法的返回值类型就是配置参数的类型.和普通的方法不一样,可以通过default关键字来声明配置参数的默认值.

需要注意:

  1. 此处只能使用public或者默认的defalt两个权限修饰符
  2. 配置参数的类型只能使用基本类型(byte,boolean,char,short,int,long,float,double)和String,Enum,Class,annotation
  3. 对于只含有一个配置参数的注解,参数名建议设置中value,即方法名为value
  4. 配置参数一旦设置,其参数值必须有确定的值,要不在使用注解的时候指定,要不在定义注解的时候使用default为其设置默认值,对于非基本类型的参数值来说,其不能为null.

像@Override这样,没有成员定义的注解称之为标记注解.

现在我们来自定义个注解@UserMeta,这个注解目前并没啥用,就是为了演示一番:

@Documented@Target(ElementType.CONSTRUCTOR)@Retention(RetentionPolicy.RUNTIME)public @interface UserMeta {    public int id() default 0;    public String name() default "";    public int age() default ;}

有了米饭,没有筷子没法吃啊(手抓饭的走开),下面来看看如何处理注解.

注解处理器

上面我们已经学会了如何定义注解,要想注解发挥实际作用,需要我们为注解编写相应的注解处理器.根据注解的特性,注解处理器可以分为运行时注解处理和编译时注解处理器.运行时处理器需要借助反射机制实现,而编译时处理器则需要借助APT来实现.

无论是运行时注解处理器还是编译时注解处理器,主要工作都是读取注解及处理特定注解,从这个角度来看注解处理器还是非常容易理解的.

先来看看如何编写运行时注解处理器.

运行时注解处理器

熟悉java反射机制的同学一定对java.lang.reflect包非常熟悉,该包中的所有api都支持读取运行时Annotation的能力,即属性为@Retention(RetentionPolicy.RUNTIME)的注解.

在java.lang.reflect中的AnnotatedElement接口是所有程序元素的(Class,Method)父接口,我们可以通过反射获取到某个类的AnnotatedElement对象,进而可以通过该对象提供的方法访问Annotation信息,常用的方法如下:

方法含义<T extends Annotation> T getAnnotation(Class<T> annotationClass)返回该元素上存在的制定类型的注解Annotation[] getAnnotations()返回该元素上存在的所有注解default <T extends Annotation> T[] getAnnotationsByType(Class<T> annotationClass)返回该元素指定类型的注解default <T extends Annotation> T getDeclaredAnnotation(Class<T> annotationClass)返回直接存在与该元素上的所有注释default <T extends Annotation> T[] getDeclaredAnnotationsByType(Class<T> annotationClass)返回直接存在该元素岸上某类型的注释Annotation[] getDeclaredAnnotations()返回直接存在与该元素上的所有注释

编写运行时注解大体就需要了解以上知识点,下面来做个小实验.

简单示例

首先我们用一个简单的实例来介绍如何编写运行时注解处理器:我们的系统中存在一个User实体类:

public class User {    private int id;    private int age;    private String name;    @UserMeta(id=1,name="dong",age = 10)    public User() {    }    public User(int id, int age, String name) {        this.id = id;        this.age = age;        this.name = name;    }  //...省略setter和getter方法    @Override    public String toString() {        return "User{" +                "id=" + id +                ", age=" + age +                ", name='" + name + '\'' +                '}';    }}

我们希望可以通过 @UserMeta(id=1,name="dong",age = 10) (这个注解我们在上面提到了)来为设置User实例的默认值。

自定义注解类如下:

@Retention(RetentionPolicy.RUNTIME)@Target(ElementType.CONSTRUCTOR)public @interface UserMeta {    public int id() default 0;    public String name() default "";    public int age() default 0;}

该注解类作用于构造方法,并在运行时存在,这样我们就可以在运行时通过反射获取注解进而为User实例设值,看看如何处理该注解吧.

运行时注解处理器:

public class AnnotationProcessor {    public static void init(Object object) {        if (!(object instanceof User)) {            throw new IllegalArgumentException("[" + object.getClass().getSimpleName() + "] isn't type of User");        }        Constructor[] constructors = object.getClass().getDeclaredConstructors();        for (Constructor constructor : constructors) {            if (constructor.isAnnotationPresent(UserMeta.class)) {                UserMeta userFill = (UserMeta) constructor.getAnnotation(UserMeta.class);                int age = userFill.age();                int id = userFill.id();                String name = userFill.name();                ((User) object).setAge(age);                ((User) object).setId(id);                ((User) object).setName(name);            }        }    }}

测试代码:

public class Main {    public static void main(String[] args) {        User user = new User();        AnnotationProcessor.init(user);        System.out.println(user.toString());    }}

运行测试代码,便得到我们想要的结果:

User{id=1, age=10, name=’dong’}

这里通过反射获取User类声明的构造方法,并检测是否使用了@UserMeta注解。然后从注解中获取参数值并将其赋值给User对象。

正如上面提到,运行时注解处理器的编写本质上就是通过反射获取注解信息,随后进行其他操作。编译一个运行时注解处理器就是这么简单。运行时注解通常多用于参数配置类模块。

2 0
原创粉丝点击