Java中注解(Annotation)的使用

注解的定义

注解,顾名思义,就是给程序打上一些标签(标签中包含了信息),以便在开发时根据这些标签获取重要的信息,根据获取的信息从而动态对程序的运行产生期望的结果。注解可以大幅度简化开发,故很多开发框架都使用了注解。注解的定义语法非常简单,代表一个注解的关键字是@interface,就是接口的关键字前面加一个@符号

@TestAnnotation //在类上使用注解
public class Test {
    @TestAnnotation //在成员变量上使用注解
    private int i;
    @TestAnnotation //在成员变量上使用注解
    private String s;
}
//定义注解
@interface TestAnnotation{
}

上面我们就定义了一个最简单的注解并使用了一下,但是这个注解里面空空如也,实际上注解里面是可以定义属性的,定义属性的格式是:属性类型 属性名()。如果有多个属性,用;分隔。注意,这里属性名后面加了一对小括号,但是并不是表示方法。下面我们尝试给刚才定义的TestAnnotation注解添加2个属性:

@TestAnnotation(f1 = 1,f2 = "class") //注解中必须显式对属性f1和f2赋值
public class Test {
    @TestAnnotation(f1 = 2,f2 = "field")
    private int i;
    @TestAnnotation(f1 = 3,f2 = "field")
    private String s;
}

@interface TestAnnotation{
   int f1(); //定义int类型的属性f1
   String f2(); //定义String类型的属性f2
}

可以看出,我们给注解定义了属性后,在使用注解时必须给所有属性显式赋值,否则编译报错。其实我们也可以在定义属性时用default关键字声明默认值,这样就可以在使用注解时不给属性显式赋值,代表使用默认值:

@TestAnnotation(f2 = "class") //使用注解时没有显式指定f1的值,代表使用默认值1
public class Test {
    @TestAnnotation(f1 = 2,f2 = "field")
    private int i;
    @TestAnnotation(f1 = 3,f2 = "field")
    private String s;
}

@interface TestAnnotation{
   int f1() default 1; //定义int类型的属性f1,并声明默认值为1
   String f2();
}

再补充一点,如果注解中只有1个属性,那么如果这个属性的名称为“value”,在使用注解给属性赋值时可以不写属性名,直接写属性值:

@TestAnnotation(1) //使用注解时指定value的值,无需写属性名value和=符号,直接写属性值即可
public class Test {
    @TestAnnotation(2)
    private int i;
    @TestAnnotation(3)
    private String s;
}

@interface TestAnnotation{
   int value(); //定义int类型的属性value
}

元注解

上面我们在使用注解的时候,把注解用到了类和方法上,那么如何限定注解的作用范围呢?那就需要用到元注解了。元注解的含义是标记在注解上的注解,最重要的元注解有下面2种:

  • @Target:表示注解可以标记在哪些地方,比如类、属性、接口、方法等等,它限定了注解的使用范围。我们看一下它的源码:
@Documented
@Retention(RetentionPolicy.RUNTIME)  
@Target(ElementType.ANNOTATION_TYPE) //标明Target注解只能作用在注解上面
public @interface Target {
    ElementType[] value(); //value属性,是一个ElementType类型的数组
}

分析下这段源码,可以看出注解Target中只有1个value属性,类型是ElementType数组。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,

    /**
     * Module declaration.
     * @since 9
     */
    MODULE
}

可以看出ElementType是一个枚举类,它的各种取值代表了Target注解可以作用于哪些地方。了解了这些之后,反过来再看Target注解的源码,发现Target给它自己的Class定义加上了一个@Target(ElementType.ANNOTATION_TYPE) 的注解,表示Target只能作用于注解。进而我们可以得出结论,任何元注解的类定义上必然有一个@Target(ElementType.ANNOTATION_TYPE) ,因为元注解只能作用于注解

  • @Retention:它表示注解的保留时间,源码如下:
@Documented
@Retention(RetentionPolicy.RUNTIME)  //表示Retention注解运行时仍然保留
@Target(ElementType.ANNOTATION_TYPE) //标明Retention注解只能作用在注解上面
public @interface Retention {
    RetentionPolicy value();  //value属性,RetentionPolicy
}

可以看出,Retention注解中只有1个value属性,类型为RetentionPolicy。同时由于Retention是1个元注解,它被标注了一个@Target(ElementType.ANNOTATION_TYPE)。
我们再看一下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,  //注解会被记录到编译生成的.class文件中,但是运行时不会被加载到JVM中。这是默认的保留策略

    /**
     * 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  //注解会被记录到编译生成的.class文件中,同时运行时会被加载到JVM中,因此在程序运行时可以通过反射获取到注解信息,这是我们自定义注解最常用的保留策略
}

可以看出RetentionPolicy是一个枚举类,它代表注解的保留策略,一共有3种取值。其中我们自定义注解最常用的是RetentionPolicy.RUNTIME,因为自定义注解通常目的是在运行的时候获取注解信息,从而控制程序行为,而只有RUNTIME策略会在运行时保留注解信息,从而能通过反射获取注解信息

常见的JDK内置注解

接下来我们看几个常见的JDK内置注解,用前面的知识来分析一下它们

  • @Deprecated:表示已过时
@Documented
//运行时保留
@Retention(RetentionPolicy.RUNTIME) 
//可以作用于构造方法、属性、局部参数、方法、包、模块、参数、类、接口、枚举
@Target(value={CONSTRUCTOR, FIELD, LOCAL_VARIABLE, METHOD, PACKAGE, MODULE, PARAMETER, TYPE}) 

public @interface Deprecated {
    String since() default "";  //since属性,表示在哪个版本后已过时
    boolean forRemoval() default false; //forRemoval,表示是否会在后续版本中删除
}

日常我们感觉好像@Deprecated一般都用于标记方法,但实际上它的作用范围非常广,可以是构造方法、属性、局部参数、方法、包、模块、参数、类、接口、枚举等等

  • @Override:表示子类对父类方法的重写
@Target(ElementType.METHOD)  //作用于方法
@Retention(RetentionPolicy.SOURCE) //只在源码中保留,编译时抛弃
public @interface Override {
}

可以很容易分析出,由于@Override作用是标识子类对父类方法的重写,所以标记@Target(ElementType.METHOD)让它只作用于方法即可。同时它只是在源码阶段做一个重写的标志,无需在运行时获取,故标记@Retention(RetentionPolicy.SOURCE)源码保留即可。

  • @FunctionalInterface:表示函数式接口,即只有1个抽象方法的接口
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface FunctionalInterface {}

由于它作用于接口,故标记为@Target(ElementType.TYPE)

注解的实际使用举例

考虑一种场景:我们在使用json数据的时候,一般会将json格式的字符串映射为javabean对象,但是一些特殊的json数据难以直接形成这种映射,如{“1”:“baobao”,“2”:18},这个json字符串无法直接对应到javabean,因为java类的属性不能命名为1,2这样的数字。这时,我们就可以自定义一个注解,给javabean中的属性名打上注解,并将json的属性名信息包含在注解中就可以了。看具体代码:

@Retention(RetentionPolicy.RUNTIME) //运行时保留
@Target(ElementType.FIELD)  //注解作用于类的成员变量
public @interface MyAnnotation {
    //定义jsonFieldName属性,代表bean属性对应的json属性名
    String jsonFieldName() default "";
}

这里我们定义了一个注解MyAnnotation,并将其作用域声明为ElementType.FIELD,并给它定义了一个jsonFieldName属性,代表bean属性所对应的json格式字符串属性名。然后我们定义一个javabean去应用这个注解:

public class Person {
    //给属性name添加注解,代表对应json对象的名为"1"
    @MyAnnotation(jsonFieldName = "1")
    private String name;
    //给属性age添加注解,代表对应json对象的名为"2"
    @MyAnnotation(jsonFieldName = "2")
    private int age;

    public Person(){}

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

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }
}

可以看到我们给Person类的name和age属性都标记上了我们自定义注解MyAnnotation,并且将name属性的注解设置jsonFieldName = “1”,age属性的注解设置jsonFieldName = “2”,这样name和age属性就分别和json格式字符串{“1”:“baobao”,“2”:18}中的属性名对应上了。但是这样还仅仅是对应上,还需要在运行时通过反射获取注解信息,然后做相应的操作:

public class AnnotationResolver {
    public static void main(String[] args) throws Exception {
        //反射Person类
        Class<?> clazz = Class.forName("baobao.annotation.Person");
        Object instance = clazz.newInstance();
        //遍历Person的所有属性
        for (Field field : clazz.getDeclaredFields()) {
            //打印属性名
            System.out.println("field = " + field.getName());
            //判断属性上是否有MyAnnotation注解
            if (field.isAnnotationPresent(MyAnnotation.class)){
                //得到注解
                MyAnnotation annotation = field.getAnnotation(MyAnnotation.class);
                //得到注解中属性的值
                String jsonFieldName = annotation.jsonFieldName();
                System.out.println("jsonFieldName = " + jsonFieldName);

                //进行json属性映射到Person bean属性的操作。。。。
            }
        }
    }
}

这里专门写了一个AnnotationResolver类对注解进行解析,主要思路是利用反射获取Person类的属性,然后判断属性上是否有MyAnnotation注解,如果有,得到注解并获取注解中jsonFieldName属性的值,这样一来Person的属性和json的属性名就对应起来了,接下来只要根据jsonFieldName(“1”,“2”)获取json格式字符串中对应的属性值(“baobao”,18),将这些属性值赋给Person对应的属性(name,age)值就可以了。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值