JAVA注解

    技术2022-07-11  76

    注解

    注解是放在java源码的类、方法、字段、参数前的特殊的一种注释

    注解可以被编辑器打包进入class文件。因此,注解是作为标注的元数据

    作用

    从JVM的角度看,注解本身对代码逻辑没有任何影响,如何使用注解完全由工具决定 Java的注解可以分为三类: 编译器使用的注解: @Override @SuppressWarnings 这类注解不会被编译进入.class文件,它们在编译后就被编译器扔掉了 由工具处理.class文件使用的注解 这类注解会被编译进入.class文件,但加载结束后并不会存在于内存中。 动态修改类 这类注解只被一些底层库使用,一般我们不必自己处理 程序运行期能够读取的注解,它们在加载后一直存在于JVM中 一个配置了@PostConstruct的方法会在调用构造方法后自动被调用 (这是Java代码读取该注解实现的功能,JVM并不会识别该注解) 具体的功能了

    注解可以定义参数

    String; 枚举类型; 基本类型、String、Class以及枚举的数组。 大部分注解会有一个名为value的配置参数,对此参数赋值,可以只写常量,相当于省略了value参数 注解都有默认值 如果只写注解说明都是默认值

    定义注解的语法

    public @interface Report { int type() default 0; String level() default "info"; String value() default ""; } 注解的参数类似无参数方法,可以用default设定一个默认值(强烈推荐)。最常用的参数应当命名为value。

    元注解

    有一些注解可以修饰其他注解,这些注解就称为元注解(meta annotation)。 @Target 最常用的元注解是@Target。使用@Target可以定义Annotation能够被应用于源码的哪些位置 类或接口:ElementType.TYPE; 字段:ElementType.FIELD; 方法:ElementType.METHOD; 构造方法:ElementType.CONSTRUCTOR; 方法参数:ElementType.PARAMETER。 例子: @Target(ElementType.METHOD) public @interface Report { int type() default 0; String level() default "info"; String value() default ""; } @Retention 另一个重要的元注解@Retention定义了Annotation的生命周期: 仅编译期:RetentionPolicy.SOURCE; 仅class文件:RetentionPolicy.CLASS; 运行期:RetentionPolicy.RUNTIME。 如果@Retention不存在,则该Annotation默认为CLASS。因为通常我们自定义的Annotation都是RUNTIME, 所以,务必要加上@Retention(RetentionPolicy.RUNTIME)这个元注解: @Repeatable 使用这个注解说明可以使用多次 @Inherited 使用@Inherited定义子类是否可继承父类定义的Annotation。 @Inherited仅针对@Target(ElementType.TYPE)类型的annotation有效,并且仅针对class的继承,对interface的继承无效: @Inherited @Target(ElementType.TYPE) public @interface Report { int type() default 0; String level() default "info"; String value() default ""; } 在使用的时候,如果一个类用到了@Report: @Report(type=1) public class Person { } 则它的子类默认也定义了该注解: public class Student extends Person { }

    如何定义Annotation

    第一步,用@interface定义注解: public @interface Report { } 第二步,添加参数、默认值: public @interface Report { int type() default 0; String level() default "info"; String value() default ""; } 把最常用的参数定义为value(),推荐所有参数都尽量设置默认值。 第三步,用元注解配置注解: @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) public @interface Report { int type() default 0; String level() default "info"; String value() default ""; } 其中,必须设置@Target和@Retention,@Retention一般设置为RUNTIME, 因为我们自定义的注解通常要求在运行期读取。一般情况下,不必写@Inherited和@Repeatable。

    我们平时写的注解

    Retention有三个值;恰好是注解的三种类型 SOURCE类型的注解在编译期就被丢掉了; 给编辑器用的不进入class CLASS类型的注解仅保存在class文件中,它们不会被加载进JVM; 修改class用的 RUNTIME类型的注解会被加载进JVM,并且在运行期可以被程序读取。 这个是运行时用的 所以咱们要写

    读取注解

    isAnnotationPresent 判断注解是否使用 getAnnotation 获得注解 Person是标记注解的类 Report是注解 类 Person.class.isAnnotationPresent(Report.class) 字段 person.getClass().getField("name").isAnnotationPresent(Report.class) 方法 Method.getAnnotation(Class) 构造函数 Constructor.getAnnotation(Class) 难点是参数的注解: 例子 public void hello(@NotNull @Range(max=5) String name, @NotNull String prefix) { } // 获取Method实例: Method m = ... // 获取所有参数的Annotation: Annotation[][] annos = m.getParameterAnnotations(); // 第一个参数(索引为0)的所有Annotation: Annotation[] annosOfName = annos[0]; for (Annotation anno : annosOfName) { if (anno instanceof Range) { // @Range注解 Range r = (Range) anno; } if (anno instanceof NotNull) { // @NotNull注解 NotNull n = (NotNull) anno; } }

    使用注解

    @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.FIELD) public @interface Range { int min() default 0; int max() default 255; } 在某个JavaBean中,我们可以使用该注解: public class Person { @Range(min=1, max=20) public String name; @Range(max=10) public String city; } 但是,定义了注解,本身对程序逻辑没有任何影响。我们必须自己编写代码来使用注解。 这里,我们编写一个Person实例的检查方法,它可以检查Person实例的String字段长度是否满足@Range的定义: void check(Person person) throws IllegalArgumentException, ReflectiveOperationException { // 遍历所有Field: for (Field field : person.getClass().getFields()) { // 获取Field定义的@Range: Range range = field.getAnnotation(Range.class); // 如果@Range存在: if (range != null) { // 获取Field的值: Object value = field.get(person); // 如果值是String: if (value instanceof String) { String s = (String) value; // 判断值是否满足@Range的min/max: if (s.length() < range.min() || s.length() > range.max()) { throw new IllegalArgumentException("Invalid field: " + field.getName()); } } } } } 这样一来,我们通过@Range注解,配合check()方法,就可以完成Person实例的检查。 我写这个例子的时候就忘了,写 @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.FIELD)
    Processed: 0.011, SQL: 9