注解

Java 注解用于为 Java 代码提供元数据。作为元数据,注解不直接影响你的代码执行,但也有一些类型的注解实际上可以用于这一目的。Java 注解是从 Java1.5 开始添加到 Java 的。(比较抽象的概念)

初学者可以这样理解注解:想像代码具有生命,注解就是对于代码中某些鲜活个体的贴上去的一张标签。简化来讲,注解如同一张标签。(引用某篇博主观点,通俗易懂!!)

https://blog.csdn.net/shengzhu1/article/details/81271409

下面创建了一个注解,那么注解的的使用方法是什么呢?

@interface Test{
}
@Test()
public class Cmath{
}

创建一个类 Cmath,然后在类定义的地方加上 @Test 就可以用 Testn 注解这个类了。

你可以简单理解为将 Test 这张标签贴到 Camth这个类上面。

不过,要想注解能够正常工作,还需要介绍一下一个新的概念那就是元注解。

元注解: 注解的注解  

元注解是可以注解到注解上的注解,或者说元注解是一种基本注解,但是它能够应用到其它的注解上面。

如果难于理解的话,你可以这样理解。元注解也是一张标签,但是它是一张特殊的标签,它的作用和目的就是给其他普通的标签进行解释说明的。

查看Target源码:   注解的作用目标(修饰方法、类、还是属性?) 

常见的修饰:

  • ElementType.TYPE:允许被修饰的注解作用在类、接口和枚举上
  • ElementType.FIELD:允许作用在属性字段上
  • ElementType.METHOD:允许作用在方法上
  • ElementType.PARAMETER:允许作用在方法参数上
  • ElementType.CONSTRUCTOR:允许作用在构造器上
  • ElementType.LOCAL_VARIABLE:允许作用在本地局部变量上
  • ElementType.ANNOTATION_TYPE:允许作用在注解上
  • ElementType.PACKAGE:允许作用在包上
@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();
}

查看Retention源码:注解的生命周期(保存到运行时还是编译时或者永久?)  默认注解保存到编译时期

  • RetentionPolicy.SOURCE:当前注解编译期可见,不会写入 class 文件
  • RetentionPolicy.CLASS:类加载阶段丢弃,会写入 class 文件
  • RetentionPolicy.RUNTIME:永久保存,可以反射获取(被保存在class字节码文件中,并被JVM读取)
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Retention {
    /**
     * Returns the retention policy.
     * @return the retention policy
     */
    RetentionPolicy value();
}

@Decumented:描述注解是否被抽取到api文档中

@Inherited:描述注解是否被子类继承

注解的属性

注解的属性也叫做成员变量。注解只有成员变量,没有方法。注解的成员变量在注解的定义中以“无形参的方法”形式来声明,其方法名定义了该成员变量的名字,其返回值定义了该成员变量的类型。

@interface Test{
    String value();
    int id();
}
@Test(value ="数学",id=20)

上面代码定义了 Test 这个注解中拥有 id 和 value两个属性。在使用的时候,我们应该给它们进行赋值。

value这个参数名字,对所有注解来说,比较特殊,是默认的注解参数名字。

赋值的方式是在注解的括号内以 value=" "形式,多个属性之前用 ,隔开。(有一个参数时,可以直接写"数学")

@interface Test{
    String value() default "y";
    int id() default 4;
}
@Test()

属性可以定义默认值default,定义默认值以后,Test里面可以不写参数

注解属性的返回值类型(2020/1/21):

1.基本数据类型 2.String  3.枚举 4.注解   5.以上类型的数组

 定义了属性后,使用的时候需要给属性赋值

1.如果用了default关键字给属性默认初始化值,则使用该注解时,可以不进行属性的赋值

2.如果只有一个属性,可以直接定义值

3.数组赋值时,使用 { }包裹,如果数组中只有一个值,可以省略{ }

注解的作用:

注解到底有什么用?

我们不妨将目光放到 Java 官方文档上来。

文章开始的时候,我用标签来类比注解。但标签比喻只是手段,而不是目的。为的是让大家在初次学习注解时能够不被那些抽象的新概念搞懵。既然现在,我们已经对注解有所了解,我们不妨再仔细阅读官方最严谨的文档。

注解是一系列元数据,它提供数据用来解释程序代码,但是注解并非是所解释的代码本身的一部分。注解对于代码的运行效果没有直接影响。

注解有许多用处,主要如下:
- 提供信息给编译器: 编译器可以利用注解来探测错误和警告信息
- 编译阶段时的处理: 软件工具可以用来利用注解信息来生成代码、Html文档或者做其它相应处理。
- 运行时的处理: 某些注解可以在程序运行的时候接受代码的提取 (反射)

常见的注解测试:Override  Deprecated   SuppressWarnning

自定义注解练习

练习1测试♥:找出所有方法的DevelopInfo注解(用于程序员的老大查看每个方法的开发人员与日期√,接口还是要注明!)

import java.lang.annotation.*;
import java.lang.reflect.Method;
import java.util.Arrays;

/**
 * 自定义注解类型   记录开发人员的  姓名String   开发日期String
 * value这个参数名字,对于所有注解来说,比较特殊,例如:
 * @DevelopInfo("张三") 就是把"张三"传给注解默认的参数名字了value
 * 也可以把参数的名字写出来,如@DevelopInfo(value = "张三")
 *
 */
@Target({ElementType.METHOD}) //Target限制修饰的类型,说明DevelopInfo只能修饰方法
@Retention(RetentionPolicy.RUNTIME) // 默认注解在编译时期存在,说明DevelopInfo注解信息可以保留到运行的时候
@interface DevelopInfo{ // 用来修饰类,方法,变量,方法参数...
    String[] value() default "nobody"; // 注解的参数   参数名字叫 value
    String date();
}

/**
 * 新定义一个修饰方法的注解接口,可以保留到运行的时候
 */
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@interface Check{
    int value();
}

class CMath{
    //@DevelopInfo(value = "张三", date = "2019-11-2")
    @DevelopInfo(value = {"张三", "李四"}, date = "2019-11-2")
    @Check(10)
    int sum(int a, int b){
        return a + b;
    }

    @DevelopInfo(value = {"高洋", "刘硕"}, date = "2019-10-2")
    int minor(int a, int b){
        return a - b;
    }

    @DevelopInfo(value = "张航", date = "2019-9-12") //数组中可以多个值{" "," "},也可以传入一个值 ""
    int div(int a, int b){
        return a / b;
    }

    @DevelopInfo(value = "吴雷", date = "2019-9-12")
    int mix(int a, int b){
        return a * b;
    }
}
public class 注解 {
    public static void main(String[] args) {
        /**
         * 通过Java反射,获取CMath这个类所有方法的注解开发信息
         */
        Class<CMath> c = CMath.class;
        /**
         * 获取所有方法的method
         * getField(只能public)     getDeclaredField(公有,私有,保护)
         */
        Method[] methods = c.getDeclaredMethods();
        for (int i = 0; i < methods.length; i++) {
            Annotation[] anns = methods[i].getDeclaredAnnotations();  //获取方法的注解
            if(anns != null){
                // 遍历methods[i]当前方法的所有注解
                for (int j = 0; j < anns.length; j++) {
                    if(anns[j] instanceof DevelopInfo){  //判断注解是否实现了DevelopInfo接口
                        DevelopInfo di = (DevelopInfo)anns[j];  //把注解类型转化为接口类型
                        System.out.println("方法名:" +
                                methods[i].getName() +
                                " 开发者:" + Arrays.toString(di.value()) +
                                " 开发日期:" + di.date());
                        break;
                    }
                }
            }
        }
    }
}

 练习2  解析注解,获取注解中属性的值来获取某类的方法(同配置文件获取某类的方法)  配置文件获取某类的某方法

主类:

package test;
import java.lang.reflect.Method;

/**
 * @since 2020/1/21
 */
@Pro(className = "test.Person",methodName = "person")
public class ReflectTest3 {
    public static void main(String[] args) throws Exception {
        //提供一个模板(写一个框架类),可以创建任意类的对象,但是不能改变该类的对象的代码,但是不能改变框架的代码

        //1.解析注解:1.获取该类的字节码文件对象  2.获取注解
       Class<ReflectTest3> cls=ReflectTest3.class;
       Pro p= cls.getAnnotation(Pro.class);
       //2.调用注解对象中的属性(抽象方法),获得返回值
        String className=p.className();//获取了Pro注解的className属性的值,即"test.Person"
        String methodName=p.methodName();//获取了Pro注解的methodName属性的值,即"person"
        System.out.println(className);
        System.out.println(methodName);
        /**
         * 下面的步骤同配置文件应用反射的步骤,对获取到的类应用反射获取其方法
         * 好处:只需要改变该注解属性的值和就能获得不同类的不同方法
         */
        //3.加载该类进内存
        Class c=Class.forName(className);
        //4.创建对象
        Object obj= c.newInstance();
        //5.获取方法对象
        Method method=c.getMethod(methodName);
        //6.执行方法
        method.invoke(obj,null);
    }
}

Pro注解:

package test;

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

/**
 * @since 2020/1/21
 * 该注解使用来描述执行的类名、方法名
 */
@Target({ElementType.TYPE})//作用于类上
@Retention(RetentionPolicy.RUNTIME)//保留在运行阶段
public @interface Pro {
    String className();
    String methodName();
}

 注:注解与配置文件原理相同,注解只需要改该类的注解中要获取的类和方法

 练习3   自动化测试♥        Junit单元测试例子

开发人员写的代码:

package Testtt;
/**
 * 描述: 开发人员写好的功能代码
 *
 * @Author 
 */

public class CMath {
    public int sum(int a, int b){
        return a + b;
    }

    public int minor(int a, int b){
        return a - b;
    }

    public int div(int a, int b){
        return a / b;
    }

    public int mix(int a, int b){
        return a * b;
    }
}

测试人员写测试接口进行测试:

package Testt;

import Testtt.CMath;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * 描述: 测试用例类,测试人员写的
 * @Author hui
 */
public class TestCaseUnit {
    @TestCase("CMath.sum")
    public boolean testsum(){
        CMath math = new CMath();
        int ret = math.sum(10, 20);
        return ret == 30;
    }

    @TestCase("CMath.minor")
    public boolean testminor(){
        CMath math = new CMath();
        int ret = math.minor(10, 20);
        return ret == -10;
    }
   @TestCase("Cmath.mix")
   public boolean testmix(){
       CMath math=new CMath();
       int ret=math.mix(10,20);
       return ret==100;
   }
}
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@interface TestCase{
    String value();//存储测试的函数接口名称
}

注:写boolean类型的测试函数,并传入实际参数,通过每个测试的返回值测试开发人员写的代码;写TestCase接口,在主函数中通过注解来测试

主函数类:

package Testt;
import java.lang.reflect.Method;

/**
 * 描述:主类进行测试
 * @Author  hui
 */
public class Mainn {
    public static void main(String[] args) throws Exception {
        /**
         * 编写代码,输出TestCaseUnit里面所有的测试用例结果
         * CMath.sum  测试结果:成功!  失败!
         */
        Class<TestCaseUnit> c = TestCaseUnit.class;
        Object obj = c.newInstance();
        Method[] methods = c.getDeclaredMethods();
        for (int i = 0; i < methods.length; i++) {
            TestCase tc = methods[i].getDeclaredAnnotation(TestCase.class);
            if (tc != null) {
                System.out.print(tc.value() + " ");  //得到注解的内容
                System.out.print("测试结果:");
                if ((boolean) methods[i].invoke(obj)) {  .//通过反射调用测试类的方法,通过返回值确定函数是否正确
                    System.out.println("成功!");
                } else {
                    System.out.println("失败!");
                }
            }
        }
    }
}

注:通过注解得到测试人员写的测试函数的值(如: Cmath.add   测试结果:成功!),通过反射调用测试类的方法,输出每一个方法的测试结果,如果true,输出测试成功!

结果:

练习4:通过注解测试后将异常写入到一个文件中

package AnnotationExample;
/**
 * @since 2020/1/22
 */
import java.io.BufferedWriter;
import java.io.FileWriter;
import java.io.IOException;
import java.lang.reflect.Method;
public class CalculatorTest {
    public static void main(String[] args)throws IOException {
        Calculator c=new Calculator();
        Class cls=c.getClass();
        Method[] m=cls.getMethods();
        int count=0;
        //创建一个文件将异常导入到文件中
        BufferedWriter bw = new BufferedWriter(new FileWriter("bug.txt"));
        for(Method method:m){
                if(method.isAnnotationPresent(Check.class)){
                    //如果有check注解,就执行该方法
                    try {
                        method.invoke(c);
                    } catch (Exception e) {
                        //将异常记录到文件中
                        count++;
                        bw.write(method.getName()+"方法出异常了");
                        bw.newLine();
                        bw.write("异常的名称:"+e.getCause());
                        bw.newLine();
                        bw.write("异常的原因:"+e.getCause().getMessage());
                        bw.newLine();
                        bw.write("--------------");
                        bw.newLine();
                    }
                }
        }
        bw.write("本次测试一共出现"+count+"次异常");
        bw.flush();
        bw.close();
    }
}

被测试的Calculator类:

package AnnotationExample;
/**
 * @since 2020/1/22
 */
public class Calculator {
    @Check
    public void add(){
        System.out.println("1+0="+(1+0));
    }
    @Check
    public void sub(){
        System.out.println("1-0="+(1-0));
    }
    @Check
    public void mul(){
        System.out.println("1*0="+(1*0));
    }
    @Check
    public void div(){
        System.out.println("1/0="+(1/0));
    }
}

另一种获取注解的方式:

package AnnotationExample;
/**
 * @since 20200/1/22
 */

import java.io.BufferedWriter;
import java.io.FileWriter;
import java.io.IOException;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
public class CalculatorTest2 {
    public static void main(String[] args)throws IOException {
        Calculator c=new Calculator();
        Class cls=c.getClass();
        Method[] m=cls.getMethods();
        int count=0;
        //创建一个文件将异常导入到文件中
        BufferedWriter bw = new BufferedWriter(new FileWriter("bug2.txt"));
        for(int i=0;i<m.length;i++){
            Annotation[] a=m[i].getDeclaredAnnotations();
            for(int j=0;j<a.length;j++) {
                if (a[j] instanceof Check) {
                    //如果有check注解,就执行该方法
                    try {
                        m[i].invoke(c);
                    } catch (Exception e) {
                        //将异常记录到文件中
                        count++;
                        bw.write(m[i].getName() + "方法出异常了");
                        bw.newLine();
                        bw.write("异常的名称:" + e.getCause());
                        bw.newLine();
                        bw.write("异常的原因:" + e.getCause().getMessage());
                        bw.newLine();
                        bw.write("--------------");
                        bw.newLine();
                    }
                }
            }
        }
        bw.write("本次测试一共出现"+count+"次异常");
        bw.flush();
        bw.close();

    }

}

注:获取注解应该是某方法的注解,不能写成该类的cls.getAnnotations( )

 bug.txt文件中同bug.txt2:

原文地址:https://www.cnblogs.com/laurarararararara/p/11809127.html