Java注解的深入理解以及超详细的使用方式

详细介绍了Java中的注解的概念、用法、以及案例演示,比如自定义注解。

1 注解的概念

1.1 什么是注解?

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

在这里插入图片描述

1.2 注解的用处

  1. 生成文档。这是最常见的,也是java 最早提供的注解。常用的有@param @return 等。
  2. 跟踪代码依赖性,实现替代配置文件功能。比如spring、mybatis等各种框架 依赖注入,未来java 开发,将大量注解配置,具有很大用处;
  3. 在编译时进行格式检查。如@override 放在方法前,如果你这个方法并不是覆盖了超类方法,则编译时就能检查出。

1.3 注解的原理

注解本质是一个继承了Annotation 的特殊接口,其具体实现类是Java 运行时生成的动态代理类。

我们通过反射获取注解时,返回的是Java 运行时生成的动态代理对象$Proxy1。通过代理对象调用自定义注解(接口)的方法,会最终调用AnnotationInvocationHandler 的invoke 方法。该方法会从memberValues 这个Map 中索引出对应的值。而memberValues 的来源是Java 常量池。

注解相关内部处理类,位于sun.reflect.annotation包。 在这里插入图片描述

1.4 基本语法

1.4.1 注解类型的声明部分

注解在Java中,与类、接口、枚举类似,因此其声明语法基本一致,只是所使用的关键字有所不同@interface。在底层实现上,所有定义的注解都会自动继承java.lang.annotation.Annotation接口。

1.4.2 注解类型的实现部分

根据我们在自定义类的经验,在类的实现部分无非就是书写构造、属性或方法。但是,在自定义注解中,其实现部分只能定义一个东西:注解类型元素(annotation type element)。

定义注解类型元素时需要注意如下几点:

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

可以看出,注解类型元素的语法非常奇怪,即又有属性的特征(可以赋值),又有方法的特征(打上了一对括号)。但是这么设计是有道理的,我们在后面的章节中可以看到:注解在定义好了以后,使用的时候操作元素类型像在操作属性,解析的时候操作元素类型像在操作方法。

2 常用的元注解

一个最最基本的注解定义就只包括了上面的两部分内容:1、注解的名字;2、注解包含的类型元素。

但是,我们在使用JDK自带注解的时候发现,有些注解只能写在方法上面(比如@Override);有些却可以写在类的上面(比如@Deprecated)。当然除此以外还有很多细节性的定义,那么这些定义该如何做呢?接下来就该元注解出场了!

元注解:专门修饰注解的注解。它们都是为了更好的设计自定义注解的细节而专门设计的。元注解位于 java.lang.annotation包中。

@Target

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Target {
    /**
     * Returns an array of the kinds of elements an annotation type
     * can be applied to.
     * @return an array of the kinds of elements an annotation type
     * can be applied to
     */
    ElementType[] value();
}

@Target注解,是专门用来限定某个自定义注解能够被应用在哪些Java元素上面的。如果在@Target注解中多次出现相同的枚举常量,那么这是一个编译时错误。

Target注解使用一个枚举类型定义如下可选的参数:

public enum ElementType {
    /** 类,接口(包括注解类型)或枚举的声明 */
    TYPE,
​
    /** 属性的声明 */
    FIELD,
​
    /** 方法的声明 */
    METHOD,
​
    /** 方法形式参数声明 */
    PARAMETER,
​
    /** 构造方法的声明 */
    CONSTRUCTOR,
​
    /** 局部变量声明 */
    LOCAL_VARIABLE,
​
    /** 注解类型声明 */
    ANNOTATION_TYPE,
​
    /** 包的声明 */
    PACKAGE
}

2.2 @Retention

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Retention {
    /**
     * Returns the retention policy.
     * @return the retention policy
     */
    RetentionPolicy value();
}

@Retention注解,翻译为持久力、保持力。即用来修饰自定义注解的生命力。

注解的生命周期有三个阶段:

  1. Java源文件阶段;
  2. 编译到class文件阶段;
  3. 运行期阶段。

同样使用了RetentionPolicy枚举类型定义了三个阶段:

public enum RetentionPolicy {
    /**
     * 注解将被编译器忽略掉
     */
    SOURCE,
​
    /**
     * 注解将被编译器记录在class文件中,但在运行时不会被虚拟机保留,这是一个默认的行为
     */
    CLASS,
​
    /**
     * 注解将被编译器记录在class文件中,而且在运行时会被虚拟机保留,因此它们能通过反射被读取到
     * @see java.lang.reflect.AnnotatedElement
     */
    RUNTIME
}
​

2.3 @Documented

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Documented {
}

@Documented注解,是被用来指定自定义注解是否能随着被定义的java文件生成到JavaDoc文档当中。

2.4 @Inherited

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Inherited {
}

@Inherited注解,是指定某个自定义注解如果写在了父类的声明部分,那么子类的声明部分也能自动拥有该注解。@Inherited注解只对那些@Target被定义为ElementType.TYPE的自定义注解起作用。

2.5 @Repeatable

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Repeatable {
    /**
     * Indicates the <em>containing annotation type</em> for the
     * repeatable annotation type.
     * @return the containing annotation type
     */
    Class<? extends Annotation> value();
}

@Repeatable表示注解是否是可重复。@Repeatable 是 Java 1.8 才加进来的。其参数是注解类型的Class,表示该注解可以重复使用。

2.6 @Native

@Documented
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.SOURCE)
public @interface Native {
}

表明一个字段引用的值可能来自于本地代码。是 Java 1.8 才加进来的,目前使用比较少。

3 自定义注解

3.1 基本类

首先,定义一个注解、和一个供注解修饰的简单Java类:

/**
 * Target:表示ManAnnotation被限定能使用在类、接口或方法上面
 * Documented:表示ManAnnotation可用于生成到JavaDoc文档中
 *
 * @author lx
 */
@Documented
@Target(value = {ElementType.TYPE, ElementType.METHOD})
public @interface ManAnnotation {
    String name();
​
    int age() default 20;
​
    String[] tel();
}
​
public class Student {
​
    @ManAnnotation(name = "stu", age = 15, tel = {"111111", "66666", "88888"})
    public void learn(int times) {
        for (int i = 0; i < times; i++) {
            System.out.println("Start learning");
        }
    }
}
​

3.2 特殊语法

特殊语法一:

如果注解本身没有注解类型元素,那么在使用注解的时候可以省略(),直接写为:@注解名,它和标准语法@注解名()等效!

@Retention(RetentionPolicy.RUNTIME)
@Target(value = {ElementType.TYPE})
@Documented
public @interface FirstAnnotation {
}
//等效于@FirstAnnotation()
@FirstAnnotation
public class JavaBean{
    //省略实现部分
}

特殊语法二:

如果注解本本身只有一个注解类型元素,而且命名为value,那么在使用注解的时候可以直接使用:@注解名(注解值),其等效于:@注解名(value = 注解值)

@Retention(RetentionPolicy.RUNTIME)
@Target(value = {ElementType.TYPE})
@Documented
public @interface ThirdAnnotation {
    String[] name();
}
//等效于@ ThirdAnnotation(name = {"this is third annotation"})
@ ThirdAnnotation(name = "this is third annotation")
public class JavaBean{
    //省略实现部分
}

特殊用法三:

如果注解中的某个注解类型元素是一个数组类型,在使用时又出现只需要填入一个值的情况,那么在使用注解时可以直接写为:@注解名(类型名 = 类型值),它和标准写法:@注解名(类型名 = {类型值})等效!

@Retention(RetentionPolicy.RUNTIME)
@Target(value = {ElementType.TYPE})
@Documented
public @interface ThirdAnnotation {
    String[] name();
}
//等效于@ ThirdAnnotation(name = {"this is third annotation"})
@ ThirdAnnotation(name = "this is third annotation")
public class JavaBean{
    //省略实现部分
}

特殊用法四:

如果一个注解的@Target是定义为Element.PACKAGE,那么这个注解是配置在package-info.java中的,而不能直接在某个类的package代码上面配置。

其他:

注解保持力的三个阶段:Java源文件阶段;编译到class文件阶段;运行期阶段。只有当注解的保持力处于运行阶段,即使用@Retention(RetentionPolicy.RUNTIME)修饰注解时,才能在JVM运行时,检测到注解,并进行一系列特殊操作。

3.3 反射操作获取注解

注解以及注解的类容可以通过反射获取。如果你不知道反射,可以看这篇博客:Java反射的深入理解以及超级详细的使用方式

/*获取Student的Class对象*/
Class<Student> stuClass = Student.class;

/*获取learn方法对象*/
Method learnMethod = stuClass.getDeclaredMethod("learn", int.class);
/*如果方法上有该注解类型,则获取注解*/
if (learnMethod.isAnnotationPresent(ManAnnotation.class)) {
    System.out.println("Student类上配置了ManAnnotation注解!");
    //获取该元素上指定类型的注解
    ManAnnotation cherryAnnotation = learnMethod.getAnnotation(ManAnnotation.class);
    System.out.println("name: " + cherryAnnotation.name() + ", age: " + cherryAnnotation.age()
                    + ", tel: " + cherryAnnotation.tel()[0]);
} else {
    System.out.println("Student类上没有配置ManAnnotation注解!");
}

解释一下

如果我们要获得的注解是配置在方法上的,那么我们要从Method对象上获取;如果是配置在属性上,就需要从该属性对应的Field对象上去获取,如果是配置在类型上,需要从Class对象上去获取。总之在谁身上,就从谁身上去获取!

isAnnotationPresent(Class<? extends Annotation> annotationClass)方法是专门判断该元素上是否配置有某个指定的注解;

getAnnotation(Class< T > annotationClass)方法是获取该元素上指定的注解。之后再调用该注解的注解类型元素方法就可以获得配置时的值数据;

反射对象上还有一个方法getAnnotations(),该方法可以获得该对象身上配置的所有的注解。它会返回给我们一个注解数组,需要注意的是该数组的类型是Annotation类型,这个Annotation是一个来自于java.lang.annotation包的接口。

  • 4
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

刘Java

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值