Java 注解(Annotation)

一. 背景

java中注解在java框架体系中有着广泛的的应用,小到JDK自带的override、Deprecated、supperwarning等注解,大到目前火热的SpringBoot框架, 还有Hibername, MyBatis-Plus等框架中, 都集成了自家的注解体系.
.
Java 注解(Annotation)又称 Java 标注,是 JDK5.0 引入的一种注释机制。
.
Java 语言中的类、方法、变量、参数和包等都可以被标注。和 Javadoc 不同,Java 标注可以通过反射获取标注内容。在编译器生成类文件时,标注可以被嵌入到字节码中。Java 虚拟机可以保留标注内容,在运行时可以获取到标注内容 。 当然它也支持自定义 Java 标注。

二. Java注解概览

2.1 Java中注解概览图

在这里插入图片描述

2.2 java内置的注解

Java 定义了一套注解,共有7 个,3 个在java.lang 中,剩下 4 个在 java.lang.annotation中。

作用在代码的注解是:

  • @Override - 检查该方法是否是重写方法。如果发现其父类,或者是引用的接口中并没有该方法时,会报编译错误。
  • @Deprecated - 标记过时方法。如果使用该方法,会报编译警告。
  • @SuppressWarnings - 指示编译器去忽略注解中声明的警告。

作用在其他注解的注解(或者说 元注解)是:

  • @Retention - 标识这个注解怎么保存,是只在代码中,还是编入class文件中,或者是在运行时可以通过反射访问。
  • @Documented - 标记这些注解是否包含在用户文档中。
  • @Target - 标记这个注解应该是哪种 Java 成员。
  • @Inherited - 标记这个注解是继承于哪个注解类(默认 注解并没有继承于任何子类)

从 Java 7 开始,额外添加了 3 个注解:

  • @SafeVarargs - Java 7 开始支持,忽略任何使用参数为泛型变量的方法或构造函数调用产生的警告。
  • @FunctionalInterface - Java 8 开始支持,标识一个匿名函数或函数式接口。
  • @Repeatable - Java 8 开始支持,标识某注解可以在同一个声明上使用多次。

三. 注解作用 & 示例

3.1 注解有什么用? 注解的作用基本有三个:

.生成文档。这是最常见的,也是java 最早提供的注解。常用的有 @see @param @return 等
.跟踪代码依赖性,实现替代配置文件功能。比较常见的是spring 2.5 开始的基于注解配置。作用就是减少配置。现在的框架基本都使用了这种配置来减少配置文件的数量。也是
.在编译时进行格式检查。如@override 放在方法前,如果你这个方法并不是覆盖了超类方法,则编译时就能检查出。

3.2 实现最简单的注解

@Retention(RetentionPolicy.RUNTIME)
public @interface MyTarget {
}

代码说明:
上面是一个最简单的注解实现,没有定义任何的属性,其中需要注意的是@Retention(RetentionPolicy.RUNTIME)是定义注解所必须的。
@Retention是注解的注解,称为注解的元注解。括号里有一个枚举类型的值,即为注解内部定义的值。打开Retention的实现:

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

代码说明:
可以看到这里定义了一个变量value并且没有缺省值,所以不写这个value就会报错。 继续打开RetentionPolicy:

public enum RetentionPolicy {
    SOURCE,
    CLASS,
    RUNTIME
}

可以发现这个枚举类定义了三个值,这三个值分别代表的是我们定义的MyTarget如何保持。

  • 用@Retention(RetentionPolicy.CLASS)修饰的注解,表示注解的信息被保留在class文件(字节码文件)中当程序编译时,但不会被虚拟机读取在运行的时候;
  • 用@Retention(RetentionPolicy.SOURCE )修饰的注解,表示注解的信息会被编译器抛弃,不会留在class文件中,注解的信息只会留在源文件中;
  • 用@Retention(RetentionPolicy.RUNTIME )修饰的注解,表示注解的信息被保留在class文件(字节码文件)中当程序编译时,会被虚拟机保留在运行时,

还需要注意的是java的元注解一共有四个:

@Document
@Target
@Retention
@Inherited

各个的作用,读者自己查一下吧。无论如何我们实现了一个最简单的注解MyTarget,下面进行简单的测试:

简单自定义注解清单:

package com.david.corejava.dirannocation.o1_basic;

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;

/**
 * User: Administrator
 * Date: 2020/4/14
 * Time: 17:46
 * Desc: 简单自定义注解
 */
@Retention(value = RetentionPolicy.RUNTIME)
public @interface MyTarget {
    
}

测试类清单:

package com.david.corejava.dirannotation.o1_basic;

import javax.annotation.Resource;
import java.lang.reflect.Method;

/**
 * User: Administrator
 * Date: 2020/4/14
 * Time: 17:47
 * Desc:
 */
public class TestDIYAnnotation {

    @MyTarget
    public void doSomething(){

    }

    @Resource
    public static void main(String[] args) throws NoSuchMethodException {
        Method method = TestDIYAnnotation.class.getDeclaredMethod("doSomething",null);
        if(method.isAnnotationPresent(MyTarget.class)){
            System.out.println(method.getAnnotation(MyTarget.class));
        }
    }

}

运行上面的测试会打出一句话:

@com.david.corejava.dirannotation.o1_basic.MyTarget()

核心代码解读:
if(method.isAnnotationPresent(MyTarget.class))

可以判断出方法是是不是存在MyTarget的注解,能打印出这句话证明我们写的确实是一个注解。

3.3 实现注解中的属性定义

上面的内容我们实现了一个最简单的注解,但是这个注解基本是没有什么功能的,java注解的功能实现基本是通过定义属性实现的(真正实现功能有相关的处理类,处置这些属性,我们先来定义属性)。注解处理器类库(java.lang.reflect.AnnotatedElement):

AnnotatedElement 接口是所有程序元素(Class、Method和Constructor)的父接口,所以程序通过反射获取了某个类的AnnotatedElement对象之后,程序就可以调用该对象的如下四个个方法来访问Annotation信息:

注解处理的一个基础:

  • 方法1: T getAnnotation(Class annotationClass): 返回改程序元素上存在的、指定类型的注解,如果该类型注解不存在,则返回null。
  • 方法2:Annotation[] getAnnotations():返回该程序元素上存在的所有注解。
  • 方法3:boolean is AnnotationPresent(Class<?extends Annotation> annotationClass):判断该程序元素上是否包含指定类型的注解,存在则返回true,否则返回false.
  • 方法4:Annotation[] getDeclaredAnnotations():返回直接存在于此元素上的所有注释。与此接口中的其他方法不同,该方法将忽略继承的注释。(如果没有注释直接存在于此元素上,则返回长度为零的一个数组。)该方法的调用者可以随意修改返回的数组;这不会对其他调用者返回的数组产生任何影响。

注解定义属性不同于java,看一下上面的Retention的定义属性方式:

RetentionPolicy value();

这基本就是注解定义属性的方式,类似于java中定义方法,可以设置缺省值,即用注解的时候缺省值是可以不写的。

为了窥探注解在实际场景中的应用, 我们以汽车销售中涉及到的几个实体(如下所示) 为例, 介绍自定义注解的使用方法.

  • 汽车销售员 ------ CarSalerTarget
  • 汽车中文名 ------ CarNameTarget
  • 汽车品牌 ------ CarTypeTarget

我们来看一下注解的功能之一: 为属性赋值

我们定义三个注解:
为了编写代码的方便, 三个注解放置在一个注解类里面进行编写.

package com.david.corejava.dirannotation.o2_advance;

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;

/**
 * User: Administrator
 * Date: 2020/4/14
 * Time: 17:56
 * Desc:
 */
@Retention(value = RetentionPolicy.RUNTIME)
public @interface CarSalerTarget {
    public String salerName();
    public int age() default 1;
}

@Retention(value = RetentionPolicy.RUNTIME)
@interface CarNameTarget {
    String name() default "";
}

@Retention(value = RetentionPolicy.RUNTIME)
@interface CarTypeTarget {
    enum Type {
        benchi,
        BMW,
        skoda,
        bentian;
    }

    Type carType();
}

下面我们定义一个奔驰汽车的类,试着用注解进行赋值:

package com.david.corejava.dirannotation.o2_advance;

/**
 * User: Administrator
 * Date: 2020/4/14
 * Time: 18:01
 * Desc:
 */
public class SkodaCar {
    @CarNameTarget(name = "斯柯达")
    private String name;

    @CarTypeTarget(carType = CarTypeTarget.Type.skoda)
    private String type;

    @CarSalerTarget(salerName = "David", age = 18)
    private String salerInfo;

}

这样其实就定义和赋值完成了,下面测试从自定义注解中赋值中, 将数据取出来:

package com.david.corejava.dirannotation.o2_advance;

import java.lang.reflect.Field;

/**
 * User: Administrator
 * Date: 2020/4/14
 * Time: 18:10
 * Desc:
 */
public class CarInfoUtil {

    public static void getCarInfo(Class<?> clazz) {
        Field[] fields = clazz.getDeclaredFields();

        for (Field field : fields) {
            if (field.isAnnotationPresent(CarSalerTarget.class)) {
                CarSalerTarget saler = field.getAnnotation(CarSalerTarget.class);
                System.out.println("销售员姓名: " + saler.salerName() + ", 销售员年龄: " + saler.age());
            } else if (field.isAnnotationPresent(CarNameTarget.class)) {
                CarNameTarget name = field.getAnnotation(CarNameTarget.class);
                System.out.println("汽车的中文名称为: " + name.name());
            } else if (field.isAnnotationPresent(CarTypeTarget.class)) {
                CarTypeTarget type = field.getAnnotation(CarTypeTarget.class);
                System.out.println("汽车的品牌为: " + type.carType().toString());
            }
        }
    }

}

下面构建测试类来测试自定义注解的使用情况是否达到预期:


这个类其实就是根据注解获取属性的值。

构建TestCase类:

IDEA 在待测试的类中, 右键 -> Go To -> Test -> Create New Test…

弹出的窗口中输入相关相关测试类的信息:在这里插入图片描述
编写实现代码:

import com.david.corejava.dirannotation.o2_advance.SkodaCar;
import com.david.corejava.dirannotation.o2_advance.CarInfoUtil;
import junit.framework.TestCase;

/**
 * User: Administrator
 * Date: 2020/4/14
 * Time: 18:20
 * Desc:
 */
public class GetJVMDefaultPropertiesTest extends TestCase {

    public void testTarget(){
        CarInfoUtil.getCarInfo(SkodaCar.class);
    }

}

执行测试代码,获取打印结果,如下:
在这里插入图片描述

四. 总结

由上可以看出, 配置自定义注解, 并且在注解里赋的值已经成功取出来了。
通过对如上简单示例的演示, 我们大致可以了解诸如SpringBoot, Hibernate, MyBatis 等框架中, 如何使用注解, 反射等方式, 进行数据的控制反转赋值(DI)操作。

参考列表:

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值