什么是注解?

240 阅读5分钟

一、什么是注解

概念:说明程序,给计算机看的

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

作用分类:

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

二、JDK 中预定义的注解

  1. @Override:定义在 java.lang.Override 中,只适用于修饰方法,表示一个方法是否打算重写超类中的另一个方法声明,检测被该注解标注的方法是否是继承自父类(接口)的
public class AnnoDemo01 {
​
    @Override // 重写父类的方法
    public String toString() {
        return super.toString();
    }
}
  1. @Deprecated:定义在 java.lang.Deprecated 中,可以用于修饰方法,属性,类,表示不鼓励使用这样的元素,通常是因为危险或者存在更好的选择,表示该注解标注的东西已经过时
@Deprecated  //表示不推荐使用,但是可以使用或者存在更好的替换方式
public void show1(){
    
}

  1. @SuppressWarnings:定义在 java.lang.SuppressWarnings 中,用来抑制编译时的警告信息,该注解需要添加一个参数才能正确使用,参数是已经定义好的
@SuppressWarnings("all")
// 编辑器会有一些警告信息的提示,通过该注解可以让其不显示
public class AnnoDemo01 {}

三、自定义注解

1. 注解的本质

创建一个注解文件编译之后反编译

public @interface MyAnno {
    ... 属性列表
}

public interface MyAnno extends java.lang.annotation.Annotation {
}
// 其实创建一个注解 @interface 就是一个接口继承了 Annotation类

2. 接口中的抽象方法

public @interface MyAnno {
    // 注解的属性,成员方法
    String value() default "111";
}                   

要求:

  • 方法(属性)返回值类型:基本数据类型、String、枚举、注解、以上类型的数组,其他都不行

  • 定义了属性,在使用时需要给属性赋值

    • 如果定义属性时,使用 default 关键字给属性默认值初始化值,则使用注解时,可以不进行属性的赋值。
    • 如果只有一个属性需要赋值,并且属性的名称是 value,则value可以省略,直接定义值即可。
    • 如果是数组赋值时,值使用 {} 包裹,如果数组中只有一个值,则省略 {}。
@MyAnno("aa")
public class Member {
}

3. 元注解

用于描述注解的注解,元注解作用就是负责注解其他注解,Java 定义了 4 个标准的 meta-annotation 类型,他们被用来提供对其他 annotation 类型作说明

这些类型和它们所支持的类在 java.lang.annotation 包中可以找到

3.1 @Target

描述注解的使用范围,即被描述的注解可以用在什么地方

  1. 自定义一个注解
@Target(ElementType.TYPE)
public @interface MyAnno {
}
  1. 测试类中测试

    类上可以使用,方法上不能使用

  • TYPE:作用于类上
  • METHOD:作用于方法上
  • FIELD:作用于成员变量上
3.2 @Retention

表示需要在什么级别保存该注解信息,用于描述注解的声明周期(source < class < runtime)

// RUNTIME 类型,会被保留到 class 字节码文件中,并被 JVM 读取到
@Retention(RetentionPolicy.RUNTIME)

// CLASS 类型,会被保留到 class 字节码文件中,不会被 JVM 读取到
@Retention(RetentionPolicy.CLASS)

// SOURCE 类型,不会保留到 class 字节码文件中
@Retention(RetentionPolicy.SOURCE)
3.3 @Document

描述注解是否被抽取到 API 文档中

// 注解信息
@Documented
@Target({ElementType.TYPE,ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnno {
}
// 分别在类和方法上使用该注解
@MyAnno
public class DemoTest {

    @MyAnno
    public void show(){

    }

}

使用 cmd 进入到该文件夹下执行命令:javadoc DemoTest 生成注解相关的文档信息

3.4 @Inherited

描述注解是否可以被子类继承

// 定义一个注解,使用该关键字修饰
@Inherited
public @interface MyAnno {
}
// 在父类上使用该注解
@MyAnno
public class Father {
}
// 子类继承父类,那么子类也有父类使用的注解信息
public class Son extends Father{
}

四、解析使用注解

获取注解中定义的属性值

  1. 创建一个注解
/**
 * 描述需要执行的类型和方法名
 */
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface Pro {
    String className();
    String methodName();
}
  1. 创建一个类,有一个 show() 方法输出
public class Demo01 {
    public void show() {
        System.out.println("Demo01 show ...");
    }
}
  1. 创建测试类,通过反射获得指定 Demo01 类的 show() 方法
@Pro(className = "com.lss.anno.demo07.Demo01",methodName = "show")
public class ReflectTest {
    public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException {
        // 解析注解
        // 1. 获取该类的字节码文件对象
        Class<ReflectTest> clzz = ReflectTest.class;
        // 2. 获取该类上的注解对象
        // 其实就是在内存中生成了一个该注解接口的子类实现对象
        /**
         *  其实就是在内存中生成了一个该注解接口的子类实现对象
         *  public class ProImpl implements Pro{
         *      public String className() {
         *          return "com.lss.Demo07.Demo01";
         *      }
         *      public String methodName() {
         *           return "show";
         *      }
         *  }
         */
        Pro annotation = clzz.getAnnotation(Pro.class);
        // 3. 调用注解对象中定义的抽象方法,获取返回值
        String className = annotation.className();
        String methodName = annotation.methodName();

        // 4. 加载类到内存中
        Class<?> cls = Class.forName(className);
        // 5. 创建对象
        Object o = cls.newInstance();
        // 6. 获取方法对象
        Method method = cls.getMethod(methodName);
        method.invoke(o);
    }
}
  1. 获取注解定义的位置的对象(Class,Method,Field)
  2. 获取指定的注解
  3. 调用注解中的抽象方法获取配置的属性值

五、异常检测小案例

通过自定义注解的方式检测一个类中的几个方法是否有异常,如果有异常则将异常信息写入到 bug.txt 文件,如果没有则正常执行

  1. 首先创建一个自定义注解
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Check {
}
  1. 创建一个类,包含3个方法,2个有异常,一个无异常
public class Calc {

    @Check	// 通过 注解标记检测的方法
    public void create() {
        String str = null;
        str.toLowerCase();
    }
    @Check
    public void delete() {
        int i = 1 / 0;
    }
    @Check
    public void show() {
        System.out.println("没有发生异常的类");
    }
}
  1. 测试类
public class CalcTest {
    public static void main(String[] args) throws IOException {

        //1. 获取 Calc 对象的字节码文件
        Calc calc = new Calc();
        Class clzz = calc.getClass();
        // 将异常信息写到 bug.txt 文件中
        BufferedWriter bw = new BufferedWriter(new FileWriter("bug.txt"));
        // 2. 获得所有方法
        Method[] methods = clzz.getMethods();
        for (Method method : methods) {
            if (method.isAnnotationPresent(Check.class)) {
                try {
                    method.invoke(calc);
                } catch (Exception e) {
                    // 有异常进行写出

                    bw.write(method.getName() + "方法发生了异常");
                    bw.newLine();
                    bw.write("异常名称:" + e.getCause().getClass().getSimpleName());
                    bw.newLine();
                    bw.write("异常原因:" + e.getCause().getMessage());
                    bw.newLine();
                }
            }
        }
        bw.flush();
        bw.close();
    }
}

学习参考视频: www.bilibili.com/video/BV1Vt…