注解 Annotation 详解

艾宁
2023-12-01

JDK1.5之后,引入了元数据的概念,也就是Annotation(注解),其实它是代码里的特殊标记,这些标记可以在编译、类加载、运行时被读取,并执行相应的处理。可以帮助程序在编译的时候进行检查

注解的作用有以下三个:
1. 编写文档:通过代码里标识的注解生成文档。
2. 代码分析:通过代码里标识的注解对代码进行分析。
3. 编译检查:通过代码里标识的注解让编译器能实现基本的编译检查。

几种常见的注解:

Markerannotation 标识型的注解,程序见到了这种注解就知道你要干什么,所以会对你下面的内容进行检查
a) @Override 注解表示子类要重写(override)父类的对应方法。
b) @Deprecated 注解表示方法是不建议被使用的。
c) @SuppressWarnings(“unchecked”) 注解表示抑制unchecked类型的警告。 如果你给一个类压制了某种警告,在类的某个方法中压制了另一种警告,则在该方法中会压制这两种警告

自定义注解:@interface 标识
当注解中的属性名为 value 时,在对其赋值时可以不指定属性的名称
而直接写上属性值即可;除了value 以外的其他值都需要使用 name=value这种赋值方式,即明确指定给谁赋值。

使用的时候,直接 @AnnotationTest,
有属性的时候要为属性复制,下面的例子的调用必须这样调用 @AnnotationTest(“aaa”)

// 表示自定义了一个注解
public @interface  AnnotationTest {
    // 注解中特殊的用法,定义了一个字符串,在外面使用的时候要为value赋值, 也可以设置一个默认值
    // 而且value这个名字还有特殊之处,
    String value() default "hello";

}

当注解里面有两个属性的时候,要为两个属性都赋值,并且指定所有的属性名字 @AnnotationTest(value1=”aa”, value = “aaa”)

在使用注解的时候,实际上已经自动继承了 java.lang.annotation.Annotation接口,但继承了这个接口,并不意味着就是一个注解,注解的定义必须是通过 @interface 关键字定义的

Annotation 本身是接口而不是注解。可以与Enum 类比,Enum也是继承自Enum类

4个标准的meta-annotation类型,它们被用来提供对其它 annotation类型作说明。
1.@Target, 定义自定义的注解可以修饰的是类,或者成员方法,或者成员变量 或者 参数等等, 限定了自定义的注解的修饰权限,指示自定义的注解可以修饰哪些对象。

Target和下面的Retention 有些类似,都需要实现一个枚举类型的形态定义。

// 定义了下面自定义的注解类型只能修饰成员方法 和 类接口枚举类型
@Target({ElementType.METHOD,ElementType.TYPE})
public @interface  AnnotationTest {
}

public  enum  ElementType{
    ANNOTATION_TYPE,//   注解
    CONSTRUCTOR , // 构造函数
    FIELD , // 成员变量
    LOCAL_VARIABLE ,  //  局部变量
    METHOD , // 成员方法
    PACKAGE ,  //  包
    PARAMETER , //  参数
    TYPE , //  Class, interface (including annotation type), or enum declaration
   TYPE_PARAMETER,  //  Type parameter declaration 
    TYPE_USE  // Use of a type 
}

2.@Retention, 里面有一个属性 value, 类型RetentionPolicy型, 是可以在我们自己自定义的注解中,指示编译器应该如何对待我们自定义的注解 Annotation
在使用Retention形态时,需要提供 java.lang.annnotation.RetentionPolicy的枚举型态,

public enum RetentionPolicy{
   SOURCE,  // 编译器处理完Annotation信息后就完成任务,不会被编译到.class文件中,只是在源文件中 @SuppressWarnings,@Override的级别就是source
   CLASS, // 默认情况下的编译器对注解的处理情况, 编译器会将注解信息留在.class 文件中,但是不会被java的虚拟机读取,仅仅用于在编译程序或者工具程序运行时提供信息
   RUNTIME // 编译程序将注解保留在.class文件中,也可以由JVM读取(通过反射的方式读取),以便在程序分析时能够分析注解的情况  @Deprecated的级别就是RUNTIME
}
@Retention(RetentionPolicy.CLASS)
public @interface  AnnotationTest {
    String value();
}

上面的代码可以说明注解是可以修饰注解的

当指定编译器处理注解Annotation的保留级别是RUNTIME时,注解可以被JVM通过反射的方式读取出来 它的读取方法如下:

需要注意的是: 只有注解中被定义为RUNTIME时,才能被反射所获取到。

@AnnotationTest(value = "aaa", value1 = "bbb")
public class AnnotationUse {
    @AnnotationTest(value = "a", value1 = "b")
    @Deprecated // 指示这个类是不被建议使用的
    @SuppressWarnings("uncheckd")
    public void annotation(){
        System.out.println("annotation");
    }

    public static void main(String[] args) throws NoSuchMethodException, SecurityException, IllegalAccessException, IllegalArgumentException, InvocationTargetException {
        // 实现获得annotation上面的注解信息
        AnnotationUse annotest = new AnnotationUse();
        Class<?> classType = annotest.getClass();

        // 获取该类上的所有注解
        classType.getAnnotations();

        Method method = classType.getMethod("annotation", new Class[]{});

        // 获得method上的所有注解, 并打印 ,
        //  获得的注解并不包含它的上一层(类上)所写的注解, 虽然上一层类的注解也同样的限制了这个方法
        //  同时只能获取 注解中的Retention类型定义为为RUNTIME的注解,
        Annotation[] anno = method.getAnnotations();  
        for (Annotation an: anno){
            System.out.println(an.annotationType().getName());
            // Annotation.AnnotationTest
            //java.lang.Deprecated       结果只有一个注解的名字,
            //  这是因为@SuppressWarnings("uncheckd")注解的类型是 SOURCE,
            //  它无法被加载到JVM上,因此不能通过反射来得到
        }

        // 判断在method方法上是否有AnnotationTest这个注解
        if (method.isAnnotationPresent(AnnotationTest.class)){ 
            method.invoke(annotest, new Object[]{});  // 输出为 annotation
            //  获取注解,并得到注解的内容
            AnnotationTest  an = method.getAnnotation(AnnotationTest.class);
            an.value();
            an.value1();
        }

    }
}

3.@Documented,
定义了一个注解能够通过javadoc 或者其他工具被写入到文件中去

4.@Inherited
加上@Inherited,定义了一个父类的注解,如果被继承,那么子类中也继承了这个注解,
默认情况下, 父类的注解是不能被子类所继承的

JUnit : java实现单元测试的一个框架, 有两种经典的版本
3.8(通过反射实现)
4.x( 反射加上注解实现)

JUnit 山的名言: keep the bar green to keep the code clean (调试的结果是绿色的,代码就是良好的)

视频老师的名言: 如果没有反射,很多框架都不存在了

要使用这个框架,需要先导入jar包,jar包就是许多.class 文件的一个打包

JUnit3.8 里面的测试函数必须以 test开头,否则程序不允许执行,这是因为JUnit是通过反射实现的,获取类名,通过类名获取方法名,在使用方法的invoke方法执行方法,因此你的使用的方法名必须符合他的规则

import junit.framework.TestCase;
public class TestJunit3_8 extends TestCase{
    public void  testHello(){
        System.out.println("helloworld");
    }
}

JUnit4.x 通过注解的方式,方法的名字就不必要以test开头, 里面的执行流程大致如下
首选通过反射获得类名,然后通过类名获得方法名,然后获得方法名上的注解,判断该注解上是否有Test注解,如果有,则调用Method上的invoke方法。
所以可以猜测,Test注解的 Target 为ElsemetType.METHOD
Retention 的 RetentionPolicy.RUNTIME

JUnit4 的执行的一般流程:
a) 首先获得待测试类所对应的 Class对象。
b) 然后通过该 Class对象获得当前类中所有 public 方法所对应的 Method 数组。
c) 遍历该Method数组,取得每一个Method对象
d) 调用每个Method 对象的isAnnotationPresent(Test.class)方法,判断该方法是否被 Test 注解所修饰。
e) 如果该方法返回true,那么调用method.invoke()方法去执行该方法,否则不执行。

单元测试不是为了证明你是对的,而是证明你没有错误。

JUnit 使用进行单元测试是比较简单的,困难的是 测试用例的设计
在一本书 Writing Secure Code(编写安全的代码) 中:
要假设: Input is evil。 用户是邪恶的。

import org.junit.Test;
public class TestJunit4_x {
    @Test
    public void hello(){
        System.out.println("hello");
    }
}
 类似资料: