侧边栏壁纸
  • 累计撰写 98 篇文章
  • 累计创建 20 个标签
  • 累计收到 3 条评论

java注解学习笔记

林贤钦
2020-12-08 / 0 评论 / 0 点赞 / 543 阅读 / 9,035 字
温馨提示:
本文最后更新于 2021-03-03,若内容或图片失效,请留言反馈。部分素材来自网络,若不小心影响到您的利益,请联系我们删除。

java注解学习笔记

最近在做一个功能,登录日志和操作日志等相关日志的记录,用注解+SpringAOP的切面技术+线程池的异步调用实现,所以来整理一下关于注解的知识点,稍后会整理如何使用Annotation+Aspect+线程池实现登录日志和操作日志功能。在这里先简单介绍注解以及注解的使用,然后根据测试结果,分析注解的功能。

注解的简介

注解也叫元数据,比如我们开发遇到最多的@Override和@Deprecated,注解是JDK1.5版本开始引入的一个特性,用于对代码进行说明,可以对包、类、接口、字段、方法参数、局部变量等进行注解。

注解分类:

  • Java自带的标准注解,包括@Override(标明重写某个方法)、@Deprecated(标明某个类或方法过时)和@SuppressWarnings(标明要忽略的警告),使用这些注解后编译器就会进行检查。

  • 元注解,元注解是用于定义注解的注解,包括@Retention(标明注解被保留的阶段)、@Target(标明注解使用的范围)、@Inherited(标明注解可继承)、@Documented(标明是否生成javadoc文档)

  • 自定义注解,可以根据自己的需求定义注解

注解的用途

  1. 生成文档,通过代码里标识的元数据生成javadoc文档。
  2. 编译检查,通过代码里标识的元数据让编译器在编译期间进行检查验证。
  3. 编译时动态处理,编译时通过代码里标识的元数据动态处理,例如动态生成代码。
  4. 运行时动态处理,运行时通过代码里标识的元数据动态处理,例如使用反射注入实例

注解和XML区别

  • 注解是一种分散式的元数据,与源代码紧绑定

  • xml是一种集中式的元数据,与源代码无绑定

注解的使用测试

定义以下4个注解来演示注解的作用

  1. 定义一个可以注解在Class,interface,enum上的注解
  2. 定义一个可以注解在METHOD上的注解
  3. 定义一个可以注解在FIELD上的注解
  4. 定义一个可以注解在PARAMETER上的注解
/**
 * 定义一个可以注解在Class,interface,enum上的注解
 */
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnTargetType {
    /**
     * 定义注解的一个元素 并给定默认值
     * @return
     */
    String value() default "我是定义在类接口枚举类上的注解元素value的默认值";
}
/**
 * 定义一个可以注解在METHOD上的注解
 */
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnTargetMethod {
    /**
     * 定义注解的一个元素 并给定默认值
     */
    String value() default "我是定义在方法上的注解元素value的默认值";
    String value2() default "我是定义在方法上的注解元素value2的默认值";
}
/**
 * 定义一个可以注解在FIELD上的注解
 */
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnTargetField {
    /**
     * 定义注解的一个元素 并给定默认值
     */
    String value() default "我是定义在字段上的注解元素value的默认值";
}
/**
 * 定义一个可以注解在PARAMETER上的注解
 */
@Target({ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnTargetParameter {
    /**
     * 定义注解的一个元素 并给定默认值
     */
    String value() default "我是定义在参数上的注解元素value的默认值";
}

编写一个测试处理类处理以上注解

@MyAnTargetType
public class AnnotationTest {
    @MyAnTargetField
    private String field = "我是字段";

    @MyAnTargetMethod("value1测试方法")
    public void test(@MyAnTargetParameter String args) {
        System.out.println("参数值 === "+args);
    }
    @MyAnTargetMethod(value2 = "value2测试方法")
    public void test2(@MyAnTargetParameter String args) {
        System.out.println("参数值 === "+args);
    }
    public static void main(String[] args) {
        // 获取类上的注解MyAnTargetType
        MyAnTargetType t = AnnotationTest.class.getAnnotation(MyAnTargetType.class);
        System.out.println("类上的注解值 === "+t.value());
        MyAnTargetMethod tm = null;
        try {
            // 根据反射获取AnnotationTest类上的test方法
            Method method = AnnotationTest.class.getDeclaredMethod("test",String.class);
            // 获取方法上的注解MyAnTargetMethod
            tm = method.getAnnotation(MyAnTargetMethod.class);
            System.out.println("方法上的注解值 === "+tm.value());
            // 获取方法上的所有参数注解  循环所有注解找到MyAnTargetParameter注解
            Annotation[][] annotations = method.getParameterAnnotations();
            for(Annotation[] tt : annotations){
                for(Annotation t1:tt){
                    if(t1 instanceof MyAnTargetParameter){
                        System.out.println("参数上的注解值 === "+((MyAnTargetParameter) t1).value());
                    }
                }
            }
            // 根据反射获取AnnotationTest类上的test2方法
            Method method2 = AnnotationTest.class.getDeclaredMethod("test2",String.class);
            // 获取方法上的注解MyAnTargetMethod
            tm = method2.getAnnotation(MyAnTargetMethod.class);
            System.out.println("方法上的注解值 === "+tm.value2());
            method.invoke(new AnnotationTest(), "改变默认参数");
            // 获取AnnotationTest类上字段field的注解MyAnTargetField
            MyAnTargetField fieldAn = AnnotationTest.class.getDeclaredField("field").getAnnotation(MyAnTargetField.class);
            System.out.println("字段上的注解值 === "+fieldAn.value());
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

运行结果如下:

类上的注解值 = = = 我是定义在类接口枚举类上的注解元素value的默认值
方法上的注解值 = = = value1测试方法
参数上的注解值 = = = 我是定义在参数上的注解元素value的默认值
方法上的注解值 = = = value2测试方法
参数值 = = = 改变默认参数
字段上的注解值 === 我是定义在字段上的注解元素value的默认值

注解案例分析

每个注解上面都有@Target和@Retention,这是java的元注解,在java中,元注解有4个,这4个元注解都是在jdk的java.lang.annotation包下面。

  • @Target :注解的作用目标
  • @Retention :注解的生命周期
  • @Documented :注解是否应当被包含在 JavaDoc 文档中
  • @Inherited : 是否允许子类继承该注解

@Target源码如下

@Target 用于指明被修饰的注解最终可以作用的目标是谁,也就是指明,你的注解到底是用来修饰方法的?修饰类的?还是用来修饰字段属性的。

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Target {
    /**
     * Returns an array of the kinds of elements an annotation type
     * can be applied to.//返回可以应用批注类型的元素类型的数组
     * @return an array of the kinds of elements an annotation type
     * can be applied to //可以应用批注类型的元素的数组
     */
    ElementType[] value();
}

源码的注释已经说明了,这是一个数组,包含了各种可以批注的类型

public enum ElementType {
    /** Class, interface (including annotation type), or enum declaration
     * 类、接口(包括注释类型)或枚举声明
     */
    TYPE,

    /** Field declaration (includes enum constants) 字段声明(包括枚举常量) */
    FIELD,

    /** Method declaration 方法声明*/
    METHOD,

    /** Formal parameter declaration 形式参数声明*/
    PARAMETER,

    /** Constructor declaration 构造函数声明*/
    CONSTRUCTOR,

    /** Local variable declaration 局部变量声明*/
    LOCAL_VARIABLE,

    /** Annotation type declaration 注释类型声明*/
    ANNOTATION_TYPE,

    /** Package declaration 包声明*/
    PACKAGE,

    /**
     * Type parameter declaration  类型参数声明
     *
     * @since 1.8
     */
    TYPE_PARAMETER,

    /**
     * Use of a type 类型的使用
     *
     * @since 1.8
     */
    TYPE_USE,

    /**
     * Module declaration. 模块
     *
     * @since 9
     */
    MODULE
}

@Retention源码如下

@Retention 注解指定了被修饰的注解的生命周期,一种是只能在编译期可见,编译后会被丢弃,一种会被编译器编译进 class 文件中,无论是类或是方法,乃至字段,他们都是有属性表的,而 JAVA 虚拟机也定义了几种注解属性表用于存储注解信息,但是这种可见性不能带到方法区,类加载时会予以丢弃,最后一种则是永久存在的可见性。

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Retention {
    /**
     * Returns the retention policy. 返回保留策略
     * @return the retention policy
     */
    RetentionPolicy value();
}

RetentionPolicy枚举了三种保存策略:编译器可见编译后丢弃、编译进class文件(默认)、永久存在的可见性

public enum RetentionPolicy {
    /**
     * Annotations are to be discarded by the compiler.  注释将被编译器丢弃。
     */
    SOURCE,

    /**
     * Annotations are to be recorded in the class file by the compiler
     * but need not be retained by the VM at run time.  This is the default
     * behavior.
     * 注释由编译器记录在类文件中,但不需要在运行时由VM保留。这是默认行为。
     */
    CLASS,

    /**
     * Annotations are to be recorded in the class file by the compiler and
     * retained by the VM at run time, so they may be read reflectively.
     * 注释将由编译器记录在类文件中,并在运行时由VM保留,因此可以反射性地读取它们。
     * @see java.lang.reflect.AnnotatedElement
     */
    RUNTIME
}

从上面的案例也能看出,下面两个实际上用到的并不多,也比较简单,需要重点研究的是前面两个注解

@Documented 注解修饰的注解,当我们执行 JavaDoc 文档打包时会被保存进 doc 文档,反之将在打包时丢弃。

@Inherited 注解修饰的注解是具有可继承性的,也就说我们的注解修饰了一个类,而该类的子类将自动继承父类的该注解。

到这里,我们就知道了注解上的4个元注解是什么意思了,接下来是该研究注解里面的方法是什么玩意了

注解方法

观察每个注解,都有一个value,后面跟着一个default,而在MyAnTargetMethod中,我多定义了一个value2,也就是说,默认情况下,我们注释了注解,但是没赋值,注解会默认使用我们定义的default默认值,赋值的时候可以不指明参数方法,则会使用value方法,如果需要指明其他方法,则需要在使用注解的时候指明,如@MyAnTargetMethod(value2 = "value2测试方法")。

知道了注解方法的使用,那么还有一个疑惑,如何获取注解上的内容?

如何获取注解

获取注解内容,可以结合反射来实现,注解在不同位置,就用反射的不同技术实现。比如上面的测试,写的很详细

  • 类注解:获取类上的注解MyAnTargetType

    MyAnTargetType t = AnnotationTest.class.getAnnotation(MyAnTargetType.class);

  • 方法注解:根据反射获取AnnotationTest类上的test方法然后获取方法上的注解

    Method method = AnnotationTest.class.getDeclaredMethod("test",String.class);MyAnTargetMethod tm =method.getAnnotation(MyAnTargetMethod.class);

  • 参数注解:获取到方法后,循环所有注解找到MyAnTargetParameter注解

    Annotation[][] annotations = method.getParameterAnnotations(); for(Annotation[] tt : annotations){ for(Annotation t1:tt){ if(t1 instanceof MyAnTargetParameter){ System.out.println("参数上的注解值 === "+((MyAnTargetParameter) t1).value()); } } }

  • 字段注解:获取AnnotationTest类上字段field的注解MyAnTargetField

    MyAnTargetField fieldAn = AnnotationTest.class.getDeclaredField("field").getAnnotation(MyAnTargetField.class);

通过反射获取到对象后,我们通过以下几个方法来获取注解(Annotation)的信息

  • <T extends Annotation >T getAnnotation(Class <T> annotationClass):返回改程序元素上存在的、指定类型的注解,如果该类型注解不存在,则返回null
  • Annotation[] getAnnotations(): 返回该程序元素上存在的所有注解。
  • boolean is AnnotationPresent(Class annotationClass):判断该程序元素上是否包含指定类型的注解,存在则返回true,否则返回false
  • Annotation[] getDeclaredAnnotations():返回直接存在于此元素上的所有注释。与此接口中的其他方法不同,该方法将忽略继承的注释。(如果没有注释直接存在于此元素上,则返回长度为零的一个数组。)该方法的调用者可以随意修改返回的数组;这不会对其他调用者返回的数组产生任何影响

《参考文献》

《深入理解java注解的实现原理(转载)》

JAVA 注解的基本原理

java注解的本质以及注解的底层实现原理

0

评论区