Java 注解

作者: wind_sky | 来源:发表于2019-08-07 20:04 被阅读3次

    一. 简介

    注解是java5的新特性。注解可以看做一种注释或者元数据(MetaData),可以把它插入到我们的java代码中,用来描述我们的java类,从而影响java类的行为。

    二. 基本语法

    1. 注解的使用形式:

    一个java注解由一个@符后面跟一个字符串构成,类似于这样:

    @Entity

    java注解中一般包含一些元素,这些元素类似于属性或者参数,可以用来设置值,比如我们有一个包含两个元素的@Entity注解:

    @Entity(tableName = "vehicles", primaryKey = "id")上面注解中有两个元素,tableName和primaryKey,它们各自都被赋予了自己的元素值。

    上面注解中有两个元素,tableName和primaryKey,它们各自都被赋予了自己的元素值。

    2. 注解作用域:

    注解可以用于描述一个类、接口、方法、方法参数、字段、局部变量等,例如

    //注解一个类
    @Entity
    public class TestClass {
        //注解一个字段
        @Persistent
        protected String name = null;
        //注解一个方法
        @Getter
        public String getName() {
            return this.name;
        }
        //注解一个参数
        public void setName(@Optional name) {
            this.name = name;
        }
    
        public String testLocal(String name) {
            //注解一个局部变量
            @Optional
            List localNames = names;
            ...
        }
    }
    
    3. 声明自定义注解:

    创建自定义的注解也比较简单,只需要使用 @interface 关键字即可,如

    @interface MyAnnotation {}
    

    这样就创建了一个最简单的注解。

    注意,注解是不支持继承的,因此不能使用关键字extends来继承某个@interface,但注解在编译后,编译器会自动继承java.lang.annotation.Annotation接口。

    4. 元注解:

    所谓元注解就是标记其他注解的注解,常用的有以下几种:
    1)@Target ,用来约束注解可以应用的地方,即上面提到的作用域,类、方法等。这个元注解接收的值是一个枚举类 ElementType 的数组,这个枚举类有以下值:

    public enum ElementType {
        /** 标明该注解可以用于类、接口(包括注解类型)或enum声明*/
        TYPE,
    
        /** 标明该注解可以用于字段(域)声明,包括enum实例 */
        FIELD,
    
        /** 标明该注解可以用于方法声明 */
        METHOD,
    
        /** 标明该注解可以用于参数声明 */
        PARAMETER,
    
        /** 标明注解可以用于构造函数声明 */
        CONSTRUCTOR,
    
        /** 标明注解可以用于局部变量声明 */
        LOCAL_VARIABLE,
    
        /** 标明注解可以用于注解声明(应用于另一个注解上)*/
        ANNOTATION_TYPE,
    
        /** 标明注解可以用于包声明 */
        PACKAGE,
    
        /** 标明注解可以用于类型参数(泛型)声明,1.8新加入 */
        TYPE_PARAMETER,
    
        /** 类型使用声明,可以用于标注任何类型,但不包括Class,1.8新加入 */
        TYPE_USE
    }
    

    比如下例,声明的注解只能注解于Java 类型 和 方法

    @Target({ElementType.TYPE, ElementType.METHOD})
    @interface MyAnnotation {}
    

    请注意,当注解未指定Target值时,则此注解可以用于任何元素之上。

    2)@Retention ,用来规定注解的作用时机,该元注解有三种取值,

    • RetentionPolicy.SOURCE : 注解只存在于源码中,不会存在于.class文件中,在编译时会被忽略掉

    • RetentionPolicy.CLASS:注解只存在于.class文件中,在编译期有效,但是在运行期会被忽略掉,这也是默认范围

    • RetentionPolicy.RUNTIME:在运行期有效,JVM在运行期通过反射获得注解信息(源码、class文件和运行时都有注解的信息),如SpringMvc中的@Controller、@Autowired、@RequestMapping等。

    @Target({ElementType.TYPE, ElementType.METHOD})
    @Retention(RetentionPolicy.RUNTIME)
    @interface MyAnnotation {}
    

    上例表示MyAnnotation 注解在运行时有效,JVM会在运行时通过反射机制获取注解信息。

    3)@Document ,被修饰的注解会生成到javadoc中,即当使用Java 命令生成JavaDoc 时,使用@Documented元注解定义的注解将会生成到javadoc中, 而没有此元注解的注解则不会在doc文档中出现。

    4)@Inherited ,可以让注解被继承,但这并不是真的继承,只是通过使用@Inherited,可以让子类Class对象使用getAnnotations()获取父类被@Inherited修饰的注解,如

    @Inherited
    @Target({ElementType.TYPE, ElementType.METHOD})
    @Retention(RetentionPolicy.SOURCE)
    @interface MyAnnotation {}
    
    @MyAnnotation
    class ParentClass {}
    
    class ChildClass extends ParentClass {}
    

    因为有了@Inherited 声明,所以ChildClass 也从父类ParentClass 那继承了这个注解。

    5)@Repeatable ,JDK1.8新加入的,它表示在同一个位置重复相同的注解。在没有该注解前,一般是无法在同一个类型上使用相同的注解的。

    //Java8前无法这样使用
    @FilterPath("/web/update")
    @FilterPath("/web/add")
    public class A {}
    

    下例显示了如何使用@Repeatable 元注解

    @Target({ElementType.TYPE,ElementType.FIELD,ElementType.METHOD})
    @Retention(RetentionPolicy.RUNTIME)
    @Repeatable(FilterPaths.class)              //参数指明接收的注解class
    public @interface FilterPath {
        String  value();
    }
    
    @Target(ElementType.TYPE)
    @Retention(RetentionPolicy.RUNTIME)
    @interface FilterPaths {
        FilterPath[] value();                   // 接收的注解必须声明的元素
    }
    

    通过使用@Repeatable后,将使用@FilterPaths注解作为接收同一个类型上重复注解的容器,而每个@FilterPath则负责保存指定的路径串。

    为了处理上述的新增注解,Java8还在AnnotatedElement接口新增了getDeclaredAnnotationsByType() 和 getAnnotationsByType()两个方法并在接口给出了默认实现,在指定@Repeatable的注解时,可以通过这两个方法获取到注解相关信息。

    注意,旧版API中的getDeclaredAnnotation() 和 getAnnotation() 是不对@Repeatable注解的处理的(除非该注解没有在同一个声明上重复出现)。

    注意,getDeclaredAnnotationsByType方法获取到的注解不包括父类,其实当 getAnnotationsByType()方法调用时,其内部先执行了getDeclaredAnnotationsByType方法,只有当前类不存在指定注解时,getAnnotationsByType()才会继续从其父类寻找,但请注意如果@FilterPath和@FilterPaths没有使用了@Inherited的话,仍然无法获取。

    5. 注解元素及其数据值:

    在自定义注解中,一般都会包含一些元素以表示某些值,以便后续处理注解时使用。注解中的每个元素定义类似于接口中的方法定义,每个元素定义包含一个数据类型和名称。

    @interface MyAnnotation {
        String name();
        int age();
    }
     
    // 使用注解,并给注解元素赋值
    @MyAnnotation(name = "whx", age = 18)
    public class AnnotationTest {
    }
    

    注解中的元素可以设置默认值,通过default 来实现

    @interface MyAnnotation {
        String name() default "whx";
        int age() default 18;
    }
    

    当一个元素被设置默认值之后,这个元素便成了注解的可选元素,即在使用注解时如果不为这个元素赋值,将使用默认值。

    编译器对元素的默认值有一些限制,首先,元素不能有不确定的值,也就是说,元素必须要么具有默认值,要么在使用注解时提供元素的值。其次,对于非基本类型的元素,无论是在源代码中声明,还是在注解接口中定义默认值,都不能以null作为值,因为每个注解的声明中,所有的元素都存在,并且都具有相应的值。

    注解元素的数据类型:

    • 所有基本类型(int,float,boolean,byte,double,char,long,short)
    • String
    • Class
    • enum
    • Annotation
    • 上述类型的数组

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

    @interface MyAnnotation {
        enum Gender {MALE, FEMALE}
        
        // 枚举类型
        Gender status() default Gender.MALE;
    
        // 常用类型
        String name() default "whx";
        int age() default 18;
    
        // Class 类型
        Class<?> testCase() default Void.class;
    
        // 注解嵌套
        Reference reference() default @Reference(next = true);
        
        // 数组类型
        long[] value() default {1, 2, 3};
    }
     
    @Target(ElementType.TYPE)
    @Retention(RetentionPolicy.RUNTIME)
    @interface Reference {
        boolean next() default false;
    }
    

    快捷方式:

    就是注解中定义了名为value 的元素,并且在使用该注解时,如果该元素是唯一需要赋值的一个元素,那么此时无需使用key=value的语法,而只需在括号内给出value元素所需的值即可。这可以应用于任何合法类型的元素,但是,这限制了元素名必须为value 。

    @interface QuickWay {
        String value();
    }
    
    @QuickWay("hello world")
    class QuickTest { 
    }
    
    6. 常用Java 内置注解:

    主要有三个:

    • @Override:用于标明此方法覆盖了父类的方法, 当我们在子类中覆盖父类的方法时,就要用到@Override注解,这样,如果父类中的方法名称或参数发生改变时,如果子类没有做相应的调整编译器便会报错,这就是@Override注解所起到的作用。

    • @Deprecated:用于标明已经过时的类、方法、属性、方法参数等,被标记为@Deprecated 的类、方法等在编程中不建议使用,因为后来版本的API 可能将这些过期的去掉。

    • @SuppressWarnnings:用于有选择的关闭编译器对类、方法、成员变量、变量初始化的警告。

    三. 注解与反射机制

    上面对注解做了一个详细介绍,具体该如何使用我们的自定义注解呢?其实在现实应用中,我们的自定义注解一般都是起到运行时指示的作用,也就是运行时注解。对于运行时注解,我们可以通过反射机制获得注解信息。

    Java在java.lang.reflect 反射包下新增了AnnotatedElement接口,它主要用于表示目前正在 VM 中运行的程序中已使用注解的元素,通过该接口提供的方法可以利用反射技术地读取注解的信息,如反射包的Constructor类、Field类、Method类、Package类和Class类都实现了AnnotatedElement接口。

    下面是AnnotatedElement中相关的API方法:

    返回值 方法名称 说明
    <A extends Annotation> getAnnotation(Class<A> annotationClass) 该元素如果存在指定类型的注解,则返回这些注解,否则返回 null。
    Annotation[] getAnnotations() 返回此元素上存在的所有注解,包括从父类继承的
    boolean isAnnotationPresent(Class<? extends Annotation> annotationClass) 如果指定类型的注解存在于此元素上,则返回 true,否则返回 false。
    Annotation[] getDeclaredAnnotations() 返回直接存在于此元素上的所有注解,注意,不包括父类的注解,调用者可以随意修改返回的数组;这不会对其他调用者返回的数组产生任何影响,没有则返回长度为0的数组

    下面来看几个示例:

    class ATest {
        public static void main(String[] args) {
    
            Class<?> clazz = ChildClass.class;
    
            // 判断类上是否有指定的注解
            boolean b = clazz.isAnnotationPresent(CommonAnno.class);
    
            // 根据指定注解类型获取该注解
            ClassAnno classAnno = clazz.getAnnotation(ClassAnno.class);
            System.out.println(classAnno.name());
    
            // 获取该类上的所有注解,包括从父类继承
            Annotation[] anos = clazz.getAnnotations();
            System.out.println(Arrays.toString(anos));
    
            // 获取该类上的所有注解,不包括从父类继承
            Annotation[] anos2 = clazz.getDeclaredAnnotations();
            System.out.println(Arrays.toString(anos2));
     
            try {
                // 获取方法上的注解
                Method method = clazz.getMethod("add", int.class, int.class);
                Annotation[] ans = method.getAnnotations();
                System.out.println(Arrays.toString(ans));
     
                // 获取方法参数的注解
                Annotation[][] paramAnnos = method.getParameterAnnotations();
                Class[] paramTypes = method.getParameterTypes();
    
                int i = 0;
    
                for (Annotation[] annotations : paramAnnos) {
                    Class paramType = paramTypes[i++];
                    for (Annotation anno : annotations) {
                        if (anno instanceof ParamAnno) {
                            ParamAnno paramAnno = (ParamAnno) anno;
    
                            System.out.println(paramType.getName());
                            System.out.println(paramAnno.name());
                        }
                    }
                }
     
                // 获取属性的注解
                Field field = clazz.getField("dep");
                FiledAnno anno = field.getAnnotation(FiledAnno.class);
            } catch (NoSuchMethodException e) {
                e.printStackTrace();
            }       
        }
    }
    

    上面只是一些简单的示例,可以通过结合注解和反射实现一些复杂的功能,比如Spring 框架的基于注解的配置等。

    相关文章

      网友评论

        本文标题:Java 注解

        本文链接:https://www.haomeiwen.com/subject/odgadctx.html