Java 注解是在 JDK5 引入的新特性,目前大部分框架都使用了注解简化代码并提高编码效率,因此掌握并深入理解 Java 注解对于一个 Java 工程师来说是很有必要的事。🚀本篇章代码 Demo

# 理解注解

  • Java 注解与普通修饰符 public static void 的使用方法并没有多大区别。
DemoApplicationTests.java
package top.rem.rain.annotation.demo;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
@SpringBootTest
class DemoApplicationTests { 
  //@Test 注解修饰方法 a 可直接测试此方法
  @Test
  public  void a(){
    System.out.println("Test...");
  }
  
  // 一个方法上可以拥有多个不同的注解
  @Deprecated // 标识过时方法
  @SuppressWarnings("uncheck") // 压制编译器警告
  public static void b(){
  }
}
  • 在方法上通过使用 @Test 注解后,在运行该方法时,测试框架会自动识别该方法并单独调用。
  • @Test 是一种标记注解,起标记作用,运行时告诉测试框架该方法为测试方法。
  • @Deprecated@SuppressWarnings(“uncheck”) 则是 Java 本身内置的注解。
  • @Deprecated :使用此注解标记的类或方法已过期不建议再使用。
  • @SuppressWarnings(“uncheck”) :在方法上使用此注解则表示忽略指定警告。

# 注解基本使用

# 声明元注解

Test.java
package top.rem.rain.annotation.demo;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
 * 自定义 Test 注解
 * @author LightRain
 */
// 用作于方法
@Target(ElementType.METHOD)
// 保留到运行期间
@Retention(RetentionPolicy.RUNTIME)
public @interface Test {
}
  • @interface : 声明 Test 注解类。
  • @Target 注解传入 ElementType.METHOD 参数来标记此注解只能用作于方法上。
  • @Retention 注解传入 RetentionPolicy.RUNTIME 则用表示该注解保留到运行期间。
  • 对于 @Target@Retention 是由 Java 提供的元注解,所谓元注解就是标记其他注解的根注解。
  • @Target 用来约束注解可以作用的地方如方法、字段、类,其中 ElementType 是枚举类型,参数如下示例。
ElementType.java
package top.rem.rain.annotation.demo.custom.enums;
/**
 * 枚举类型可用值
 * @author LightRain
 */
public enum ElementType {
  /** 标明该注解可以用于类、接口(包括注解类型)或 enum 声明 */
  TYPE,
  /** 标明该注解可以用于字段 (域) 声明,包括 enum 实例 */
  FIELD,
  /** 标明该注解可以用于方法声明 */
  METHOD,
  /** 标明该注解可以用于参数声明 */
  PARAMETER,
  /** 标明注解可以用于构造函数声明 */
  CONSTRUCTOR,
  /** 标明注解可以用于局部变量声明 */
  LOCAL_VARIABLE,
  /** 标明注解可以用于注解声明 (应用于另一个注解上) */
  ANNOTATION_TYPE,
  /** 标明注解可以用于包声明 */
  PACKAGE,
  /**
   * 标明注解可以用于类型参数声明(1.8 新加入)
   * @since 1.8
   */
  TYPE_PARAMETER,
  /**
   * 类型使用声明(1.8 新加入)
   * @since 1.8
   */
  TYPE_USE
}
  • 注意:当注解未指定 Target 值时,此注解可以用于任何元素之上,多个值使用 {} 包含并用逗号隔开。
Test2.java
package top.rem.rain.annotation.demo.custom.annotations;
import java.lang.annotation.Target;
import static java.lang.annotation.ElementType.*;
/**
 * 自定义 Test2 注解示例可使用多个值的 @Target
 * @author LightRain
 */
@Target(value = {CONSTRUCTOR, FIELD, LOCAL_VARIABLE, METHOD, PACKAGE, PARAMETER, TYPE})
public @interface Test2 {
}
  • @Retention 元注解用来约束注解的生命周期,分别有三个值:
    • SOURCE(源码级别) 注解将被编译器丢弃,该类型的注解信息只会保留在源码中,源码经过编译后,注解信息将被丢弃,不会保留进编译好的 class 文件中。
    • CLASS(类文件级别) 注解将会被保留到 class 文件中,但会被 Java 虚拟机的 VM 丢弃,该类型的注解信息会保留在源码和 class 文件中,在执行时不会加载到虚拟机中,请注意:当注解未定义 Retention 值时,默认值是 CLASS , 如 Java 内置注解: @Override @Deprecated @SuppressWarnning 等。
    • RUNTIME(运行时级别) 注解将保留到运行期 ( JVM ) 中,因此可以通过反射机制读取注解的信息 (源码、 class 文件和执行的时候都有注解信息),如: SpringMvc 中的 @Controller@Autowired@RequestMapping 等。

# 注解元素及其数据类型

  • 通过上述对 @Test 注解的定义,我们了解了注解定义的过程,由于 @Test 内部没有定义其他元素。
  • 所以 @Test 也称为标记注解 marker annotation , 但在自定义注解中一般都会包含一些元素以表示某些值。
DBTable.java
package top.rem.rain.annotation.demo;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
 * 对应数据表注解
 * @author LightRain
 */
// 作用于类上
@Target(ElementType.TYPE)
// 保留到运行期
@Retention(RetentionPolicy.RUNTIME)
public @interface DBTable {
  String name() default "";
}
  • 上述定义了一个名为 DBTable 的注解,该注解主要用于数据库表与 Bean 类的映射,与前面 Test 注解不同的是我们声明一个 String 类型的 name 元素,其默认值为空。
  • 但是必须注意到对应任何元素的声明采用方法的声明方式,同时可选择使用 default 提供默认值。
Member.java
package top.rem.rain.annotation.demo;
/**
 * @Author: LightRain
 * @Description: 数据表实体映射
 * @DateTime: 2023-11-10 16:02
 * @Version:1.0
 **/
// 在类上使用该注解
@DBTable(name = "MEMBER")
public class Member {
    //...
}
  • 关于注解支持的元素数据类型如下:
    • int
    • float
    • boolean
    • byte
    • double
    • char
    • long
    • short
    • String
    • Class
    • enum
    • Annotation
    • 以及上述类型的数组

注意:如使用了其它数据类型,编译器将会丢出一个编译错误,声明注解元素时可以使用基本类型但不允许使用任何包装类型,同时还应该注意到注解也可以作为元素的类型,也就是注解嵌套,以下代码演示上述类型的使用。

AnnotationElementDemo.java
package top.rem.rain.annotation.demo;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
 * 数据类型使用示例
 * @author LightRain
 */
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@interface Reference {
  boolean next() default false;
}
/**
 * @author LightRain
 */
public @interface AnnotationElementDemo {
  /**
   * 枚举类型
   */
  enum Status {FIXED, NORMAL};
  /**
   * 声明枚举
   * @return Status
   */
  Status status() default Status.FIXED;
  /**
   * 布尔类型
   * @return boolean
   */
  boolean showSupport() default false;
  /**
   * String 类型
   * @return String
   */
  String name() default "";
  /**
   * class 类型
   * @return Class
   */
  Class<?> testCase() default Void.class;
  /**
   * 注解嵌套
   * @return Reference
   */
  Reference reference() default @Reference(next = true);
  /**
   * 数组类型
   * @return long []
   */
  long[] value();
}

# 编译器对默认值的限制

  • 编译器对元素的默认值有些挑剔。
  • 首先,元素不能有不确定的值,也就是说元素必须要么具有默认值,要么在使用注解时提供元素的值。
  • 其次,对于非基本类型的元素,无论是在源代码中声明还是在注解接口中定义默认值,都不能以 null 作为值。
  • 但造成一个元素的存在或缺失状态,因为每个注解的声明中所有的元素都存在,并且都具有相应的值为了绕开这个限制只能定义一些特殊值,例如:空字符串或负数来表示某个元素不存在。

# 注解不能继承

  • 注解是不支持继承的,因此不能使用关键字 extends 来继承某个 @interface
  • 但注解在编译后,编译器会自动继承 java.lang.annotation.Annotation 接口。
  • 我们这里反编译一下前面定义的 DBTable 注解。
DBTable.java
package top.rem.rain.annotation.demo;
import java.lang.annotation.Annotation;
// 反编译后的代码
public interface DBTable extends Annotation {
    public abstract String name();
}

虽然反编译后发现 DBTable 注解继承了 Annotation 接口,请记住即使 Java 的接口可以实现多继承,但定义注解时依然无法使用 extends 关键字来继承 @interface

# 快捷方式

  • 快捷方式即使注解中定义了名为 value 的元素,并且在使用该注解是,如果该元素是唯一需要赋值的一个元素,那么此时无需使用 key=value 的语法。
  • 只需要在括号内给出 value 元素所需要的值即可,这可以应用于任何合法类型的元素,记住这限制了元素名必须为 value
QuicklyWay.java
package top.rem.rain.annotation.demo;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
 * @Author: LightRain
 * @Description: 快捷方式
 * @DateTime: 2023-11-10 16:29
 * @Version:1.0
 **/
// 定义注解
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@interface IntegerValue {
  int value() default 0;
  String name() default "";
}
/**
 * 使用注解
 * @author LightRain
 */
public class QuicklyWay {
  /**
   * 当只想给 value 赋值时,可以使用以下快捷方式
   */
  @IntegerValue(20)
  public int age;
  /**
   * 当 name 也需要赋值时必须采用 key=value 的方式赋值
   */
  @IntegerValue(value = 10000, name = "MONEY")
  public int money;
}

# 内置注解与其他元注解

  • Java 提供的内置注解主要有 5 个,如下:
    • @Override : 用于标识此方法重写了超类的方法,源码如下
    • @Deprecated : 用于标识过时的方法或类,源码如下
    • @SuppressWarnings : 用于抑制编译器警告,源码如下
    • @SafeVarargs : 用于抑制关于参数安全性的警告,源码如下
    • @FunctionalInterface : 用于标识一个接口类型声明是一个函数接口,源码如下

# @Override

Override.java
package java.lang;
import java.lang.annotation.*;
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
public @interface Override {
}

# @Deprecated

Deprecated.java
package java.lang;
import java.lang.annotation.*;
import static java.lang.annotation.ElementType.*;
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(value = {CONSTRUCTOR, FIELD, LOCAL_VARIABLE, METHOD, PACKAGE, MODULE, PARAMETER, TYPE})
public @interface Deprecated {
    String since() default "";
    boolean forRemoval() default false;
}

# @SuppressWarnings

  • 内部 String 数组主要接参数如下:
    • deprecation :使用了不赞成使用的类或方法时的警告。
    • unchecked :执行了未检查的转换时的警告,例如当使用集合时未使用泛型 ( Generice ) 来指定集合保存的类型。
    • fallthrough :当 Switch 程序块直接通往下一种情况而没有 break 时的警告。
    • path :在类路径、源文件路径等中有不存在的路径是的警告。
    • serial :当在可序列化的类上缺少 serialVersionUID 定义时的警告。
    • finally :然后 finally 子句不能正常完成时的警告。
    • all :关于以上所有情况的警告。
SuppressWarnings.java
package java.lang;
import java.lang.annotation.*;
import static java.lang.annotation.ElementType.*;
@Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE, MODULE})
@Retention(RetentionPolicy.SOURCE)
public @interface SuppressWarnings {
    String[] value();
}

# @SafeVarargs

SafeVarargs.java
package java.lang;
import java.lang.annotation.*;
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.CONSTRUCTOR, ElementType.METHOD})
public @interface SafeVarargs {
}

# @FunctionalInterface

FunctionalInterface.java
package java.lang;
import java.lang.annotation.*;
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface FunctionalInterface {
}

# 另外两种元注解

前面我们分析了两种元注解: @Target@Retention 除了这两种元注解外, Java 还提供了另外两种元注解, @Documented@Inherited

  • @Documented :被修饰的注解会生成到 Javadoc 中。
DocumentDemo.java
// 使用 @Documented
@Documented
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface DocumentA {
}
// 没有使用 @Documented
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface DocumentB {
}
// 使用注解
@DocumentA
@DocumentB
public class DocumentDemo {
    public void A(){
    }
}

使用 javadoc 命令生成文档:
javadoc DocumentDemo.java DocumentA.java DocumentB.java

  • @Inherited :可以让注解被继承,但这并不是真的继承,只是通过使用 @Inherited 可以让子类 Class 对象使用 getAnnotations() 方法来获取父类被 @Inherited 修饰的注解。
DocumentA.java
package top.rem.rain.annotation.demo.custom.annotations;
import java.lang.annotation.*;
/**
 * 带 @Inherited 的注解示例
 * @author LightRain
 */
// 使用 @Inherited
@Inherited
@Documented
@Target(java.lang.annotation.ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface DocumentA {
}
DocumentB.java
package top.rem.rain.annotation.demo.custom.annotations;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
 * 不带 @Inherited 注解示例
 * @author LightRain
 */
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface DocumentB {
}
DocumentDemo.java
package top.rem.rain.annotation.demo.custom.clas;
import top.rem.rain.annotation.demo.custom.annotations.DocumentA;
import top.rem.rain.annotation.demo.custom.annotations.DocumentB;
import java.util.Arrays;
@DocumentA
class A {
}
class B extends A {
}
@DocumentB
class C {
}
class D extends C {
}
/**
 * @Author: LightRain
 * @Description: 测试 @Inherited 注解
 * @DateTime: 2023-11-10 16:40
 * @Version:1.0
 **/
public class DocumentDemo {
  public static void main(String... args) {
    A instanceA = new B();
    System.out.println("已使用的@Inherited注解:" + Arrays.toString(instanceA.getClass().getAnnotations()));
    C instanceC = new D();
    System.out.println("没有使用的@Inherited注解:" + Arrays.toString(instanceC.getClass().getAnnotations()));
  }
    /*
      运行结果:
      已使用的 @Inherited 注解:[@top.rem.rain.annotation.demo.custom.annotations.DocumentA ()]
      没有使用的 @Inherited 注解:[]
     */
}

# 注解 & 反射机制

  • 前面经过反编译后,我们知道 Java 所有注解都继承了 Annotation 接口,也就是说 Java 使用 Annotation 接口代表注解元素,该接口是所有 Annotation 类型的父接口。
  • 同时为了运行时能准确获取到注解的相关信息, Javajava.lang.reflect 反射包下新增了 AnnotatedElement 接口。
  • 主要用于表示目前正在 VM 中运行的程序中已使用注解的元素,通过该接口提供的方法可以利用反射技术读取注解的信息。
  • 如反射包的 Constructor类Field类Method类Package类Class类 都实现了 AnnotatedElement 接口。
    • Class :类的 Class 对象定义。
    • Constructor :代表类的构造器定义。
    • Field :代表类的成员变量定义。
    • Method :代表类的方法定义。
    • Package :代表类的包定义。
  • 下面是 AnnotatedElement 中相关的 API 方法,以上 5 个类都实现以下的方法。
返回值方法名称说明
<A extends Annotation>getAnnotation(Class<A> annotationClass)该元素如果存在指定类型的注解,则返回这些注解,否则返回 null
Annotation[]getAnnotations()返回此元素上存在的所有注解,包括从父类继承的。
booleanisAnnotationPresent(Class<? extends Annotation> annotationClass)如果指定类型的注解存在于此元素上,则返回 true ,否则返回 false
Annotation[]getDeclaredAnnotations()返回直接存在于此元素上的所有注解,注意,不包括父类的注解,调用者可以随意修改返回的数组;这不会对其他调用者返回的数组产生任何影响,没有则返回长度为 0 的数组

案例如下:

DocumentDemo.java
package top.rem.rain.annotation.demo.custom.clas;
import top.rem.rain.annotation.demo.custom.annotations.DocumentA;
import top.rem.rain.annotation.demo.custom.annotations.DocumentB;
import java.lang.annotation.Annotation;
import java.util.Arrays;
@DocumentA
class A2 {
  //...
}
/**
 * @Author: LightRain
 * @Description: 继承了 A2 类
 * @DateTime: 2023-11-10 16:47
 * @Version:1.0
 **/
@DocumentB
public class DocumentDemo2 extends A2 {
  public static void main(String... args) {
    Class<?> clazz = DocumentDemo2.class;
    // 根据指定注解类型获取该注解
    DocumentA documentA = clazz.getAnnotation(DocumentA.class);
    System.out.println("A:" + documentA);
    // 获取该元素上的所有注解,包含从父类继承
    Annotation[] an = clazz.getAnnotations();
    System.out.println("an:" + Arrays.toString(an));
    // 获取该元素上的所有注解,但不包含继承!
    Annotation[] an2 = clazz.getDeclaredAnnotations();
    System.out.println("an2:" + Arrays.toString(an2));
    // 判断注解 DocumentA 是否在该元素上
    boolean b = clazz.isAnnotationPresent(DocumentA.class);
    System.out.println("b:" + b);
        /*
          执行结果:
         A:@top.rem.rain.annotation.demo.custom.annotations.DocumentA ()
         an:[@top.rem.rain.annotation.demo.custom.annotations.DocumentA (), @top.rem.rain.annotation.demo.custom.annotations.DocumentB ()]
         an2:[@top.rem.rain.annotation.demo.custom.annotations.DocumentB ()]
         b:true
         */
  }
}

# 注解处理器

  • 了解完注解与反射的相关 API 后,现在通过一个实例来演示利用运行时注解来创建数据库 SQL 构建语句的过程。
DBTable.java
package top.rem.rain.annotation.demo.custom.annotations;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
 * 对应数据表注解
 * @author LightRain
 */
// 作用于类上
@Target(ElementType.TYPE)
// 保留到运行期
@Retention(RetentionPolicy.RUNTIME)
public @interface DBTable {
  /**
   * 数据表名称
   * @return String
   */
  String name() default "";
}
SQLInteger.java
package top.rem.rain.annotation.demo.custom.annotations;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
 * 注解 Integer 类型的字段
 * @author LightRain
 */
// 只作用于字段
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface SQLInteger {
  /**
   * 该字段对应数据库表列名
   * @return String
   */
  String name() default "";
  /**
   * 嵌套注解
   * @return Constraints
   */
  Constraints constraint() default @Constraints;
}
SQLString.java
package top.rem.rain.annotation.demo.custom.annotations;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
 * 注解 String 类型的字段
 * @author LightRain
 */
// 只作用于字段
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface SQLString {
  /**
   * 对应数据库表的列名
   * @return String
   */
  String name() default "";
  /**
   * 列类型分配的长度,如:varchar (30)
   * @return int
   */
  int value() default 0;
  /**
   * 嵌套注解
   * @return Constraints
   */
  Constraints constraint() default @Constraints;
}
Constraints.java
package top.rem.rain.annotation.demo.custom.annotations;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
 * 约束注解
 * @author LightRain
 */
// 只作用于字段
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Constraints {
  /**
   * 判断是否作为主键约束
   * @return boolean
   */
  boolean primaryKey() default false;
  /**
   * 判断是否允许为 null
   * @return boolean
   */
  boolean allowNull() default false;
  /**
   * 判断是否唯一
   * @return boolean
   */
  boolean unique() default false;
}
Member.java
package top.rem.rain.annotation.demo.custom.clas;
import top.rem.rain.annotation.demo.custom.annotations.Constraints;
import top.rem.rain.annotation.demo.custom.annotations.DBTable;
import top.rem.rain.annotation.demo.custom.annotations.SQLInteger;
import top.rem.rain.annotation.demo.custom.annotations.SQLString;
/**
 * @Author: LightRain
 * @Description: 数据表实体映射
 * @DateTime: 2023-11-10 16:02
 * @Version:1.0
 **/
@DBTable(name = "MEMBER")
public class Member {
  /**
   * 主键 ID
   */
  @SQLString(name = "ID", value = 108, constraint = @Constraints(primaryKey = true))
  private String id;
  /**
   * 名称
   */
  @SQLString(name = "NAME", value = 30)
  private String name;
  /**
   * 年龄
   */
  @SQLInteger(name = "AGE")
  private int age;
  /**
   * 个人描述
   */
  @SQLString(name = "DESCRIPTION", value = 150, constraint = @Constraints(allowNull = true))
  private String description;
  public String getId() {
    return id;
  }
  public void setId(String id) {
    this.id = id;
  }
  public String getName() {
    return name;
  }
  public void setName(String name) {
    this.name = name;
  }
  public int getAge() {
    return age;
  }
  public void setAge(int age) {
    this.age = age;
  }
  public String getDescription() {
    return description;
  }
  public void setDescription(String description) {
    this.description = description;
  }
}
  • 上述定义 4 个注解,分别是 @DBTable(用于类)@Constraints(用于字段)@SQLString(用于字段)@SQLInteger(用于字段) 并在 Member 类中使用这些注解。
  • 这些注解的作用是用于帮助注解处理器生成创建数据库表 MEMBER 的构建语句,在这里需要注意的是,我们使用了嵌套注解 @Constraints 该注解主要用于判断字段是否为 null 或者字段是否唯一。
  • 必须清楚认识到上述提供的注解生命周期必须为 @Retention(RetentionPolicy.RUNTIME) 运行时,这样才可以使用反射机制获取其信息。
  • 剩余的就是编写上述的注解处理器,前面我们聊了很多注解,其处理器要么是 Java 自身已提供、要么是框架已提供的,我们自己都没有涉及到注解处理器的编写。
  • 但上述定义处理 SQL 的注解,其处理器必须由我们自己编写,如下。
TableCreator.java
package top.rem.rain.annotation.demo.custom.clas;
import top.rem.rain.annotation.demo.custom.annotations.Constraints;
import top.rem.rain.annotation.demo.custom.annotations.DBTable;
import top.rem.rain.annotation.demo.custom.annotations.SQLInteger;
import top.rem.rain.annotation.demo.custom.annotations.SQLString;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List;
/**
 * @Author: LightRain
 * @Description: 注解处理器,构造表创建语句
 * @DateTime: 2023-11-10 19:22
 * @Version:1.0
 **/
public class TableCreator {
  /**
   * 创建表 SQL
   *
   * @param className 类名称
   * @return String
   * @throws ClassNotFoundException 未找到类异常
   */
  public static String createTableSql(String className) throws ClassNotFoundException {
    // 获取字节码文件
    Class<?> cl = Class.forName(className);
    // 获取指定注解类型的注解
    DBTable dbTable = cl.getAnnotation(DBTable.class);
    // 如果没有表注解,直接返回
    if (dbTable == null) {
      System.out.println("No DBTable annotations in class " + className);
      return null;
    }
    // 获取数据表名称
    String tableName = dbTable.name();
    // 判断数据表名称长度
    if (tableName.length() < 1) {
      // 如果数据表名称为空则使用类名代替
      tableName = cl.getName().toUpperCase();
    }
    // 创建 List 集合
    List<String> columnDefs = new ArrayList<String>();
    // 通过字节码文件获取到所有成员字段
    for (Field field : cl.getDeclaredFields()) {
      String columnName = null;
      // 获取字段元素上的注解
      Annotation[] declaredAnnotations = field.getDeclaredAnnotations();
      // 判断长度是否存在注解,没有则跳过进入下次循环
      if (declaredAnnotations.length < 1) {
        continue;
      }
      // 判断注解类型
      if (declaredAnnotations[0] instanceof SQLInteger) {
        SQLInteger sqlInt = (SQLInteger) declaredAnnotations[0];
        // 获取字段对应名称,如果没有就是使用字段名称替代
        if (sqlInt.name().length() < 1) {
          columnName = field.getName().toUpperCase();
        } else {
          columnName = sqlInt.name();
        }
        // 构建语句
        columnDefs.add(columnName + " INT" + getConstraints(sqlInt.constraint()));
      }
      // 判断注解类型
      if (declaredAnnotations[0] instanceof SQLString) {
        SQLString sqlStr = (SQLString) declaredAnnotations[0];
        // 获取字段对应名称,如果没有就是使用字段名称替代
        if (sqlStr.name().length() < 1) {
          columnName = field.getName().toUpperCase();
        } else {
          columnName = sqlStr.name();
        }
        // 构建语句
        columnDefs.add(columnName + " VARCHAR(" + sqlStr.value() + ")" + getConstraints(sqlStr.constraint()));
      }
    }
    // 数据库表构建语句
    StringBuilder createCommand = new StringBuilder("CREATE TABLE " + tableName + "(");
    for (String columnDef : columnDefs) {
      createCommand.append("\n    ").append(columnDef).append(",");
    }
    // 删除尾部逗号并返回
    return createCommand.substring(0, createCommand.length() - 1) + ");";
  }
  /**
   * 判断该字段是否有其他约束
   *
   * @param con Constraints
   * @return String
   */
  private static String getConstraints(Constraints con) {
    // 约束条件
    String constraints = "";
    if (!con.allowNull()) {
      constraints += " NOT NULL";
    }
    // 主键
    if (con.primaryKey()) {
      constraints += " PRIMARY KEY";
    }
    // 唯一
    if (con.unique()) {
      constraints += " UNIQUE";
    }
    return constraints;
  }
  public static void main(String[] args) throws Exception {
    // 打印建表语句
    System.out.println("Table Creation SQL for " + "top.rem.rain.annotation.demo.custom.clas.Member" + " is :\n" + createTableSql(Member.class.getName()));
        /*
          输出结果:
         Table Creation SQL for top.rem.rain.annotation.demo.custom.clas.Member is :
         CREATE TABLE MEMBER (
         ID VARCHAR (108) NOT NULL PRIMARY KEY,
         NAME VARCHAR (30) NOT NULL,
         AGE INT NOT NULL,
         DESCRIPTION VARCHAR (150));
         */
  }
}
  • 上述我们通过传递 Member 的全路径通过 Class.forName() 方法获取到 Memberclass 对象,然后利用 Class 对象中的方法获取所有成员字段 Field
  • 最后我们利用 field.getDeclaredAnnotations() 方法遍历每个 Field 上的注解再通过注解的类型判断来构建建表语句。
  • 这便是利用注解结合反射来构建 SQL 语句的简单的处理器模型。

# 注解增强

# @Repeatable 元注解

  • @Repeatable 元注解是 JDK1.8 加入的,它表示在同一个位置重复相同的注解。
  • 在没有该注解前一般无法在同一个类型上使用相同的注解。
FilterDemo.java
package top.rem.rain.annotation.demo.custom.clas;
import top.rem.rain.annotation.demo.custom.annotations.FilterPath;
/**
 * @Author: LightRain
 * @Description: 使用重复过滤器注解示例,Java8 之前无法这样使用
 * @DateTime: 2023-11-11 18:46
 * @Version:1.0
 **/
@FilterPath("/web/post")
@FilterPath("/web/get")
public class FilterDemo {
    //...
}
  • Java8 之前如果想实现类似功能,我们需要定义 @FilterPath 注解时定义一个数组元素接收多个值。
FilterPath.java
package top.rem.rain.annotation.demo.custom.annotations;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
 * 自定义过滤器
 * @author LightRain
 */
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface FilterPath {
  String [] value();
}
FilterDemo2.java
package top.rem.rain.annotation.demo.custom.clas;
import top.rem.rain.annotation.demo.custom.annotations.FilterPath;
/**
 * @Author: LightRain
 * @Description: 过滤器注解使用示例
 * @DateTime: 2023-11-11 18:46
 * @Version:1.0
 **/
@FilterPath({"/web/post","/web/get"})
public class FilterDemo2 {
}
  • 但在 Java8 新增了 @Repeatable 注解后就可以采用以下方式定义并使用。
FilterPath.java
package top.rem.rain.annotation.demo.custom.annotations;
import java.lang.annotation.*;
/**
 * 自定义过滤器,Java8 增强
 * 使用 Java8 新增 @Repeatable 元注解
 *
 * @author LightRain
 */
@Target({ElementType.TYPE,ElementType.FIELD,ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
// 参数指明接收的注解 class
@Repeatable(FilterPaths.class)
public @interface FilterPath {
  String value();
}
FilterPaths.java
package top.rem.rain.annotation.demo.custom.annotations;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
 * 自定义过滤器数组
 * @author LightRain
 */
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface FilterPaths {
  /**
   * 此处使用 FilterPath 数组进行接收
   * @return FilterPath
   */
  FilterPath[] value();
}
FilterDemo.java
package top.rem.rain.annotation.demo.custom.clas;
import top.rem.rain.annotation.demo.custom.annotations.FilterPath;
/**
 * @Author: LightRain
 * @Description: 使用 java8 增强后的重复过滤器注解示例
 * @DateTime: 2023-11-11 18:58
 * @Version:1.0
 **/
@FilterPath("/web/get")
@FilterPath("/web/post")
public class FilterDemo {
    //...
}
  • 我们可以简单的理解为通过使用 @Repeatable 元注解后,将使用 @FilterPaths 注解作为接收同一个类型上重复注解的容器。
  • 而每个 @FilterPath 则负责保存指定的路径串,为了处理上述的新增注解, Java8 还在 AnnotatedElement 接口新增了 getDeclaredAnnotationsByType()getAnnotationsByType() 两个方法并在接口给出了默认实现。
  • 在指定 @Repeatable 的注解时可以通过这两个方法获取到注解相关信息,但请注意旧版 API 中的 getDeclaredAnnotationsByType()getAnnotation 是不对 @Repeatable 注解的处理的。
  • 除非该注解没有在同一个声明上重复出现,注意 getDeclaredAnnotationsByType() 方法获取到的注解不包含父类,其中当 getAnnotationsByType() 方法调用是其内部先执行了 getDeclaredAnnotationsByType 方法。
  • 只有当前类不存在指定注解时 getAnnotationsByType() 才会继续从其父类寻找,但请注意如果 @FilterPath@FilterPaths 没有使用了 @Inherited 的话扔然无法获取。
GetRepeatableAnnotationsInformation.java
package top.rem.rain.annotation.demo.custom.clas;
import top.rem.rain.annotation.demo.custom.annotations.FilterPath;
@FilterPath("/web/test")
class TestDemo {
}
/**
 * @Author: LightRain
 * @Description: 获取 @Repeatable 注解相关信息
 * @DateTime: 2023-11-11 19:03
 * @Version:1.0
 **/
@FilterPath("/web/get")
@FilterPath("/web/post")
public class GetRepeatableAnnotationsInformation extends TestDemo {
    public static void main(String[] args) {
        // 获取字节码对象
        Class<?> clazz = GetRepeatableAnnotationsInformation.class;
        // 通过 getAnnotationsByType 方法获取所有重复注解
        FilterPath[] annotationsByType = clazz.getAnnotationsByType(FilterPath.class);
        FilterPath[] annotationsByType2 = clazz.getDeclaredAnnotationsByType(FilterPath.class);
        for (FilterPath filter : annotationsByType) {
            System.out.println("1:" + filter.value());
        }
        System.out.println("-----------------");
        for (FilterPath filter : annotationsByType2) {
            System.out.println("2:" + filter.value());
        }
        System.out.println("使用getAnnotation的结果:" + clazz.getAnnotation(FilterPath.class));
        /*
         执行结果:当前类拥有该注解 @FilterPath 时则不会从 TestDemo 父类寻找
         1:/web/get
         1:/web/post
        -----------------
         2:/web/get
         2:/web/post
        使用 getAnnotation 的结果:null
         */
    }
}

咱们来看执行结果,如果当前类拥有 @FilterPath 注解则 getAnnotationsByType() 方法不会去父类中寻找,下面来看另一种情况,即 GetRepeatableAnnotationsInformation 类上面没有 @FilterPath 注解的示例。

FilterPath.java
package top.rem.rain.annotation.demo.custom.annotations;
import java.lang.annotation.*;
/**
 * 自定义过滤器,Java8 增强
 * 使用 Java8 新增 @Repeatable 元注解
 * 并添加 @Inherited 可继承元注解
 *
 * @author LightRain
 */
// 添加可继承的元注解
@Inherited
@Target({ElementType.TYPE,ElementType.FIELD,ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
// 参数指明接收的注解 class
@Repeatable(FilterPaths.class)
public @interface FilterPath {
  String value();
}
FilterPaths.java
package top.rem.rain.annotation.demo.custom.annotations;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
 * 自定义过滤器数组
 * 同样添加 @Inherited 可继承元注解
 * @author LightRain
 */
// 添加可继承的元注解
@Inherited
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface FilterPaths {
  /**
   * 此处使用 FilterPath 数组进行接收
   * @return FilterPath
   */
  FilterPath[] value();
}
GetRepeatableAnnotationsInformation.java
package top.rem.rain.annotation.demo.custom.clas;
import top.rem.rain.annotation.demo.custom.annotations.FilterPath;
@FilterPath("/web/test")
class TestDemo {
}
/**
 * @Author: LightRain
 * @Description: 获取父类 @Repeatable 注解相关信息
 * @DateTime: 2023-11-11 19:13
 * @Version:1.0
 **/
//@FilterPath("/web/get")
//@FilterPath("/web/post")
public class GetRepeatableAnnotationsInformation extends TestDemo {
    public static void main(String[] args) {
        // 获取字节码对象
        Class<?> clazz = GetRepeatableAnnotationsInformation.class;
        // 通过 getAnnotationsByType 方法获取所有重复注解
        FilterPath[] annotationsByType = clazz.getAnnotationsByType(FilterPath.class);
        FilterPath[] annotationsByType2 = clazz.getDeclaredAnnotationsByType(FilterPath.class);
        for (FilterPath filter : annotationsByType) {
            System.out.println("1:" + filter.value());
        }
        System.out.println("-----------------");
        for (FilterPath filter : annotationsByType2) {
            System.out.println("2:" + filter.value());
        }
        System.out.println("使用getAnnotation的结果:" + clazz.getAnnotation(FilterPath.class));
        /*
         执行结果:将当前类拥有的 @FilterPath 注解注释掉后 getAnnotationsByType () 方法会去父类寻找
         1:/web/test
         1:/web/demo
        -----------------
         使用 getAnnotation 的结果:null
         */
    }
}

需要值得注意的是在定义 @FilterPath@FilterPaths 注解时必须添加 @Inherited 元注解才可以通过父类被获取到,否则无法通过 getAnnotationsByType() 方法从父类中获取 @FilterPath 注解,这是为什么呢?下面来看下 getAnnotationsByType() 方法的实现原理。

AnnotatedElement.java
package java.lang.reflect;
import java.lang.annotation.Annotation;
import java.lang.annotation.AnnotationFormatError;
import java.lang.annotation.Repeatable;
import java.util.Arrays;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Objects;
import java.util.function.Function;
import java.util.stream.Collectors;
import sun.reflect.annotation.AnnotationSupport;
import sun.reflect.annotation.AnnotationType;
public interface AnnotatedElement {
  /**
   * 返回与该元素关联的注解,接口默认实现方法
   * @param annotationClass 注释类
   * @return T []
   * @param <T> T
   */
  default <T extends Annotation> T[] getAnnotationsByType(Class<T> annotationClass) {
    // 内部会先调用 getDeclaredAnnotationsByType () 方法
    T[] result = getDeclaredAnnotationsByType(annotationClass);
    // 判断当前类获取到的注解数组是否为 0
    if (result.length == 0 && this instanceof Class &&
            // 判断定义注解上是否使用了 @Inherited 元注解
            AnnotationType.getInstance(annotationClass).isInherited()) {
        // 从父类中获取
      Class<?> superClass = ((Class<?>) this).getSuperclass();
      if (superClass != null) {
        // 获取父类中的注释
        result = superClass.getAnnotationsByType(annotationClass);
      }
    }
    return result;
  }
}

# 两种新 ElementType 类型

  • ElementTypeJava8 中新增了两个新的枚举成员, TYPE_PARAMETERTYPE_USEJava8 前注解只能标注在一个声明的类、方法、字段上。
  • Java8 之后新增的 TYPE_PARAMETER 可以用于标注类型参数,而 TYPE_USE 则可以用于标注任意类型但不包含 class
TypeParam.java
package top.rem.rain.annotation.demo.custom.annotations;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
 * 使用 TYPE_PARAMETER 类型
 * TYPE_PARAMETER:表示该注解能写在类型参数的声明语句中,泛型 例如:<@ElementTypeDemo1 T>
 * @author LightRain
 */
@Target(ElementType.TYPE_PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
public @interface TypeParam {
}
Not.java
package top.rem.rain.annotation.demo.custom.annotations;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
 * 使用 TYPE_USE 类型
 * TYPE.USE:表示可以在任何用到类型的地方使用
 * @author LightRain
 */
@Target(ElementType.TYPE_USE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Not {
}
ElementTypeDemo.java
package top.rem.rain.annotation.demo.custom.clas;
import top.rem.rain.annotation.demo.custom.annotations.Not;
import top.rem.rain.annotation.demo.custom.annotations.TypeParam;
/**
 * @Author: LightRain
 * @Description: 类型注解 @TypeParam 跟 @Not
 * @DateTime: 2023-11-11 22:59
 * @Version:1.0
 **/
public class ElementTypeDemo<@TypeParam T> {
  @Not long lon;
  public <@TypeParam T extends Integer> void test(@Not String str){
    @Not boolean boo;
  }
}
package top.rem.rain.annotation.demo.custom.annotations;
import java.lang.annotation.ElementType;
import java.lang.annotation.Target;
/**
 * @author LightRain
 */
@Target(ElementType.TYPE_USE)
public @interface Path {
}
ElementTypeDemo.java
package top.rem.rain.annotation.demo.custom.clas;
import top.rem.rain.annotation.demo.custom.annotations.Not;
import top.rem.rain.annotation.demo.custom.annotations.Path;
import top.rem.rain.annotation.demo.custom.annotations.TypeParam;
/**
 * @Author: LightRain
 * @Description: 类型注解 @TypeParam 跟 @Not
 * @DateTime: 2023-11-11 22:59
 * @Version:1.0
 **/
public class ElementTypeDemo{
    public static void main(String[] args) {
        // 用于构造函数
        String path = new @Path String("/usr/bin");
        System.out.println(path);
        /*
        还可以用于一下两种情况: 
        用于指定异常
        public Person read () throws @Localized IOException.
  
        用于通配符绑定
        List<@ReadOnly ? extends Person>
        List<? extends @ReadOnly Person>
         */
    }
}

主要说明一下 TYPE_USE 注解类型是用来支持在 Java 程序中做强类型检查和配合第三方插件工具的如: CheckerFramework ,可以在编译期间检测出 RuntimeError 如: UnsupportedOperationExceptionNullPointerException 异常,避免异常延续到运行期后发现,从而提高代码质量这就是类型注解的作用。