1、定义

对比

注解:说明程序的,给计算机看的

注释:用文字描述的,给程序员看的

定义:注解(Anotation)也叫元数据,一种代码级别的说明。它是JDK1.5及以后版本引入的一个特性,与类、接口、枚举是在同一个层次。它可以声明在包、类、字段、方法、局部变量、方法参数等的前面,用来对这些元素进行说明,注释

定义描述

  • JDK1.5后的新特性
  • 用来说明程序的
  • 使用注解:@注解名称

2、作用

  • 编写文档:通过代码里标识的注解生成文档【生成doc文档】
  • 代码分析:通过代码里标识的注解对代码进行分析【使用反射】
  • 编译检查:通过代码里标识的注解让编译器能够实现基本的编译检查【如Override】

3、JDK中预定义的注解

  • @Override:检测被该注解标注的方法是否是继承自父类(接口)的
  • @Deprecated:表示该注解标注的内容已经过时
  • @SuppressWarnings:压制警告
    • 一般传递参数all,@SuppressWarnings(“all”)表示压制所有警告

4、自定义注解

  • 格式:
    • 元注解
    • public @interface 接口名{}
  • 本质:注解本质上就是一个接口,该接口默认继承Annotation接口
    • 原型:public interface MyAnno extends java.lang.annotation.Annotation{}
  • 属性:接口中的抽象方法
    • 属性的返回值有以下要求:
      • 基本数据类型
      • String
      • 枚举
      • 注解
      • 以上类型的数组
    • 定义了属性,在使用时需要给属性赋值
      • 如果定义属性时,使用default关键字给属性默认初始化值,则使用注解时,可以不进行属性的赋值
      • 如果只有一个属性需要赋值,并且属性的名称是value,则value可以省略,直接定义值即可
      • 数组赋值时,值使用{}包裹,如果数组中只有一个值,则{}省略
//自定义注解
package com.first;

public @interface MyAnno {
    int age();
    String name();
}
//使用注解
package com.first;

@MyAnno(age = 13, name = "cyh")
public class UseMyAnno {
}

5、元注解

所谓元注解就是注解上的注解,其本身也是一种注解,一种作用于其他注解的注解

  • @Target:描述注解能够作用的位置
    • ElementType取值
      • TYPE:可以作用于类上
      • METHOD:可以作用于方法上
      • FIELD:可以作用于成员变量上
  • @Retention:描述注解被保留的阶段
    • @Retention(RetentionPolicy.RUMTIME):当前被描述的注解,会被保留到class字节码文件中,并被JVM读取到
    • @Retention(RetentionPolicy.CLASS):当前被描述的注解,会被保留到class字节码文件中,当不会被JVM读取到
    • @Retention(RetentionPolicy.SOURCE):当前被描述的注解,不会被保留到class字节码文件中
  • @Documented:描述注解是否被抽取到API文档中
  • @Inherited:描述注解是否被子类继承

6、使用(解析)注解

所谓解析注解,就是在程序中获取注解中定义的属性值

使用步骤:

  1. 获取注解定义的位置的对象(CLASS,METHOD,FIELD)
    • 注解定义在类上:类名.class获取其字节码对象
    • 注解定义在方法上:先获取方法所在类的字节码文件,再对此字节码文件用getMethod()即可得到这个方法的对象
  2. 获取指定的注解
    • Class.getAnnotation(Class)
    • Method.getAnnotation(Class)
  3. 调用注解中的抽象方法获取配置的属性值
package com.first;


import java.lang.reflect.Constructor;
import java.lang.reflect.Method;

@ReflectAnno(className = "com.domain.Person", methodName = "eat")
public class ReflectByAnnoTest {

    public static void main(String[] args) throws Exception{
        //解析类上注解
        //1获取该类的字节码文件对象
        Class<ReflectByAnnoTest> reflectByAnnoClass = ReflectByAnnoTest.class;
        //2.获取该类上边的某一注解的对象
        ReflectAnno relAnno = reflectByAnnoClass.getAnnotation(ReflectAnno.class);
        //3.调用注解对象中定义的抽象方法,获取返回值
        String className = relAnno.className();
        String methodName = relAnno.methodName();
        //通过注解中classname定义的类名加载其字节码文件
        Class<?> aClass = Class.forName(className);
        //创建一个该类的对象
        Constructor<?> constructor = aClass.getConstructor();
        Object o = constructor.newInstance();
        //获取注解中的方法
        Method method = aClass.getMethod(methodName);
        //调用该对象的方法
        method.invoke(o);
    }
    @MyAnno(age = 11, name = "cuyj")
    public static void test() throws NoSuchMethodException {
        //解析方法上的注解
        //1.获取方法所在类的对象
        Class<ReflectByAnnoTest> reflectByAnnoClass = ReflectByAnnoTest.class;
        //2.通过所在类的字节码文件获取到自己这个方法的对象
        Method method = reflectByAnnoClass.getMethod("test");
        //3.通过方法对象获取方法上的某个注解的对象
        MyAnno myAnno = method.getAnnotation(MyAnno.class);
        //4.调用注解中定义的方法
        System.out.println(myAnno.name());
        System.out.println(myAnno.age());
    }
}

7、小结

  • 以后大多数时候,我们会使用注解,而不是自定义注解
  • 注解给谁用?
    1. 编译器:编译检查
    2. 给解析程序用:测试框架(如下)
  • 注解不是程序的一部分,可以理解为注解就是一个标签

注解就是一个标签,告诉负责解析的程序(如测试框架)这个地方需要进行解析测试

Calculator类

package com.first;

import com.test.Check;

public class Calculator {
    
    //@Check
    public int add(int a, int b) {
       return a + b;
    }

    //@Check
    public int sub(int a, int b) {
        return a - b;
    }

    @Check
    public void div() {
        System.out.println(2 / 0);
    }
    
    @Check
    public void show() {
        String s = null;
        s.toString();
    }
}

测试(解析)程序

package com.test;

import com.first.Calculator;

import javax.swing.*;
import java.io.BufferedWriter;
import java.io.FileWriter;
import java.io.IOException;
import java.lang.reflect.Method;

public class TestCheck {

    public static void main(String[] args) throws IOException {
        //1.创建计算器对象
        Calculator c = new Calculator();
        //2.获取字节码文件
        Class cls = c.getClass();
        //3.获取所有方法
        Method[] methods = cls.getMethods();

        int num = 0;//出现异常的次数
        BufferedWriter bw = new BufferedWriter(new FileWriter("bug.txt"));
        for (Method method : methods) {
            if(method.isAnnotationPresent(Check.class)){//如果该方法上存在Check注解
                try{
                    method.invoke(c);
                }catch (Exception e) {
                    //捕获异常
                    //记录到文件中
                    num++;
                    bw.write(method.getName()+"方法出异常了");
                    bw.newLine();
                    bw.write("异常的原因" + e.getCause.getClass().getSimpleName());
                    bw.newLine();
                    bw.write("异常的信息" + e.getCause.getMessage());
                    bw.newLine();
                    bw.write("===================");
                    bw.newLine();
                }
            }
        }
        bw.write("本次测试一共出现" + num + "次异常");
        bw.flush();
        bw.close();
    }
}

输出结果bug.txt

div方法出异常了
异常的原因ArithmeticException
异常的信息/ by zero
===================
show方法出异常了
异常的原因NullPointerException
异常的信息null
===================
本次测试一共出现2次异常

Q.E.D.


   七岁几胆敢预言自己,操一艘战机