跳到主要内容

第25章-注解【扩展】

概述

  • 从JDK1.5开始,Java增加对元数据的支持,也就是注解(Annotation)
  • 注解可以理解为代码里的特殊标记,这些标记可以在源代码,加载类,运行时被读取,并执行相应的处理。通过注解开发人员可以在不改变原有代码和逻辑的情况下在源代码中嵌入补充信息
  • 注解是放在类、方法、属性或参数前的一种特殊的注释,起到标记的作用,使得我们可以在这些类、方法、属性或参数等添加上一些附加信息
  • 注解对代码逻辑本身并没有直接影响
  • 注解使用语法词@符号,定义注解使用关键字@interface

注解和注释

  • 虽然注释和注解都属于对代码做的特殊标记,但是这两者却有本质的区别
    • 注释:就是对代码的解释说明,专门用于给程序员看的,程序编译时会忽略注释内容
    • 注解:用于给编译器或其他程序读取的,程序中根据注解的不同,可以做出相应的处理
  • 注解本身不起作用,起作用的是注解解释器,注解需要和反射一起使用才能发挥大的威力

注解的重要性

  • 在JavaSE中,注解的使用非常简单,例如用于标记过时的功能、忽略警告和生成API文档等操作,但是在JavaEE中注解就占据了非常重要的角色,例如用于配置应用程序的切面,替代JavaEE旧版本中遗留的繁冗代码和XML配置等
  • 未来的开发模式都是基于注解的,JPA(Java的持久化API)是基于注解的,Spring2.5以都是基于注解的,Hibernate3.x以后也是基于注解的,现在的Struts2有一部分也是基于注解的了,注解是一种趋势,一定程度上可以说:框架 = 反射 + 注解 + 设计模式
  • 典型应用场景
    • Spring容器中,@Component等注解的类会自动注入到容器中
    • 一些ORM组件,会通过使用注解对字段的进行验证,比如非空、长度限制等
    • 一些第三方组件引入后,通过注解的方式将其功能附着在已有业务逻辑上,但并不改变现有的业务逻辑流程,如日志、缓存、微服务等
    • 对相应业务做特殊的标记,如区别接口鉴权(接口自动化测试平台项目中,对于需要进身份验证就单独定义了注解,然后在Interceptor中反射进行拦截处理)

注解的使用过程

  • 主要分为这3个过程
    • 定义注解,可以是Java类库、框架自带、第三方依赖包自带或自定义
    • 应用到目标,可将定义好的注解应用到类、接口、属性、方法、参数等面向对象元素上
    • 对应用目标进行附加操作,通过反射等机制,识别到相应注解应用的目标后,可在不修改代码逻辑的基础上附加相应操作,如权限拦截、日志记录等

常见注解

@Deprecated

  • 这个注解是用来标记过时的元素,在编译阶段遇到这个注解时会发出提醒警告,告诉开发者正在调用一个过时的元素比如过时的类、过时的方法、过时的属性等

  • 应用实例

    • 应用实例1,过时的类、属性和方法

      过时目标类

      package com.bjpoernode.demo;

      /**
      * 注解实例类
      */
      @Deprecated
      public class DeprecatedTarget {
      //姓名,过时的
      @Deprecated
      public String name;

      //用户名
      public String username;

      @Deprecated
      public String getName() {
      return this.name;
      }

      public String getUsername() {
      return this.username;
      }
      }

      过时目标测试类

      package com.bjpoernode.demo;

      /**
      * @Deprecated实例
      */
      public class DeprecatedTargetTest {
      public static void main(String[] args) {
      //定义对象,类型过时
      DeprecatedTarget deprecatedTarget = new DeprecatedTarget();
      //使用属性
      deprecatedTarget.name = "张三"; //属性过时
      deprecatedTarget.username = "zhansan";
      //使用方法
      System.out.println(deprecatedTarget.getName()); //方法过时
      System.out.println(deprecatedTarget.getUsername());
      }
      }

@SuppressWarnings

  • 当你的编码可能存在警告时,比如安全警告,可以用@SuppressWarnings来消除。使用@SuppressWarnings注解可以为类、属性、方法、构造方法、构造函数和局部变量等进行标注

  • 常见的抑制警告的关键字如下

    • @SuppressWarnings(“unchecked”) 抑制未检查的转化,例如集合没有指定类型的警告
    • @SuppressWarnings(“unused”) 抑制未使用的变量的警告
    • @SuppressWarnings(“resource”) 抑制与使用Closeable类型资源相关的警告
    • @SuppressWarnings(“path”) 抑制在类路径,原文件路径中有不存在的路径的警告
    • @SuppressWarnings("deprecation") 抑制使用了某些不赞成使用的类和方法的警告
    • @SuppressWarnings("fallthrough") 抑制switch语句执行到底没有break关键字的警告
    • @SuppressWarnings("serial") 抑制某类实现Serializable,但是没提供serialVersionUID的警告
    • @SuppressWarnings("rawtypes") 抑制没有传递带有泛型的参数的警告 @SuppressWarnings("all") 抑制全部类型的警告
  • 应用实例

    • 应用实例1

      package com.bjpoernode.demo;

      import java.util.ArrayList;

      /**
      * @SuppressWarnings实例
      */
      public class SuppressWarningsDemo {
      public static void main(String[] args) {
      //抑制使用了某些不赞成使用的类和方法的警告
      @SuppressWarnings("deprecation")
      DeprecatedTarget deprecatedTarget = new DeprecatedTarget();
      //抑制未使用的变量的警告
      @SuppressWarnings("unused")
      int num = 10;
      //抑制所有的警告
      @SuppressWarnings("all")
      ArrayList arrayList = new ArrayList();
      }
      }

@Override

  • 使用@Override注解修饰的成员方法,则该方法必须是个重写方法,否则就会编译失败
  • 应用实例
    • 应用实例1,略

@FunctionalInterface

  • @FunctionalInterface属于“函数式接口”的注解,这个是 JDK1.8 版本引入的新特性
  • 使用@FunctionalInterface标注的接口,则该接口就有且只能存在一个抽象方法,否则就会发生编译错误
  • 应用实例
    • 应用实例1,略

【练习】

  1. 练习应用实例内容,完成代码编写

自定义注解

定义注解

  • 注解使用@interface来定义,语法同类和接口

  • 一般注解中只有属性,且定义格式比较特殊

  • 如果注解中仅仅只有一个名字为value的属性时,则给该属性赋值时就可以省略属性名,而是直接在小括号中填写属性值即可

  • 语法如下

    [修饰符] @interface 注解名称 {
    // 属性列表
    }
注解属性
  • 注解的属性也叫做成员变量,注解只有成员变量,没有方法

  • 注解的属性以“无参的方法”形式来声明,其方法名定义了该成员变量的名字,其返回值类型定义了该成员变量的类型

  • 注解中的属性设置默认值,给属性设置默认值需要用default关键值指定

  • 应用实例

    package com.bjpoernode.demo;

    import java.lang.annotation.*;

    /**
    * 自定义注解
    */
    @Retention(RetentionPolicy.RUNTIME)
    public @interface MyAnnotation {
    int no();

    String name() default "admin"; //默认值

    double[] data();
    }

应用到目标

  • 使用注解的时候,我们就需要给注解中的属性进行赋值,赋值的格式为 “name=value” 的形式,多个属性之间使用“,”隔开

  • 针对数组类型的属性,如果该属性中只有一个元素值,那么我们还可以省略大括号

  • 应用实例

    package com.bjpoernode.demo;

    @MyAnnotation(no = 1, name = "aaaa", data = {1.0, 2.0})
    public class MyAnnotationTarget {
    @MyAnnotation(no = 2, name = "bbbb", data = 2.22)
    public String username;

    @MyAnnotation(no = 33, name = "cccc", data = 5.6)
    public void show() {
    System.out.println("show ...");
    }
    }

对应用目标附加操作

  • 通过反射机制,可以在应用目标上获取注解信息

  • 应用实例

    package com.bjpoernode.demo;

    import java.lang.annotation.Annotation;
    import java.lang.reflect.Field;
    import java.lang.reflect.Method;
    import java.util.Arrays;

    public class MyAnnotationTest {
    public static void main(String[] args) throws NoSuchFieldException, NoSuchMethodException {
    //1、定义对象
    MyAnnotationTarget myAnnotationTarget = new MyAnnotationTarget();
    myAnnotationTarget.username = "张三";

    //2、获取Class对象
    Class clazz = myAnnotationTarget.getClass();

    //3、获取注解
    //3.1、类注解
    System.out.println("------------------类注解------------------");
    Annotation[] annotations = clazz.getAnnotations();
    System.out.println(Arrays.toString(annotations));
    //3.2、属性注解
    System.out.println("------------------属性注解------------------");
    Field field = clazz.getField("username");
    annotations = field.getAnnotations();
    System.out.println(Arrays.toString(annotations));
    //3.3、方法注解
    System.out.println("------------------方法注解------------------");
    Method method = clazz.getMethod("show");
    annotations = method.getAnnotations();
    System.out.println(Arrays.toString(annotations));
    }
    }

【练习】

  1. 练习应用实例内容,完成代码编写

元注解

  • 注解的注解
  • 用于系统定义的注解和自定义注解中
  • 主要有@Retention、 @Target、 @Documented、 @Inherited和@Repeatable(JDK1.8加入)五种

@Retention

  • 用于限定注解的生命周期,重要

  • 主要的生命周期有

    • @Retention(RetentionPolicy.SOURCE):注解仅应用于.java源代码文件,在字节码文件中不包含,如@Override
    • @Retention(RetentionPolicy.CLASS):注解应用于.class字节码文件,但运行时无法获得,默认,但不常用
    • @Retention(RetentionPolicy.RUNTIME):注解应用于内存中的字节码,且运行时可通过反射获取,常用
  • 应用实例

    • 应用实例1,注解使用

      定义注解

      package com.bjpoernode.demo;

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

      /**
      * 自定义注解
      */
      @Retention(RetentionPolicy.RUNTIME) //生命周期为运行时有效
      public @interface MyAnnotation {
      String value();
      }

      应用到目标

      package com.bjpoernode.demo;

      @MyAnnotation("我是类")
      public class MyAnnotationTarget {
      @MyAnnotation("我是属性")
      public String name;

      @MyAnnotation("我是方法")
      public void test() {
      System.out.println(this.name);
      }
      }

      对应用目标附加操作

      package com.bjpoernode.demo;

      import java.lang.reflect.Field;
      import java.lang.reflect.Method;

      public class MyAnnotationTest {
      public static void main(String[] args) throws NoSuchFieldException, NoSuchMethodException {
      //1、定义对象
      MyAnnotationTarget myAnnotationTarget = new MyAnnotationTarget();
      myAnnotationTarget.name = "张三";

      //2、获取Class对象
      Class clazz = myAnnotationTarget.getClass();

      //3、获取注解
      //3.1、类注解
      MyAnnotation myAnnotation = (MyAnnotation) clazz.getAnnotation(MyAnnotation.class);
      if (myAnnotation != null) {
      System.out.println(myAnnotation.value());
      }
      //3.2、属性注解
      Field field = clazz.getField("name");
      myAnnotation = field.getAnnotation(MyAnnotation.class);
      if (myAnnotation != null) {
      System.out.println(myAnnotation.value());
      }
      //3.3、方法注解
      Method method = clazz.getMethod("test");
      myAnnotation = method.getAnnotation(MyAnnotation.class);
      if (myAnnotation != null) {
      System.out.println(myAnnotation.value());
      }
      }
      }

【练习】

  1. 练习应用实例内容,完成代码编写

@Target

  • 用于限定可修饰的目标,重要

  • 主要可修饰目标有

    • @Target(ElementType.TYPE):作用于接口、类、枚举、注解
    • @Target(ElementType.FIELD):作用于属性、枚举的常量
    • @Target(ElementType.METHOD):作用于方法
    • @Target(ElementType.PARAMETER):作用于方法参数
    • @Target(ElementType.CONSTRUCTOR):作用于构造方法
    • @Target(ElementType.LOCAL_VARIABLE):作用于局部变量
    • @Target(ElementType.ANNOTATION_TYPE):作用于注解
    • @Target(ElementType.PACKAGE):作用于包
    • @Target(ElementType.TYPE_PARAMETER):作用于泛型,即泛型方法、泛型类和泛型接口。
    • @Target(ElementType.TYPE_USE):作用于任意类型
  • 应用实例

    • 应用实例1,注解可修饰目标

      定义注解

      package com.bjpoernode.demo;

      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.TYPE) //可修饰目标为类、接口、枚举、注解
      public @interface MyAnnotation {
      String value();
      }

      应用到目标

      package com.bjpoernode.demo;

      //【正确使用】
      @MyAnnotation("我是类")
      public class MyAnnotationTarget {
      //【编译错误】
      @MyAnnotation("我是属性")
      public String name;
      //【编译错误】
      @MyAnnotation("我是方法")
      public void test() {
      System.out.println(this.name);
      }
      }

      对应用目标附加操作

      package com.bjpoernode.demo;

      import java.lang.reflect.Field;
      import java.lang.reflect.Method;

      public class MyAnnotationTest {
      public static void main(String[] args) throws NoSuchFieldException, NoSuchMethodException {
      //1、定义对象
      MyAnnotationTarget myAnnotationTarget = new MyAnnotationTarget();
      myAnnotationTarget.name = "张三";

      //2、获取Class对象
      Class clazz = myAnnotationTarget.getClass();

      //3、获取注解
      //3.1、类注解
      MyAnnotation myAnnotation = (MyAnnotation) clazz.getAnnotation(MyAnnotation.class);
      if (myAnnotation != null) {
      System.out.println(myAnnotation.value());
      }
      //3.2、属性注解
      Field field = clazz.getField("name");
      myAnnotation = field.getAnnotation(MyAnnotation.class);
      if (myAnnotation != null) {
      System.out.println(myAnnotation.value());
      }
      //3.3、方法注解
      Method method = clazz.getMethod("test");
      myAnnotation = method.getAnnotation(MyAnnotation.class);
      if (myAnnotation != null) {
      System.out.println(myAnnotation.value());
      }
      }
      }

【练习】

  1. 练习应用实例内容,完成代码编写

@Inherited

  • 用于让子类获得父类注解,类似于继承,不常用

  • 应用实例

    • 应用实例1,获得父类注解

      定义注解

      package com.bjpoernode.demo;

      import java.lang.annotation.*;

      /**
      * 自定义注解
      */
      @Retention(RetentionPolicy.RUNTIME) //生命周期为运行时有效
      @Target(ElementType.TYPE) //可修饰目标为类、接口、枚举、注解
      @Inherited //可继承,添加和取消查看效果
      public @interface MyAnnotation {
      String value();
      }

      应用到目标

      package com.bjpoernode.demo;

      @MyAnnotation("我是类")
      public class MyAnnotationTarget {
      public String name;
      public void test() {
      System.out.println(this.name);
      }
      }

      目标子类

      package com.bjpoernode.demo;

      /**
      * MyAnnotationTarget的子类
      */
      public class MyAnnotationTargetSon extends MyAnnotationTarget {
      }

      对目标子类附加操作

      package com.bjpoernode.demo;

      public class MyAnnotationTest {
      public static void main(String[] args) throws NoSuchFieldException, NoSuchMethodException {
      //1、定义子类对象
      MyAnnotationTargetSon myAnnotationTargetSon = new MyAnnotationTargetSon();
      myAnnotationTargetSon.name = "张三";

      //2、获取Class对象
      Class clazz = myAnnotationTargetSon.getClass();

      //3、获取子类注解
      MyAnnotation myAnnotation = (MyAnnotation) clazz.getAnnotation(MyAnnotation.class);
      if (myAnnotation != null) {
      System.out.println(myAnnotation.value());
      }
      }
      }

【练习】

  1. 练习应用实例内容,完成代码编写

@Repeatable

  • 用于限定是否可重复修饰,不常用

  • 应用实例

    • 应用实例1,是否可重复修饰

      定义注解

      package com.bjpoernode.demo;

      import java.lang.annotation.*;

      /**
      * 自定义注解
      */
      @Retention(RetentionPolicy.RUNTIME) //生命周期为运行时有效
      @Target(ElementType.TYPE) //可修饰目标为类、接口、枚举、注解
      @Repeatable(MyAnnotations.class) //是否可重复
      public @interface MyAnnotation {
      String value();
      }

      可重复注解

      package com.bjpoernode.demo;

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

      /**
      * 可重复MyAnnotation注解,与MyAnnotation注解有相同的元注解
      */
      @Retention(RetentionPolicy.RUNTIME) //生命周期为运行时有效
      @Target(ElementType.TYPE) //可修饰目标为类、接口、枚举、注解
      public @interface MyAnnotations {
      MyAnnotation[] value();
      }

      重复应用到目标

      package com.bjpoernode.demo;

      @MyAnnotation("我是类")
      @MyAnnotation("我是类-2")
      @MyAnnotation("我是类-3")
      public class MyAnnotationTarget {
      public String name;

      public void test() {
      System.out.println(this.name);
      }
      }

      对目标进行操作

      package com.bjpoernode.demo;

      public class MyAnnotationTest {
      public static void main(String[] args) throws NoSuchFieldException, NoSuchMethodException {
      //1、定义对象
      MyAnnotationTarget myAnnotationTarget = new MyAnnotationTarget();
      myAnnotationTarget.name = "张三";

      //2、获取Class对象
      Class clazz = myAnnotationTarget.getClass();

      //3、获取注解
      MyAnnotation[] myAnnotations = (MyAnnotation[]) clazz.getAnnotationsByType(MyAnnotation.class);
      if (myAnnotations != null) {
      for (MyAnnotation myAnnotation : myAnnotations) {
      System.out.println(myAnnotation.value());
      }
      }
      }
      }

【练习】

  1. 练习应用实例内容,完成代码编写

@Documented

  • 用来标注生成Javadoc的时候是否会被记录,了解即可

应用实例

  • 应用实例1,模拟ORM框架的实体类验证功能,见附件annotation项目