• Java_注解_01_注解(Annotation)详解


    一、注解的概念

    Annotation(注解)是插入代码中的元数据(元数据从metadata一词译来,就是“描述数据的数据”的意思),在JDK5.0及以后版本引入。它可以在编译期使用预编译工具进行处理, 也可以在运行期使用 Java 反射机制进行处理,用于创建文档,跟踪代码中的依赖性,甚至执行基本编译时检查。因为本质上,Annotion是一种特殊的接口,程序可以通过反射来获取指定程序元素的Annotion对象,然后通过Annotion对象来获取注解里面的元数据。

    二、注解的本质

    2.1 通过示例看清本质

    注解本质上是一种继承自接口`java.lang.annotation.Annotation`的特殊接口。

    一个自定义注解的示例:

    @Documented
    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.METHOD)
    public @interface PersonAnno {
    
        String name() default "";
        int age() default 0;
    }
    View Code

    本质上注解会被编译为继承了(Annotation接口)的接口,反编译上面的PersonAnno.class可以看到代码如下: 

    这里写图片描述

    2.2 注解源码

    java.lang.annotation.Annotation类源码如下:

    /**
     * The common interface extended by all annotation types.  Note that an
     * interface that manually extends this one does <i>not</i> define
     * an annotation type.  Also note that this interface does not itself
     * define an annotation type.
     *
     * @author  Josh Bloch
     * @since   1.5
     */
    /**
     * 首先声明英语不是很好,大致意思是:这是一个基础接口,所有的注解类型都继承与它。但是需要注意的是
     * (1)不需要手动指明一个注解类型是继承与它的(意思是自动继承)
     * (2)它本身不是注解类型
     */
    public interface Annotation {
        /**
         * 这三个方法就不用多说了吧!
         */
        boolean equals(Object obj);
       
        int hashCode();
    
        String toString();
    
        /**
         * Returns the annotation type of this annotation.
         */
        /**
         * 返回注解的class
         */
        Class<? extends Annotation> annotationType();
    }
    View Code

    2.3 注解本质的总结

    (1)注解实质上会被编译器编译为接口,并且继承java.lang.annotation.Annotation接口。

    (2)注解的成员变量会被编译器编译为同名的抽象方法。

    (3)根据Java的class文件规范,class文件中会在程序元素的属性位置记录注解信息。例 如,RuntimeVisibleAnnotations属性位置,记录修饰该类的注解有哪些;flags属性位置,记录该类是不是注解;在方法的 AnnotationDefault属性位置,记录注解的成员变量默认值是多少。 

    三、注解的作用

    Annotation的作用大致可分为三类:

    (1)编写文档:通过代码里标识的元数据生成文档;

    (2)代码分析:通过代码里标识的元数据对代码进行分析;

    (3)编译检查:通过代码里标识的元数据让编译器能实现基本的编译检查;

    综上所述可知,Annotation主要用于提升软件的质量和提高软件的生产效率。

    四、注解的分类

    4.1 注解分类

    4.1.1 根据成员个数分类

    (1)标记注解:没有定义成员的Annotation类型,自身代表某类信息,如:@Override

    (2)单成员注解:只定义了一个成员,比如@SuppressWarnings 定义了一个成员String[] value,使用value={…}大括号来声明数组值,一般也可以省略“value=”

    (3)多成员注解:定义了多个成员,使用时以name=value对分别提供数据

    4.1.2 根据注解使用的功能和用途分类

    (1)Java内置注解:Java自带的注解类型

                    @Override:用于修饰此方法覆盖了父类的方法;

                    @Deprecated:用于修饰已经过时的方法;

                    @SuppressWarnnings:用于通知java编译器禁止特定的编译警告;

    (2)元注解:注解的注解,负责注解其他注解

                   @Target:用于描述注解可以修饰的类型

                   @Retention:用于声明注解的生命周期,即注解在什么范围内有效。

                   @Documented:是一个标记注解,表明含有该注解类型的元素(带有注释的)会通过javadoc或类似工具进行文档化。

                   @Inherited:是一个标记注解,表示该注解类型能被自动继承。

                   @Repeatable :规定注解是否可以重复,重复型的注解还需要指明注解容器,用来存储可重复性注解,同样也是 Java 8 之后才支持

    (3)自定义注解:用户根据自己的需求自定义的注解类型

           使用@interface自定义注解,自动继承了java.lang.annotation.Annotation接口

    五、Java内置注解

    5.1 @Override(覆写) ——限定重写父类方法

    (1)源码:

    @Target(ElementType.METHOD)
    @Retention(RetentionPolicy.SOURCE)
    public @interface Override {
    }

    (2)分析:

    @Override 是一个标记注解,标注于方法上,仅保留在java源文件中。

    (3)用途:

    用于告知编译器,我们需要覆写超类的当前方法。如果某个方法带有该注解但并没有覆写超类相应的方法,则编译器会生成一条错误信息。

    5.2 @Deprecated(不赞成使用)——用于标记已过时方法

    (1)源码:

    @Documented
    @Retention(RetentionPolicy.RUNTIME)
    @Target(value={CONSTRUCTOR, FIELD, LOCAL_VARIABLE, METHOD, PACKAGE, PARAMETER, TYPE})
    public @interface Deprecated {
    }

    (2)分析:

    @Deprecated 是一个标记注解,可标注于除注解类型声明之外的所有元素,保留时长为运行时VM。

    (3)用途:

    用于告知编译器,某一程序元素(比如方法,成员变量)不建议使用时,应该使用这个注解。Java在javadoc中推荐使用该注解,一般应该提供为什么该方法不推荐使用以及相应替代方法。

    5.3 @SuppressWarnings(抑制警告)——抑制编译器警告

    (1)源码:

    @Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE})
    @Retention(RetentionPolicy.SOURCE)
    public @interface SuppressWarnings {
        String[] value();
    }

    (2)分析:

     @SuppressWarnings有一个类型为String[]的成员,不是标记注解,这个成员的值为被禁止的警告名,可标注于除注解类型声明和包名之外的所有元素,仅保留在java源文件中。

    (3)用途:

    用于告知编译器忽略特定的警告信息。

    (4)使用示例:

    public class SuppressWarningTest {
        @SuppressWarnings("unchecked")
        public void addItems2(String item){
            @SuppressWarnings("unused")
            List list = new ArrayList();
            List items = new ArrayList();
            items.add(item);
        }
    
        @SuppressWarnings({"unchecked","unused"})
        public void addItems1(String item){
            List list = new ArrayList();
            list.add(item);
        }
    
        @SuppressWarnings("all")
        public void addItems(String item){
            List list = new ArrayList();
            list.add(item);
        }
    }
    View Code

    (5)常见参数值

    该注解有方法value(),可支持多个字符串参数,例如:

    @SupressWarning(value={"uncheck","deprecation"})

    前面讲的@Override,@Deprecated都是无需参数的,而压制警告是需要带有参数的,可用参数如下:

    参数含义
    deprecation 使用了过时的类或方法时的警告
    unchecked 执行了未检查的转换时的警告
    fallthrough 当Switch程序块进入进入下一个case而没有Break时的警告
    path 在类路径、源文件路径等有不存在路径时的警告
    serial 当可序列化的类缺少serialVersionUID定义时的警告
    finally 任意finally子句不能正常完成时的警告
    all 以上所有情况的警告
    更多关键字  

    六、元注解

    元注解的作用就是负责注解其他注解

    6.1 @Target

    (1)源码:

    @Documented
    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.ANNOTATION_TYPE)
    public @interface Target {
        ElementType[] value();
    }

    (2)作用:

    用于描述注解可以修饰的程序元素。当注解类型声明中没有@Target元注解,则默认为可适用所有的程序元素。

    如果存在指定的@Target元注解,则编译器强制实施相应的使用限制。程序元素(ElementType)是枚举类型,共定义8种程序元素,如下表:

    ElementType含义
    ANNOTATION_TYPE 注解类型声明
    CONSTRUCTOR 构造方法声明
    FIELD 字段声明(包括枚举常量)
    LOCAL_VARIABLE 局部变量声明
    METHOD 方法声明
    PACKAGE 包声明
    PARAMETER 参数声明
    TYPE 类、接口(包括注解类型)或枚举声明

    例如,上面源码@Target的定义中有一行  @Target(ElementType.ANNOTATION_TYPE) ,意思是指当前注解的元素类型是注解类型。

    6.2 @Retention

    (1)源码:

    @Documented
    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.ANNOTATION_TYPE)
    public @interface Retention {
        RetentionPolicy value();
    }

    (2)作用:

    描述该注解的生命周期,表示在什么编译级别上保存该注解的信息。

    当注解类型声明中没有@Retention元注解,则默认保留策略为RetentionPolicy.CLASS。

    保留策略(RetentionPolicy)是枚举类型,共定义3种保留策略,如下表:

    RetentionPolicy含义
    SOURCE 仅存在Java源文件,经过编译器后便丢弃相应的注解
    CLASS 存在Java源文件,以及经编译器后生成的Class字节码文件,但在运行时VM不再保留注释
    RUNTIME 存在源文件、编译生成的Class字节码文件,以及保留在运行时VM中,可通过反射性地读取注解

    例如,上面源码@Retention的定义中有一行 @Retention(RetentionPolicy.RUNTIME),意思是指当前注解的保留策略为RUNTIME,即存在Java源文件,也存在经过编译器编译后的生成的Class字节码文件,同时在运行时虚拟机(VM)中也保留该注解,可通过反射机制获取当前注解内容。

    6.3 @Documented

    (1)源码:

    @Documented
    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.ANNOTATION_TYPE)
    public @interface Documented {
    }

    (2)作用:

    是一个标记注解,表示拥有该注解的元素可通过javadoc此类的工具进行文档化,即在生成javadoc文档的时候将该Annotation也写入到文档中。

    例如,上面源码@Retention的定义中有一行 @Documented,意思是指当前注解的元素会被javadoc工具进行文档化,那么在查看Java API文档时可查看当该注解元素。

    6.4 @Inherited

    (1)源码:

    @Documented
    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.ANNOTATION_TYPE)
    public @interface Inherited {
    }

    (2)作用:

    是一个标记注解,表示该注解类型被自动继承。如果一个使用了@Inherited修饰的annotation类型被用于一个class,则这个annotation将被用于该class的子类。

    当@Inherited annotation类型标注的annotation的Retention是RetentionPolicy.RUNTIME,则反射API增强了这种继承性。如果我们使用java.lang.reflect去查询一个@Inherited annotation类型的annotation时,反射代码检查将展开工作:检查class和其父类,直到发现指定的annotation类型被发现,或者到达类继承结构的顶层。

    七、自定义注解

    7.1 自定义注解的规则

    自定义注解示例:

    /**
     *自定义注解MyAnnotation
     */
    @Target(ElementType.TYPE) //目标对象是类型
    @Retention(RetentionPolicy.RUNTIME) //保存至运行时
    @Documented //生成javadoc文档时,该注解内容一起生成文档
    @Inherited //该注解被子类继承
    public @interface MyAnnotation {
        public String value() default ""; //当只有一个元素时,建议元素名定义为value(),这样使用时赋值可以省略"value="
        String name() default "devin"; //String
        int age() default 18; //int
        boolean isStudent() default true; //boolean
        String[] alias(); //数组
        enum Color {GREEN, BLUE, RED,} //枚举类型
        Color favoriteColor() default Color.GREEN; //枚举值
    }
    View Code

    自定义注解规则:

    (1)定义注解:使用@interface来声明一个注解,同时将自动继承java.lang.annotation.Annotation接口,由编译程序自动完成其他细节。在定义注解时,不能继承其他的注解或接口。

    (2)配置注解参数(key):注解的每一个方法实际上是声明了一个配置参数。注解方法不带参数,方法的名称就是参数的名称,返回值类型就是参数的类型(返回值类型只能是基本类型、Class、String、enum)。

    (3)注解参数的默认值(value):可以通过default来声明参数的默认值。

    (4)注解参数的可支持数据类型:基本类型、String、Enums、Annotation以及前面这些类型的数组类型。

    (5)注解参数的访问权限:只能用public或默认(default)这两个访问权修饰。

    (6)如果只有一个参数成员,建议参数名称设为value()。

    (7)注解元素必须有确定的值,要么在定义注解的默认值中指定,要么在使用注解时指定,非基本类型的注解元素的值不可为null。因此, 使用空字符串或负数作为默认值是一种常用的做法。

    7.2 使用自定义注解

    @MyAnnotation(
            value = "info",
            name = "myname",
            age = 99,
            isStudent = false,
            alias = {"name1", "name2"},
            favoriteColor = MyAnnotation.Color.RED
    )
    public class MyClass {
        //使用MyAnnotation注解,该类生成的javadoc文档包含注解信息如下:
        /*
        @MyAnnotation(value = "info", name = "myname", age = 99, isStudent = false, alias = {"name1","name2"}, favoriteColor = Color.RED)
        public class MyClass
        extends Object
         */
    }
    
    
    public class MySubClass extends MyClass{
        //子类MySubClass继承了父类MyClass的注解
    }
    七、解
    View Code

    八、 解析注解信息

    8.1 AnnotatedElement 接口

    Java使用Annotation接口来代表程序元素前面的注解,该接口是所有Annotation类型的父接口。

    通过反射技术来解析自定义注解,关于反射类位于包java.lang.reflect,其中有一个接口 AnnotatedElement,该接口代表程序中可以接受注解的程序元素。

    AnnotatedElement接口是所有程序元素(Field、Method、Package、Class和Constructor)的父接口,所以程序通过反射获取了某个类的AnnotatedElement对象之后,程序就可以调用该对象的如下七个方法来访问Annotation信息:

    返回值方法解释
    T getAnnotation(Class annotationClass) 当存在该元素的指定类型注解,则返回相应注释,否则返回null
    Annotation[] getAnnotations() 返回此元素上存在的所有注解
    Annotation[] getDeclaredAnnotation(Class) 返回该程序元素上存在的、指定类型的注解,如果该类型注解不存在,则返回null;与此接口中的其他方法不同,该方法将忽略继承的注解;
    Annotation[] getDeclaredAnnotations() 返回直接存在于此元素上的所有注释。与此接口中的其他方法不同,该方法将忽略继承的注解;
    Annotation[] getAnnotationsByType(Class) 返回直接存在于此元素上指定注解类型的所有注解;
    boolean  isAnnotationPresent (Class<?extends Annotation> annotationClass) 判断该程序元素上是否包含指定类型的注解,存在则返回true,否则返回false;

    8.1.2 isAnnotationPresent(Class<?extends Annotation>)源码

     public boolean isAnnotationPresent(
            Class<? extends Annotation> annotationClass) {
            if (annotationClass == null)
                throw new NullPointerException();
    
            return getAnnotation(annotationClass) != null;
        }
    View Code

    其实是调用了 getAnnotation(Class )

    8.1.3 getAnnotation(Class )源码

    public <A extends Annotation> A getAnnotation(Class<A> annotationClass) {
            if (annotationClass == null)
                throw new NullPointerException();
    
            initAnnotationsIfNecessary();//初始化
            return (A) annotations.get(annotationClass);
        }
    View Code

    initAnnotationsIfNecessary()  源码

    //看了下这个方法。基本上就是扫描后放入到annotations中,完成初始话!
     private synchronized void initAnnotationsIfNecessary() {
            clearCachesOnClassRedefinition();//看了下这个方法,好像是清理缓存用的。
            if (annotations != null)
                return;
            declaredAnnotations = AnnotationParser.parseAnnotations(
                getRawAnnotations(), getConstantPool(), this);//这个方法没法找到啊
            Class<?> superClass = getSuperclass();
            if (superClass == null) {
                annotations = declaredAnnotations;
            } else {
                annotations = new HashMap<Class, Annotation>();
                superClass.initAnnotationsIfNecessary();
                for (Map.Entry<Class, Annotation> e : superClass.annotations.entrySet()) {
                    Class annotationClass = e.getKey();
                    if (AnnotationType.getInstance(annotationClass).isInherited())
                        annotations.put(annotationClass, e.getValue());
                }
                annotations.putAll(declaredAnnotations);
            }
        }
    View Code

    8.2 解析注解示例

    (1)FruitName注解

    package com.ray.annotation;
    
    import java.lang.annotation.Documented;
    import java.lang.annotation.Retention;
    import java.lang.annotation.Target;
    import java.lang.annotation.RetentionPolicy;
    import java.lang.annotation.ElementType;
    /**
     * 水果名称注解
     */
    @Target(ElementType.FIELD)
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    public @interface FruitName {
        String value() default " ";
    }
    View Code

    (2)FruitColor注解

    package com.ray.annotation;
    
    import java.lang.annotation.Documented;
    import java.lang.annotation.Retention;
    import java.lang.annotation.Target;
    import java.lang.annotation.RetentionPolicy;
    import java.lang.annotation.ElementType;
    /**
     * 水果颜色注解
     */
    @Target(ElementType.FIELD)
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    public @interface FruitColor {
        /**
         * 颜色枚举
         */
        public enum Color{BLUE, RED, GREEN};
    
        /**
         * 颜色属性
         * @return
         */
        Color fruitColor() default Color.GREEN;
    }
    View Code

    (3)FruitProvider注解

    package com.ray.annotation;
    
    import java.lang.annotation.Documented;
    import java.lang.annotation.Retention;
    import java.lang.annotation.Target;
    import java.lang.annotation.RetentionPolicy;
    import java.lang.annotation.ElementType;
    /**
     * 水果供应商注解
     */
    @Target(ElementType.FIELD)
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    public @interface FruitProvider {
        /**
         * 供应商编号
         * @return
         */
        public int id() default -1;
    
        /**
         * 供应商名称
         * @return
         */
        public String name() default " ";
    
        /**
         * 供应商地址
         * @return
         */
        public String address() default " ";
    }
    View Code

    (4)实体类——Apple类

    package com.ray.annotation;
    
    /***********注解使用***************/
    public class Apple {
        @FruitName("Apple")
        private String appleName;
        
        @FruitColor(fruitColor = FruitColor.Color.RED)
        private String appleColor;
        
        @FruitProvider(id = 1, name = "陕西红富士集团", address = "陕西红富士大厦")
        private String appleProvider;
    
        public String getAppleProvider() {
            return appleProvider;
        }
    
        public void setAppleProvider(String appleProvider) {
            this.appleProvider = appleProvider;
        }
    
        public String getAppleName() {
            return appleName;
        }
    
        public void setAppleName(String appleName) {
            this.appleName = appleName;
        }
    
        public String getAppleColor() {
            return appleColor;
        }
    
        public void setAppleColor(String appleColor) {
            this.appleColor = appleColor;
        }
    
        public void displayName(){
            System.out.println(getAppleName());
        }
    }
    View Code

    (5)注解解析类——AnnotationParser类

    package com.ray.annotation;
    
    import java.lang.reflect.Field;
    
    public class AnnotationParser {
        public static void main(String[] args) throws SecurityException, ClassNotFoundException {
            //1.获取苹果类的属性
            String clazz = "com.ray.annotation.Apple";
            //Field[] fields = AnnotationParser.class.getClassLoader().loadClass(clazz).getDeclaredFields();
            Field[] fields = Class.forName(clazz).getDeclaredFields();
            
            //2.解析注解信息
            for (Field field : fields) {
                //System.out.println(field.getName().toString());
                //2.1当field上标注了FruitName注解时
                if (field.isAnnotationPresent(FruitName.class)){
                    FruitName fruitName = field.getAnnotation(FruitName.class);
                    System.out.println("水果的名称:" + fruitName.value());
    
                    //2.2当field上标注了FruitColor注解时
                }else if (field.isAnnotationPresent(FruitColor.class)){
                    FruitColor fruitColor = field.getAnnotation(FruitColor.class);
                    System.out.println("水果的颜色:"+fruitColor.fruitColor());
                }else if (field.isAnnotationPresent(FruitProvider.class)){
                    FruitProvider fruitProvider = field.getAnnotation(FruitProvider.class);
                    System.out.println("水果供应商编号:" + fruitProvider.id() + " 名称:" + fruitProvider.name() + " 地址:" + fruitProvider.address());
                }
            }
        }
    
    }
    View Code

    参考文章:

    1.java注解解析

    2.Java 注解深入理解

    4.Java注解(Annotation)

    5.Java annotation源码解读

    6.聊聊 Java 注解(上)

    7.Java Annotation认知(包括框架图、详细介绍、示例说明)

  • 相关阅读:
    java.net.SocketException: Unconnected sockets not implemented 解
    ios 瀑布流
    IOS --- 日期时间格式 更改
    平衡二叉树(常问问题)
    Oracle 学习笔记 17 -- 异常处理(PL/SQL)
    【Java先进】Lock、通过使用线程池
    兼容 谷歌、火狐、360系列浏览器桌面通知()有用
    iOS截取特定的字符串(正则匹配)
    改造世界、知行合一、实践论、学以致用
    如何理解“哲学家们只是用不同的方式解释世界,而问题在于改变世界”?
  • 原文地址:https://www.cnblogs.com/shirui/p/7562061.html
Copyright © 2020-2023  润新知