在框架的使用过程中,常用到注解,本文对注解进行简单介绍。

注解

  • 定义:注解(Annotation),也叫元数据。一种代码级别的说明。
  • 它是JDK1.5及以后版本引入的一个特性,与类、接口、枚举是在同一个层次。它可以声明在包、类、字段、方法、局部变量、方法参数等的前面,用来对这些元素进行说明,注释。
  • 作用分类:
    • 编写文档:通过代码里标识的元数据生成文档【生成文档doc文档】
    • 代码分析:通过代码里标识的元数据对代码进行分析【使用反射】
    • 编译检查:通过代码里标识的元数据让编译器能够实现基本的编译检查【Override】
  • 类比
    • 注释:给程序员看
    • 注解:给计算机看

1. 编译检查

  • @Override:

2. 编写文档

  • @author :作者
  • @version:版本
  • @since:
  • @return:返回值

3. Java中预定义的注解

  • @Override:检测该被该注解标注的方法是否是继承自父类(接口)

  • @Deprecated:表示该方法已经过时

  • @SuppressWarnings():压制警告

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    package top.tobing.annotation;
    /**
    * Java内预定义的注解
    * @author Tobing
    * @version 1.0
    * @since 1.5
    */
    public class Demo01Annotation01 {
    public static void main(String[] args) {
    Cat cat = new Cat();
    cat.run();
    }
    }
    class Animal{
    private String name;
    public void eat() {
    System.out.println("吃吃吃!!!");
    }
    }
    // 压制所有的警告
    @SuppressWarnings("all")
    class Cat extends Animal{
    // 表示该方法是继承自父类的
    @Override
    public void eat() {
    System.out.println("吃鱼!!!");
    }
    // 表示该方法已经过时,不建议使用
    @Deprecated
    public void run() {
    System.out.println("飞檐走壁!!!!");
    }
    }

4. 自定义注解

  • 如何自定义呢?照葫芦画瓢 — > 查看@Deprecated源码

    1
    2
    3
    4
    5
    6
    @Documented
    @Retention(RetentionPolicy.RUNTIME)
    @Target(value={CONSTRUCTOR, FIELD, LOCAL_VARIABLE, METHOD, PACKAGE, MODULE, PARAMETER, TYPE})
    public @interface Deprecated {
    ......
    }
  • 以下是自定义的注解:MyAnno.java

    1
    2
    3
    package top.tobing.annotation;
    public @interface MyAnno {
    }
  • 对自定义的注解反编译

    1
    2
    3
    4
    5
    6
    E:\Code>javac MyAnno.java   # 编译获取字节码文件
    E:\Code>javap MyAnno # 反编译字节码文件
    Compiled from "MyAnno.java"
    # 反编译得到的代码
    public interface MyAnno extends java.lang.annotation.Annotation {
    }
  • 从以上编译所的代码可以知道注解的本质

    • 本质:注解本质上就是一个接口,该接口默认继承了Annotation接口

      • public interface MyAnno extends java.lang.annotation.Annotation
      • Annotation:The common interface extended by all annotation types.
      • Annotation:所有注解类型拓展的公共接口。
    • 注解属性:接口的抽象方法

      • 要求:
        • 属性返回值类型
          • 基本数据类型
          • String
          • 枚举
          • 注解
          • 以上类型的数值
        • 定义了属性,在使用时需要给属性赋值
          1. 定义属性时,使用default关键字给定默认值,使用注解时可以不进行赋值
          2. 只有一个属性需要赋值,并且名称是value,可以value可以省略,直接赋值即可。
          3. 数值赋值是,值需要用{}包裹。如果数组中只有一个值则可以省略{}
      • 以下是代码演示
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      package top.tobing.annotation;
      // 自定义注解Anno
      public @interface MyAnno {
      //int show(); // int
      //String show2(); // String
      //MyAnno1 anno1(); // 注解类型
      String name();
      String[] cats();
      int[] ages();
      int age() default 10; // 赋值了默认,可以不需要数值
      }
      // 自定义注解
      @interface MyAnno1{
      String value(); // 只有一个值,且值的名称为value
      }
      //////////////使用注解//////////////////
      // age由于定义了default,可以不赋值
      // cats是数组,赋值多个的时候使用大括号包裹
      // ages是数值,赋值一个的时候,可以省略大括号
      @MyAnno(name="张三",cats= {"小王","小白"},ages=10)

      // 省略了value=
      @MyAnno1("Value")
      public class Demo01Annotation2 {
      }
  • 元注解

    • 用于描述注解的注解(可以体会到元意思)
    • @Target:描述注解的作用时机
      • ElementType取值
        • ElementType.TYPE:可以作用在类上
        • ElementType.METHOD:可以作用在方法上
        • ElementType.FIELD:可以作用在成员变量上
    • @Retention:描述注解被保留的阶段
      • @Retention(保留阶段)
      • RetentionPolicy.RUNTIME:保留到class字节码文件中,并且被JVM读取到(这个最常用)
      • RetentionPolicy.SOURCE:略
      • RetentionPolicy.CLASS:略
    • @Documented:描述注解是否被抽取到api中
    • @Inherited:描述注解是否被子类继承
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    // 以下元注解表述了:注解保留到class字节码文件中,并且被JVM读取到(这个最常用)
    @Retention(RetentionPolicy.RUNTIME)
    // 以下元注解表述了:注解可以被抽取到API中
    @Documented
    // 以下元注解表述了:自定义注解可以被子类继承
    @Inherited
    // 以下元注解表述了:自定义注解可以被使用在类(TYPE)、方法(METHOD)、成员变量(FIELD)
    @Target({ElementType.TYPE,ElementType.METHOD,ElementType.FIELD})
    @interface MyAnno2{
    String value();
    }

    package top.tobing.annotation;
    @MyAnno2("类")
    public class Demo01Annotation3 {
    @MyAnno2("成员变量")
    private String name;
    @MyAnno2("方法")
    public void show() {

    }
    }

5. 注解的应用

1. 代替xml配置文件
  • 传统的获取全限定类名,方法名,需要定义xml配置文件中,利用文件流获取xml,并将其中内容读取。操作比较麻烦。

  • 使用注解可以代替此功能

    • 自定义获取className methodName的注解:Load.java
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    package top.tobing.annotation;
    import java.lang.annotation.ElementType;
    import java.lang.annotation.Retention;
    import java.lang.annotation.RetentionPolicy;
    import java.lang.annotation.Target;
    @Target(ElementType.TYPE)
    @Retention(RetentionPolicy.RUNTIME)
    public @interface Load {
    String className();
    String methodName();
    }

    • 通过自定义注解获取classNamemethodName,利用反射执行该方法
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    package top.tobing.annotation;
    import java.lang.reflect.Method;
    @Load(className="top.tobing.annotation.Demo",methodName="show")
    public class Demo04AnnotationReflect {
    public static void main(String[] args) throws Exception {
    // 获取本类字节码文件对象
    Class<Demo04AnnotationReflect> demoClass = Demo04AnnotationReflect.class;
    /**
    此处本质上是获取了,内存中实现的Load接口的子类对象执行方法的返回值
    public class LoadImpl{
    public String className(){
    return "top.tobing.annotation.Demo";
    }
    public String methodName(){
    return "show";
    }
    }
    */
    // 获取注解对象
    Load load = demoClass.getAnnotation(Load.class);
    // 获取注解的内容
    String className = load.className();
    String methodName = load.methodName();
    System.out.println(className);
    System.out.println(methodName);
    // 通过类名以及方法名称利用反射,执行方法
    //3.加载该类进内存
    Class cls = Class.forName(className);
    //4.创建对象
    Object obj = cls.newInstance();
    //5.获取方法对象
    Method method = cls.getMethod(methodName);
    //6.执行方法
    method.invoke(obj);
    }
    }

2. 自动以测试
  • 要测试的类

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    package top.tobing.annotation.test;
    public class Cal {
    //加法
    @MyCheck
    public void add(){
    String str = null;
    str.toString();
    System.out.println("1 + 0 =" + (1 + 0));
    }
    //减法
    @MyCheck
    public void sub(){
    System.out.println("1 - 0 =" + (1 - 0));
    }
    //乘法
    @MyCheck
    public void mul(){
    System.out.println("1 * 0 =" + (1 * 0));
    }
    //除法
    @MyCheck
    public void div(){
    System.out.println("1 / 0 =" + (1 / 0));
    }
    public void show(){
    System.out.println("永无bug...");
    }
    }
  • 测试使用的注解

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    package top.tobing.annotation.test;
    import java.lang.annotation.ElementType;
    import java.lang.annotation.Retention;
    import java.lang.annotation.RetentionPolicy;
    import java.lang.annotation.Target;
    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.METHOD)
    public @interface MyCheck {

    }
  • 测试类

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    package top.tobing.annotation.test;
    import java.lang.reflect.Method;
    public class CalTest {
    public static void main(String[] args) {
    //1. 创建计算器对象
    Cal c = new Cal();
    //2. 获取计算器字节码文件
    Class cls = c.getClass();
    //3. 通过字节码文件获取使用方法
    Method[] methods = cls.getMethods();
    int num = 0;//记录异常出现的次数
    for(Method m:methods) {
    // 判断方法上是否包含注解
    if(m.isAnnotationPresent(MyCheck.class)) {
    // 包含,捕获异常
    try {
    m.invoke(c);
    }catch(Exception e) {
    num++;
    System.out.println(m.getName()+"出现异常了");
    System.out.println("异常名称为:"+e.getCause().getClass().getSimpleName());
    System.out.println("异常原因是:"+e.getCause().getMessage());
    System.out.println("----------------------");
    }
    }
    }
    System.out.println("本次测试一共出现"+num+"次异常");
    }
    }

  • 运行结果

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    add出现异常了
    异常名称为:NullPointerException
    异常原因是:null
    ----------------------
    1 - 0 =1
    div出现异常了
    异常名称为:ArithmeticException
    异常原因是:/ by zero
    ----------------------
    1 * 0 =0
    本次测试一共出现2次异常

总结

  • 在实战开发中,我们通常是使用注解,而并不是定义注解
  • 此处复习注解是为了更好地学习框架。

评论