zoukankan      html  css  js  c++  java
  • java注解和自定义注解的简单使用

    前言

    在使用Spring Boot的时候,大量使用注解的语法去替代XML配置文件,十分好用。

    然而,在使用注解的时候只知道使用,却不知道原理。直到需要用到自定义注解的时候,才发现对注解原理一无所知,所以要学习一下。

    特别注意的是,注解是Java提供的语法,而不是Spring特殊的语法。Java自5.0版本开始引入注解之后,注解就成为了Java平台中非常重要的一部分。

    什么是注解(Annotation)

    注解(Annotation)是一种应用于类、方法、参数、变量、构造器及包声明中的特殊修饰符。它是一种由JSR-175标准选择用来描述元数据的工具。

    用一个词来描述注解,那就是元数据(描述数据的数据)。 那么就可以说,注解是源代码的元数据。

    @Override
    public String toString() {
        return "良辰美景奈何天";
    }

    在上面的代码中,我重写了toString()方法并在方法上使用了@Override注解。但是,即使我不使用@Override注解标记代码,程序也能正常执行。那么为什么还要使用注解呢?事实上,@Override注解会告诉编译器这个方法是一个重写的方法(描述方法的元数据),如果父类中不存在该方法,编译器便会报错,提示该方法没有重写父类中的方法。但是如果我不小心拼写错误,例如将toString()写成了toSpring(),而且我也没有使用@Override注解,那么程序依然能够编译运行,虽然运行结果会和我期望的大不相同。到这里我们就可以明白什么是注解,还有使用注解既可以帮助编译器检查代码,也可以帮助我们阅读程序。

    注解的用途

    注解仅仅是元数据,和业务逻辑无关。但是元数据的用户可以通过读取这些元数据并实现必要的逻辑(第三方代码,注解不能干扰源代码的编译运行),以此来附加相应的业务操作。

    生成文档。比如我们用的IDE里面会自动加上的@param,@return,@author等注解。

    编译时检查格式。比如@Override,@SuppressWarnings等注解。

    跟踪代码依赖性,实现替代配置文件功能。比如@Configuration,@Bean等注解。

    Java类库提供的元注解

    Java类库中提供了java.lang.annotation包,包中包含了元注解和接口,定义自定义注解所需要的所有内容都在这个包中。

    比如java.lang.annotation.Annotation是所有注解自动继承的接口,不需要定义时指定,类似于所有类都自动继承Object一样。

    @Documented,@Retention,@Target,@Inherited是Java提供的4个元注解。

    @Documented注解表示将此注解包含在javadoc中。

    @Documented // 生成文档
    @Retention(RetentionPolicy.RUNTIME) // 注解在运行期级别保留注解信息
    @Target(ElementType.ANNOTATION_TYPE) // 注解放置的目标位置,ANNOTATION_TYPE是可注解在注解定义上
    public @interface Documented {
    
    }

    使用此注解的类会被javadoc工具提取成文档。在doc文档中的内容会因为此注解的信息内容不同而不同,相当于@see,@param等。

    @Retention注解表示在什么级别保留该注解信息。

    @Documented // 生成文档
    @Retention(RetentionPolicy.RUNTIME) // 注解在运行期级别保留注解信息
    @Target(ElementType.ANNOTATION_TYPE) // 注解放置的目标位置,ANNOTATION_TYPE表示是可注解在注解定义上
    public @interface Retention {
        
        RetentionPolicy value(); // 在哪个阶段保留注解信息的级别,必须参数(无默认值)
    }

    可选的参数值在枚举类型RetentionPolicy中。

    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.
         * 注解在class文件中可用,但会被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
    }

    @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(); // 注解的目标位置,必须参数(无默认值)
    }

    可能的参数值在枚举类型ElementType中。

    public enum ElementType {
        /**
    * Class, interface (including annotation type), or enum declaration
    * 类,接口(包括注解类型)或enum声明
    */ TYPE, /**
    * Field declaration (includes enum constants)
    * 域声明(包括enum实例)
    */ 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 }

    @Inherited注解表示允许子类继承父类中的注解。

    @Documented // 生成文档
    @Retention(RetentionPolicy.RUNTIME) // 注解在运行期级别上保留注解信息
    @Target(ElementType.ANNOTATION_TYPE) // 注解放置的目标位置,ANNOTATION_TYPE是可注解在注解定义上
    public @interface Inherited {
    
    }

    自定义注解 

    使用@interface关键字来自定义注解时,自动继承java.lang.annotation.Annotation接口(隐式继承),由编译程序自动完成其它细节。在定义注解时,不能显式继承其它的注解或接口。@interface关键字用来声明一个注解,其中的每一个方法实际上是声明了一个配置参数。方法的名称就是参数的名称,返回值类型就是参数的类型(返回值类型只能是基本类型、Class、String、enum),可以通过default来声明参数的默认值。

    定义注解的通用格式:

    public @interface 注解名 {
        访问修饰符 返回值类型 参数名() default 默认值;
    }

    注解参数成员的可支持数据类型:

    1.所有基本数据类型(int,float,boolean,byte,double,char,long,short)

    2.String类型

    3.Class类型

    4.enum类型

    5.Annotation类型

    6.以上所有类型的数组

    注解参数的规范:

    1.只能用public或默认(default)这两个访问修饰符修饰。一般使用default,即不指定访问修饰符。

    2.参数成员只能用上面列出的、注解参数支持的数据类型。

    3.如果只有一个参数成员,最好把参数名称设为value。

    简单使用例子:

    首先定义注解。

    @Documented
    @Target({ElementType.METHOD, ElementType.TYPE}) // 注解放置的目标位置,这里可以添加在METHOD上,也可以添加在TYPE上
    @Retention(RetentionPolicy.RUNTIME) // 注解在运行期保留注解信息,在运行期可以通过反射获取注解信息
    public @interface MyAnnotation {
    
        String name() default ""; // 姓名,默认值空字符串
    
        String sex() default "男"; // 性别,默认值男
    }

    将注解使用在类上。

    @MyAnnotation(name = "谢小猫")
    public class MyAnnotationOnClass {
    
    }

    将注解使用在方法上。

    public class MyAnnotationOnMethod {
    
        @MyAnnotation(name = "赵小虾")
        public void zhaoShrimp() {
    
        }
    
        @MyAnnotation(name = "李小猪", sex = "女")
        public void liPig() {
    
        }
    }

    最后通过反射获取注解信息并打印出来。

    public class MyAnnotationTest {
    
        public static void main(String[] args) {
            // 调用Class的isAnnotationPresent方法,检查类MyAnnotationUse是否含有@AnnotationTest注解
            if (MyAnnotationOnClass.class.isAnnotationPresent(MyAnnotation.class)) {
                // 若存在就获取注解
                MyAnnotation myAnnotation = MyAnnotationOnClass.class.getAnnotation(MyAnnotation.class);
                System.out.println("完整注解:" + myAnnotation);
                // 获取注解属性
                System.out.println("性别:" + myAnnotation.sex());
                System.out.println("姓名:" + myAnnotation.name());
                System.out.println("---------------------------------------------------------");
            }
    
            // 调用Class的getDeclaredMethods方法,获取MyAnnotationUse的所有方法声明
            Method[] methods = MyAnnotationOnMethod.class.getDeclaredMethods();
            for (Method method : methods) {
                System.out.println("方法声明:" + method);
                // 调用Method的isAnnotationPresent方法,检查方法是否含有@AnnotationTest注解
                if (method.isAnnotationPresent(MyAnnotation.class)) {
                    MyAnnotation myAnnotationInMethod = method.getAnnotation(MyAnnotation.class);
                    System.out.println("方法名:" + method.getName()
                            + ",姓名:" + myAnnotationInMethod.name()
                            + ",性别:" + myAnnotationInMethod.sex() + ")");
                }
            }
        }
    }

    查看结果。

    通过上面的简单使用例子,我们对注解的使用基本有些了解了:注解的使用与反射是离不开的。我们可以通过Java反射机制读取注解的信息,并根据这些信息更改目标程序的逻辑。

    这样,我们就可以利用代码中的注解,达到间接控制程序代码运行的目的。

    注解的总结

    1.Java中所有注解都隐式继承(自动继承)于java.lang.annotation.Annotation,注解不允许显式继承其他接口。

    2.注解不能直接干扰程序代码的运行,无论增加或删除注解,代码都能够正常运行。Java语言解释器会忽略这些注解,而由第三方工具负责对注解进行处理。

    3.一个注解可以拥有多个成员,成员声明和接口方法声明类似,成员声明有规范限制。

  • 相关阅读:
    前端进击的巨人(一):执行上下文与执行栈,变量对象
    读书笔记(06)
    前端博客收藏
    Nodejs-Can't set headers after they are sent
    Mac OS安装包管理工具Homebrew教程
    webpack自动化构建脚本指令npm run dev/build
    使用express搭建node中间件
    【转】基于localStorage的资源离线和更新技术
    web前端性能优化
    Vue生命周期详解
  • 原文地址:https://www.cnblogs.com/yanggb/p/10374500.html
Copyright © 2011-2022 走看看