Skip to content

注解

定义注解

  • 注解是一种结构化接受检查的向代码中添加元数据的方法,可以帮助面出部署描述文件或其它生成文件的编写工作。是对代码的一种特殊标记,这些标记可以在编译,类加载和运行时被读取,并执行相应的处理

  • 注解通常包含一些可以设定值的元素,程序或工具在处理注解时可以使用参数,没有任何元素的注解为标记注解

  • 使用@interface 定义注解定义注解

    @Target(ElementType.METHOD)
    @Retention(RetentionPolicy.RUNTIME)
    public @interface name{
        int id();
        String description() default "no description";//默认值 
    }
    
    @UseCase(id = 47, description = "xxx")
    public void...//绑定注解的方法
    

  • 注解允许的元素类型:基本数据类型、String、Class、enum、Annotation(注解)、及以上类型的数组

  • 元素要么有默认值,要么在使用时必须给出值
  • 非基本类型不允许赋值为null

  • 标准注解

  • @Override
  • @Deprecated:如果元素被使用了,则编译器会发出警告
  • @SuppressWarnings:关闭不当的编译警告
  • @SafeVarargs:在使用泛型作为可变参数的方法或构造器中关闭警告
  • @FunctionalInterface:声明函数式接口

  • 元注解

  • 注解进行注解
  • @Target:该注解可用的地方
    • ElementType.CONSTRUCTOR:构造器声明
    • ElementType.FIELD:字段声明(包括枚举常量)
    • ElementType.LOCAL_VARIABLE:本地变量声明
    • ElementType.METHOD:方法声明
    • ElementType.PACKAGE:包声明
    • ElementType.PARAMETRE:参数声明
    • ElementType.TYPE:类、接口(含注解)枚举的声明
  • @Retention:注解信息可以保存多久
    • RetentionPolicy.SOURCE:注解会被编译器丢弃
    • RetentionPolicy.CLASS:可以被编译器使用但是会被虚拟机丢弃
    • RetentionPolicy.RUNTIME:运行时被虚拟机保留,可以通过反射读取注解信息
  • @Documented:在javadoc中引入注解
  • @Inherited:允许子类继承父类注解
  • @Repeatable:可以多次应用于同一个声明

  • 注解不支持继承,不能对@interface使用extends,但是可以通过内嵌注解实现类似的功能

  • 嵌套注解

  • 嵌套注解通常用于当一个注解的属性值本身就是一个注解
  • 这就是一个嵌套注解
    public @interface Uniqueness {
      Constraints constraints()
      default @Constraints(unique = true);
    }
    

注解处理器

  • 例:读取被注解的类,通过反射查找注解标签,通过注解的id查找出已经注册的测试案例

    public static void trackUseCases(List <Integer> useCases, Class <?> cl) {
        for(Method m : cl.getDeclaredMethods()) {
            UseCase uc = m.getAnnotation(UseCase.class);
            if(uc != null) {
                System.out.println("Found Use Case " +uc.id() + "\n  " + uc.description());
                useCases.remove(Integer.valueOf(uc.id()));
            }
        }
    }
    

  • 示例

    @Target(ElementType.TYPE) // Applies to classes only
    @Retention(RetentionPolicy.RUNTIME)
    public @interface DBTable {
      String name() default "";
    }
    
    @Target(ElementType.FIELD)
    @Retention(RetentionPolicy.RUNTIME)
    public @interface Constraints {
      boolean primaryKey() default false;
      boolean allowNull() default true;
      boolean unique() default false;
    }
    
    @Target(ElementType.FIELD)
    @Retention(RetentionPolicy.RUNTIME)
    public @interface SQLInteger {
      String name() default "";
      Constraints constraints() default @Constraints;
    }
    
    @Target(ElementType.FIELD)
    @Retention(RetentionPolicy.RUNTIME)
    public @interface SQLString {
      int value() default 0;
      String name() default "";
      Constraints constraints() default @Constraints;
    }
    
    public @interface Uniqueness {
      Constraints constraints()
      default @Constraints(unique = true);
    }
    
    @DBTable(name = "MEMBER")
    public class Member {
      @SQLString(30) String firstName;
      @SQLString(50) String lastName;
      @SQLInteger Integer age;
      @SQLString(value = 30,
      constraints = @Constraints(primaryKey = true))
      String reference;
      static int memberCount;
      public String getReference() { return reference; }
      public String getFirstName() { return firstName; }
      public String getLastName() { return lastName; }
      @Override public String toString() {
        return reference;
      }
      public Integer getAge() { return age; }
    }
    

  • 这套自定义注解的意义在于提供一种声明式的方法来描述一个Java类如何映射到数据库的表和字段。通过这些注解,你可以将类和字段的元数据(例如表名、字段名、字段类型、约束等)直接嵌入到Java代码中。这种方式的好处是代码更清晰、更直观,并且可以由框架或工具在运行时动态解析,从而自动生成SQL语句或执行其他数据库相关操作。

  • 根据注解读取文件创建 SQL 数据库

    public class TableCreator {
      public static void
      main(String [] args) throws Exception {
        if(args.length < 1) {
          System.out.println(
            "arguments: annotated classes");
          System.exit(0);
        }
          //参数标识已经标注好需要进行自动构建的类
        for(String className : args) {
          Class <?> cl = Class.forName(className);
            //获取注解
          DBTable dbTable = cl.getAnnotation(DBTable.class);
          if(dbTable == null) {
            System.out.println(
              "No DBTable annotations in class " +
              className);
            continue;
          }
          String tableName = dbTable.name();
          // If the name is empty, use the Class name:
          if(tableName.length() < 1)
            tableName = cl.getName().toUpperCase();
          List <String> columnDefs = new ArrayList <>();
           //检查所有字段
          for(Field field : cl.getDeclaredFields()) {
            String columnName = null;
              //获取字段上的注解
            Annotation [] anns =
              field.getDeclaredAnnotations();
            if(anns.length < 1)
              continue; // Not a db table column
              //根据类型自动执行 SQL 命令进行构建
            if(anns [0] instanceof SQLInteger) {
              SQLInteger sInt = (SQLInteger) anns [0];
              if(sInt.name().length() < 1)
                columnName = field.getName().toUpperCase();
              else
                columnName = sInt.name();
              columnDefs.add(columnName + " INT" +
                getConstraints(sInt.constraints()));
            }
            if(anns [0] instanceof SQLString) {
              SQLString sString = (SQLString) anns [0];
              // Use field name if name not specified.
              if(sString.name().length() < 1)
                columnName = field.getName().toUpperCase();
              else
                columnName = sString.name();
              columnDefs.add(columnName + " VARCHAR(" +
                sString.value() + ")" +
                getConstraints(sString.constraints()));
            }
            StringBuilder createCommand = new StringBuilder(
              "CREATE TABLE " + tableName + "(");
            for(String columnDef : columnDefs)
              createCommand.append(
                "\n    " + columnDef + ",");
            // Remove trailing comma
            String tableCreate = createCommand.substring(
              0, createCommand.length() - 1) + ");";
            System.out.println("Table Creation SQL for " +
              className + " is:\n" + tableCreate);
          }
        }
      }
      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;
      }
    

用javac处理-编译时注解*
  • 通过javac可以创建编译时注解处理器,将注解用于java源文件而不是编译之后的文件。
  • 不能通过注解来修改源代码,唯一能影响结果的方法是创建新文件
  • 如果注解创建了新文件,新一轮处理中会检查该文件自身的注解。工具会一轮一轮的持续处理,直到没有新的源文件被创建
  • 需要创建一个注解处理器并绑定到编译器
  • @SupportedAnnotationTypes("com.class")指定注解处理器支持的注解类型
    • 这意味着注解处理器会处理所有被 com.class 注解标记的元素。
    • 使用这个注解能够提高处理器的查找效率,并且使得处理器的意图更加明确。
  • @SupportedSourceVersion(SourceVersion.RELEASE_8)注解用于指定注解处理器支持的源代码版本。在这个例子中,它表明处理器支持Java 8的源代码版本。

    • 避免在与处理器不兼容的Java版本上运行时可能出现的问题。
    • 默认情况下,注解处理器被假定为支持最新的源码版本。
  • Element 是用于表示和操作元素在源代码中的结构和信息的基础。

  • PackageElement:表示一个。提供对包名、包内成员等的访问。
  • TypeElement:表示一个类或接口。提供对类名、父类、接口、方法和字段等的访问。
  • ExecutableElement:表示某些可执行的元素,如方法、构造函数或初始化器(静态或实例)。
  • VariableElement:表示一个字段、枚举常量、方法或构造函数的参数、局部变量或异常参数。
  • getKind():返回此元素的类型(如类、方法、字段等)。
  • getModifiers():返回此元素的修饰符(如 publicprivatestatic 等)。
  • getSimpleName():返回此元素的简单(不带包名的)名称。
  • getEnclosingElement():返回包含此元素的最近的上层元素。
  • getAnnotation(Class<A> annotationType):返回此元素上特定类型的注解。

  • 示例

//注解定义
@Retention(RetentionPolicy.SOURCE)
@Target({ElementType.TYPE, ElementType.METHOD,
         ElementType.CONSTRUCTOR,
         ElementType.ANNOTATION_TYPE,
         ElementType.PACKAGE, ElementType.FIELD,
         ElementType.LOCAL_VARIABLE})
public @interface Simple {
    String value() default "-default-";
}
//使用注解
@Simple
public class SimpleTest {
  @Simple
  int i;
  @Simple
  public SimpleTest() {}
  @Simple
  public void foo() {
    System.out.println("SimpleTest.foo()");
  }
  @Simple
  public void bar(String s, int i, float f) {
    System.out.println("SimpleTest.bar()");
  }
  @Simple
  public static void main(String [] args) {
    @Simple
    SimpleTest st = new SimpleTest();
    st.foo();
  }
}
//处理器(仅仅打印注解的信息)
@SupportedAnnotationTypes(
  "annotations.simplest.Simple")
@SupportedSourceVersion(SourceVersion.RELEASE_8)
//继承标识是一个注解处理器
public class SimpleProcessor
extends AbstractProcessor {
  @Override public boolean process(
    Set <? extends TypeElement> annotations,
    RoundEnvironment env) {
      //打印所有待处理的注解类型
    for(TypeElement t : annotations)
      System.out.println(t);
    for(Element el : env.getElementsAnnotatedWith(Simple.class))
        //显示所有被注解标记的元素
      display(el);
    return false;
  }
  private void display(Element el) {
    System.out.println("==== " + el + " ====");
    System.out.println(el.getKind() +
      " : " + el.getModifiers() +
      " : " + el.getSimpleName() +
      " : " + el.asType());
      //不同类型不同显示
    if(el.getKind().equals(ElementKind.CLASS)) {
      TypeElement te = (TypeElement)el;
      System.out.println(te.getQualifiedName());
      System.out.println(te.getSuperclass());
      System.out.println(te.getEnclosedElements());
    }
    if(el.getKind().equals(ElementKind.METHOD)) {
      ExecutableElement ex = (ExecutableElement)el;
      System.out.print(ex.getReturnType() + " ");
      System.out.print(ex.getSimpleName() + "(");
      System.out.println(ex.getParameters() + ")");
    }
  }
}
  • 正常编译是不会使用注解处理器,要加上参数-processor com.注解类

  • 在编译时注解处理器中不能使用反射,因为操作的是源代码而不是编译后的类,使用镜子查看源代码中的方法字段类型等信息

    @Target(ElementType.TYPE)
    @Retention(RetentionPolicy.SOURCE)
    public @interface ExtractInterface {
        String interfaceName() default "-!!-";
    }
    //一个简单的乘法计算类
    @ExtractInterface(interfaceName = "IMultiplier")
    public class Multiplier {
        public boolean flag = false;
        private int n = 0;
        public int multiply(int x, int y) {
            int total = 0;
            for(int i = 0; i < x; i++)
                total = add(total, y);
            return total;
        }
        public int fortySeven() { return 47; }
        private int add(int x, int y) {
            return x + y;
        }
        public double timesTen(double arg) {
            return arg * 10;
        }
        public static void main(String [] args) {
            Multiplier m = new Multiplier();
            System.out.println(
                " 11 * 16 = " + m.multiply(11, 16));
        }
    }
    //处理器,提取出方法创建新接口的源代码文件
    @SupportedAnnotationTypes(
        "annotations.ifx.ExtractInterface")
    @SupportedSourceVersion(SourceVersion.RELEASE_8)
    public class IfaceExtractorProcessor
        extends AbstractProcessor {
        //存储符合条件的方法字段
        private ArrayList <Element>
            interfaceMethods = new ArrayList <>();
        Elements elementUtils;
        private ProcessingEnvironment processingEnv;
        @Override public void init(ProcessingEnvironment processingEnv) {
            this.processingEnv = processingEnv;
            elementUtils = processingEnv.getElementUtils();
        }
        @Override public boolean process(
            Set <? extends TypeElement> annotations,
            RoundEnvironment env) {
            //处理有目标注解的字段
            for(Element elem: env.getElementsAnnotatedWith(
                ExtractInterface.class)) {
                //获取设置的名称参数
                String interfaceName = elem.getAnnotation(
                    ExtractInterface.class).interfaceName();
                //检查目标类的所有封闭元素(字段、方法、构造器等)
                for(Element enclosed :
                    elem.getEnclosedElements()) {
    
                    //寻找 public static 方法
                    if(enclosed.getKind()
                       .equals(ElementKind.METHOD) &&
                       enclosed.getModifiers()
                       .contains(Modifier.PUBLIC) &&
                       ! enclosed.getModifiers()
                       .contains(Modifier.STATIC)) {
                        interfaceMethods.add(enclosed);
                    }
                }
                if(interfaceMethods.size() > 0)
                    writeInterfaceFile(interfaceName);
            }
            return false;
        }
        //创建并写入接口
        private void
            writeInterfaceFile(String interfaceName) {
            try(
                Writer writer = processingEnv.getFiler()
                .createSourceFile(interfaceName)
                .openWriter()
            ) {
                String packageName = elementUtils
                    .getPackageOf(interfaceMethods
                                  .get(0)).toString();
                writer.write(
                    "package " + packageName + ";\n");
                writer.write("public interface " +
                             interfaceName + " {\n");
                for(Element elem : interfaceMethods) {
                    ExecutableElement method =
                        (ExecutableElement)elem;
                    String signature = "  public ";
                    signature += method.getReturnType() + " ";
                    signature += method.getSimpleName();
                    signature += createArgList(
                        method.getParameters());
                    System.out.println(signature);
                    writer.write(signature + ";\n");
                }
                writer.write("}");
            } catch(Exception e) {
                throw new RuntimeException(e);
            }
        }
        private String createArgList(
            List <? extends VariableElement> parameters) {
            String args = parameters.stream()
                .map(p -> p.asType() + " " + p.getSimpleName())
                .collect(Collectors.joining(", "));
            return "(" + args + ")";
        }
    }
    

  • 得到的文件

    //IMultiplier.java
    package annotations.ifx;
    public interface IMultiplier{
        public int multiply(int x, int y);
        public int fortySeven();
        public double timesTen(double arg);
    }