Java 中注解的使用

简介

本文介绍Java中注解的用法,需要有反射相关的知识内容,不熟悉反射的建议先看反射相关内容后再看本篇文章会更容易理解。

1. 什么是注解?

注解(Annotation),也叫元数据。一种代码级别的说明。简单理解就是注解就是给机器看的注释,那给机器看到这些注释当然就可以再编译时期和运行时期去干些事情。
所以,接下来我们从以下几个步骤来介绍

  1. java 注释用 // 来表示,那注解怎么写?有什么规范?
  2. 注解既然是机器看的,那机器怎么看到注解?

2. 定义注解

注解的定义和类的定义有点相似,类的定义用class关键字,注解使用@interface来定义,所有的注解都是java.lang.annotation.Annotation接口的子类,JDK中注解相关的类和接口都定义java.lang.annotation包中,注解和类一样,也可以定义属性,不过语法上有差异,具体规则如下:

  • 访问修饰符必须为public,不写默认为public
  • 该元素的类型只能是基本数据类型、String、Class、枚举类型、注解类型(体现了注解的嵌套效果)以及上述类型的一位数组
  • 该元素的名称一般定义为名词,如果注解中只有一个元素,通常把名字起为value
  • 属性名称后面的()不是定义方法参数的地方,也不能在括号中定义任何参数,仅仅只是一个特殊的语法
  • default代表默认值,值必须和第2点定义的类型一致
  • 如果没有默认值,代表后续使用注解时必须给该类型元素赋值

如下定义一个注解

public @interface MyAnnotation {
	//定义一个属性
	String value(); 
	//定义属性指定默认值
	String name() default "";
}

3. Java 中内置的元注解

java 中的元注解用在注解上边,用来定义注解的使用规则。

3.1. @Target 指定注解使用位置

标注注解使用位置,比如注解可用在类上、方法上等,不指定默认可用在任何地方,源码如下

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Target {
    ElementType[] value();
}
package java.lang.annotation;
/*注解的使用范围*/
public enum ElementType {
       /*类、接口、枚举、注解上面*/
    TYPE,
    /*字段上*/
    FIELD,
    /*方法上*/
    METHOD,
    /*方法的参数上*/
    PARAMETER,
    /*构造函数上*/
    CONSTRUCTOR,
    /*本地变量上*/
    LOCAL_VARIABLE,
    /*注解上*/
    ANNOTATION_TYPE,
    /*包上*/
    PACKAGE,
    /*类型参数上*/
    TYPE_PARAMETER,
    /*类型名称上*/
    TYPE_USE
}

3.2. @Retention 指定注解生命周期

先来看一下java程序的3个过程

  1. 源码阶段
  2. 源码被编译为字节码之后变成class文件
  3. 字节码被虚拟机加载然后运行

@Retention 的作用就是指定注解会在以上哪些时期存在,对应源码如下

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Retention {
    RetentionPolicy value();
}
public enum RetentionPolicy {
    /*注解只保留在源码中,编译为字节码之后就丢失了,也就是class文件中就不存在了*/
    SOURCE,
    /*注解只保留在源码和字节码中,运行阶段会丢失*/
    CLASS,
    /*源码、字节码、运行期间都存在*/
    RUNTIME
}

3.3. @Inherited 允许子类继承父类上的注解

注解继承只针对class 级别注解有效

示例代码:类UseAnn 可以继承UseAnnParent 上的@MyAnnotation 注解,因为@MyAnnotation 注解上使用@Inherited 标注

@Documented
@Inherited
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotation {
    String value();
}

@MyAnnotation(value = "用在类上")
public class UseAnnParent{

}


public class UseAnn extends UseAnnParent{

}

3.4. @Repeatable 表示注解可以在一个位置重复使用

@Repeatable 使用时需要指定注解容器,如下,定义一个@MyAnnotations 注解,里面有一个MyAnnotation[] 属性,并且@MyAnnotations 生命周期要大于等于@MyAnnotation 生命周期。

示例代码:

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.FIELD})
@interface MyAnnotations {
    MyAnnotation[] value(); 
}
@Documented
@Inherited
@Retention(RetentionPolicy.RUNTIME)
@Repeatable(MyAnnotations.class)
public @interface MyAnnotation {
    String value();

}

4.使用注解

前面我们一直在说注解的定义,接下来终于介绍到该怎么获取使用了

4.1. AnnotatedElement 获取注解信息

java.lang.reflect.AnnotatedElement接口提供的方法可以获取注解信息,常用API如下:

  • boolean isAnnotationPresent(Class<? extends Annotation> annotationClass) 判断是否有注解
  • T getAnnotation(Class annotationClass) 获取指定类型注解
  • Annotation[] getAnnotations(); 获取所有注解,包括从父类继承的。
  • T[] getAnnotationsByType(Class annotationClass)
  • Annotation[] getDeclaredAnnotations(); 返回此元素上的注解,不包括父类上的注解

以下类都实现了java.lang.reflect.AnnotatedElement接口 ,所以以下类型都可以直接获取到注解信息

  • Package:用来表示包的信息
  • Class:用来表示类的信息
  • Constructor:用来表示构造方法信息
  • Field:用来表示类中属性信息
  • Method:用来表示方法信息
  • Parameter:用来表示方法参数信息
  • TypeVariable:用来表示类型变量信息,如:类上定义的泛型类型变量,方法上面定义的泛型类型变量

来个简单的例子

    @Test
    public void test(){
        Class<UseAnn> clazz = UseAnn.class;
        for (Annotation annotation: clazz.getAnnotations()){
            // 执行逻辑,比如判断该类上有注解,然后获取对应注解信息,进行下一步操作
            System.out.println(annotation);
        }
    }

4.2. Spring 对注解的支持

我们发现子类可以继承父类上的注解,JDK提供的java.lang.reflect.AnnotatedElement 工具也只能获取父类上的注解,就是解决了类的继承关系,那注解上的注解该怎么获取使用?接下来看Spring中对这个问题的解决

  • @AliasFor 注解
    这个注解可以和 @Inherited 对照理解, @Inherited 是java子类可以继承获取java父类上的注解,@AliasFor注解是设置注解上的注解属性

  • AnnotatedElementUtils 是Spring 提供的一个工具,用来查找注解,并且可以查找注解上的注解

来个代码例子:

1.先定义一个注解

@Documented
@Inherited
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotation01 {
    String value01() default "";
}

2.再定义一个注解,用上边定义的注解MyAnnotation01标注,使用@AliasFor 注解 对value01 标注,当我们使用注解设置MyAnnotation.value01 属性的同时会设置@MyAnnotation01.value01 的属性。

@Documented
@Inherited
@Retention(RetentionPolicy.RUNTIME)
@MyAnnotation01
public @interface MyAnnotation {
    String value();
    @AliasFor(annotation = MyAnnotation01.class,value = "value01")
    String value01();
}

接下来测试

@MyAnnotation(value = "用在类上", value01 = "用在类上02")
public class UseAnn{

}

    @Test
    public void test(){
        for (Annotation annotation: UseAnn.class.getAnnotations()){
            System.out.println(annotation);
        }

        MyAnnotation01 myAnnotation01= AnnotatedElementUtils.getMergedAnnotation(UseAnn.class,MyAnnotation01.class);

        System.out.println(myAnnotation01);
    }

输出结果

@com.wangjunxin.simple.annotations.MyAnnotation(value=用在类上, value01=用在类上02)
@com.wangjunxin.simple.annotations.MyAnnotation01(value01="用在类上02")

看结果发现我们并没有设置MyAnnotation01属性,但是MyAnnotation01的属性有值, @AliasFor 用法一目了然。

5. 最后

AnnotatedElementUtilsjava.lang.reflect.AnnotatedElement 工具只能获取到运行时期的注解,我们平常自定义的注解都使用 @Retention(RetentionPolicy.RUNTIME) ,如果想了解其他两个生命周期的,可以看下 mapstruct 框架(用来复制属性的框架)、lombok (帮值我们自动注入set/get方法)

以上就是本文全部内容啦,自己写代码练习理解会更深。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值