Back

注解

注解

注解简述

  • 为什么要引入注解

    在使用注解之前,XML被广泛应用于描述元数据,得到各大框架的青睐,它以松耦合的方式完成了框架中几乎所有的配置。但是随着项目越来越庞大,XML的内容也越来越复杂,一些开发人员和架构师发现维护成本变高。他们希望使用一些和代码紧耦合的东西来解决这个问题。于是就有人提出了一种标记式高耦合的配置方式——注解。方法上可以进行注解,类上也可以注解,字段属性上也可以注解,反正几乎需要配置的地方都可以进行注解。

    下面我们通过一个例子来理解这两者的区别。

    假如你想为应用设置很多的常量或参数,这种情况下,**【XML】是一个很好的选择,因为它不会同特定的代码耦合。如果你想把某个方法声明为服务,那么使用【注解】会更好一些,因为这种情况下需要注解和方法紧密耦合起来,**开发人员也必须认识到这点。

    同时,【注解】定义了一种标准的描述元数据的方式。

    关于【注解】和【XML】两种不同的配置模式,争论了好多年,各有各的优劣,注解可以提供更大的便捷性,易于维护修改,但耦合度高,而 【XML】 相对于注解则是相反的。追求低耦合就要抛弃高效率,追求效率必然会遇到耦合。目前,许多框架将【XML】和【注解】两种方式结合使用,平衡两者之间的利弊。

  • 什么是注解

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

    在Annotation接口中有下面这句话来描述注解:Annotation 是所有注解继承的公共接口。

    The common interface extended by all annotation types.
    

    所以,注解的本质就是一个继承了Annotation接口的接口。

    一个注解准确意义上来说,只不过是一种特殊的注释而已,如果没有解析它的代码,它可能连注释都不如。

    而解析一个类或者方法的注解往往有两种形式,一种是编译期直接的扫描,一种是运行期反射。反射的事情我们先不讨论,而编译器的扫描指的是编译器在对 Java 代码编译成字节码的过程中会检测到某个类或者方法被一些注解修饰,这时它就会对于这些注解进行某些处理。

    @Override
    public String toString(){
        return "Hello Annotation";
    }
    

    在上述代码中,我重写了toSting()方法,并使用了@Override注解、但是,即使我不使用@Override注解标记代码,程序也能够正常执行。

    那么,该注解表示什么呢?这样写有什么好处吗?事实上,@Override告诉编译器这个方法是一个重写方法(描述方法的元数据),如果父类中不存在该方法时,编译器会报错,提示该方法并没有重写父类的方法。

    如果我们不小心拼写错误,例如将toString()写成了toStrring(),而且我们也没有使用@Override注解**,此时程序依然能编译运行。但是运行结果会和期望的大不相同。**

    现在我们了解了什么是注解,并且使用注解有助于提高代码的可读性。

  • 注解的用途

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

    1. Java自带的标准注解,包括@Override(标明重写某个方法)、@Deprecated(标明某个类或方法过时)和@SuppressWarnings(标明要忽略的警告),使用这些注解后编译器就会进行检查
    2. 元注解,元注解是用于定义注解的注解,包括@Retention(标明注解被保留的阶段)、@Target(标明注解使用的范围)、@Inherited(标明注解可继承)、@Documented(标明是否生成javadoc文档)
    3. 自定义注解,可以根据自己的需求定义注解

元注解

元注解是用于修饰注解的注解

元注解有@Retention、@Documented、@Target、@Inherited、@Repeatable五种

  • @Retention

    Retention 的英文意为保留期的意思。当 @Retention 应用到一个注解上的时候,它解释说明了这个注解的的存活时间。

    它的取值如下:

    • RententionPolicy.SOURCE注解只在源码阶段保留,在编译期进行编译时它将被丢弃忽视。
    • RetentionPolicy.CLASS 注解只被保留到编译进行的时候,它并不会被加载到 JVM 中。如Java内置注解,@Override、@Deprecated、@SuppressWarnning等
    • RetentionPolicy.RUNTIME 注解可以**保留到程序运行的时候,它会被加载进入到 JVM 中,所以在程序运行时可以获取到它们。**如SpringMvc中的@Controller、@Autowired、@RequestMapping等。
  • @Documented

    这个元注解肯定是和文档有关。它的作用是能够将注解中的元素包含到 Javadoc 中去。

  • Target

    Target 是目标的意思,@Target 指定了注解运用的地方(即作用范围)。

    可以这样理解,当一个注解被 @Target 注解时,这个注解就被限定了运用的场景。

    类比到标签,原本标签是你想张贴到哪个地方就到哪个地方,但是因为 @Target 的存在,它张贴的地方就非常具体了,比如只能张贴到方法上、类上、方法参数上等等。@Target 有下面的取值

    • ElementType.ANNOTATION_TYPE 可以给一个注解进行注解
    • ElementType.CONSTRUCTOR 可以给构造方法进行注解
    • ElementType.FIELD 可以给属性进行注解
    • ElementType.LOCAL_VARIABLE 可以给局部变量进行注解
    • ElementType.METHOD 可以给方法进行注解
    • ElementType.PACKAGE 可以给一个包进行注解
    • ElementType.PARAMETER 可以给一个方法内的参数进行注解
    • ElementType.TYPE 可以给一个类型进行注解,比如类、接口、枚举
  • @Inherited

    Inherited 是继承的意思,但是它并不是说注解本身可以继承,而是说如果一个超类使用了@Inherited 注解,那么如果它的子类没有被任何注解应用的话,那么这个子类就继承了超类的注解。

  • @Repeatable

    Repeatable 自然是可重复的意思。@Repeatable 是 Java 1.8 才加进来的,所以算是一个新的特性。

    Repeatable使用场景:在需要对同一种注解多次使用时,往往需要借助@Repeatable。

下面举例说明一下,在生活中一个人往往是具有多种身份,如果我把每种身份当成一种注解该如何使用

  • 先声明一个Persons类用来包含所有的身份

    package com.annotation_;
    
    import java.lang.annotation.ElementType;
    import java.lang.annotation.Retention;
    import java.lang.annotation.RetentionPolicy;
    import java.lang.annotation.Target;
    
    /**
     * @Author: minster
     * @Date: 2021/10/12 21:13
     */
    @Target(ElementType.TYPE)
    @Retention(RetentionPolicy.RUNTIME)
    public @interface Persons {
        Person[] value();
    }
    

    这里@Target是声明Persons注解的作用范围,参数ElementType.Type代表可以给一个类进行注解

    @Retention是注解的有效时间,RetentionPolicy.RUNTIME是指程序运行的时候。

  • 然后再声明Person注解,表示一个身份

    package com.annotation_;
    
    import java.lang.annotation.Repeatable;
    
    /**
     * @Author: minster
     * @Date: 2021/10/12 21:14
     */
    @Repeatable(Persons.class)
    public @interface Person {
        String role() default "";
    }
    

    @Repeatable括号内的就相当于用来保存该注解内容的容器。

  • 然后声明一个Man类,给该类赋予一些身份

    package com.annotation_;
    
    import java.lang.annotation.*;
    
    /**
     * @Author: minster
     * @Date: 2021/10/12 19:02
     */
    @Person(role="CEO")
    @Person(role="husband")
    @Person(role="father")
    @Person(role="son")
    public class Man {
        String name = "";
    }
    

    在这里,一个Man可以有多重身份,是CEO、丈夫、父亲、儿子等等。每一个身份我们都使用@Person注解来声明

  • 在主方法中访问注解

    package com.annotation_;
    
    import java.lang.annotation.Annotation;
    
    /**
     * @Author: minster
     * @Date: 2021/10/12 21:16
     */
    public class Test {
        public static void main(String[] args) {
            //获取所有注解
            Annotation[] annotations = Man.class.getAnnotations();
            //打印获取到的注解的数量,因为只声明了Person一个注解,所以为1
            System.out.println(annotations.length);
            Persons p1 = (Persons) annotations[0];
            for (Person person : p1.value()) {
                System.out.println(person.role());
            }
        }
    }
    

    image01

注解的特性与使用

  • 注解的属性

    注解的属性也叫做成员变量。注解只有成员变量,没有方法。注解的成员变量在注解的定义中以“无形参的方法”形式来声明,其方法名定义了该成员变量的名字,其返回值定义了该成员变量的类型。

    @Target(ElementType.TYPE)
    @Retention(RetentionPolicy.RUNTIME)
    public @interface TestAnnotation{
        int id();
        String msg();
    }
    

    上面代码定义了 @TestAnnotation 这个注解中拥有 id 和 msg 两个属性。在使用的时候,我们应该给它们进行赋值。

    赋值的方式是在注解的括号内以value=""形式,多个属性之间用,隔开。

    @TestAnnotation(id=3, msg="hello annotation")
    public class Test {
    }
    
  • 注解的快捷方式

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

    @Target(ElementType.FIELD)
    @Retention(RetentionPolicy.RUNTIME)
    @interface IntegerVaule{
       int value() default 0;
       String name() default "";
    }
    
    public class QuicklyWay {
    
       @IntegerVaule(20)
       public int age;
    
       @IntegerVaule(value = 10000, name = "MONEY")
       public int money;
    
    }
    
  • 注解不支持继承

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

声明注解

这里总共定义了4个注解来演示注解的声明

  1. 定义一个可以注解在Class,interface,enum上的注解

    @Target(ElementType.TYPE)
    @Retention(RetentionPolicy.RUNTIME)
    public @interface MyAnTargetType {
    
        /**
         * 定义注解的一个元素 并给定默认值
         * @return
         */
        String value() default "定义在类接口枚举类上的注解元素value的默认值";
    
    }
    
  2. 定义一个可以注解在METHOD上的注解

    @Target({ElementType.METHOD})
    @Retention(RetentionPolicy.RUNTIME)
    public @interface MyAnTargetMethod {
    
        /**
         * 定义注解的一个元素 并给定默认值
         * @return
         */
        String value() default "定义在方法上的注解元素value的默认值";
    
    }
    
  3. 定义一个可以注解在FIELD上的注解

    @Target({ElementType.FIELD})
    @Retention(RetentionPolicy.RUNTIME)
    public @interface MyAnTargetField {
    
        /**
         * 定义注解的一个元素 并给定默认值
         * @return
         */
        String value() default "定义在字段上的注解元素value的默认值";
    
    }
    
  4. 定义一个可以注解在PARAMETER上的注解

    @Target({ElementType.PARAMETER})
    @Retention(RetentionPolicy.RUNTIME)
    public @interface MyAnTargetParameter {
    
        /**
         * 定义注解的一个元素 并给定默认值
         * @return
         */
        String value() default "定义在参数上的注解元素value的默认值";
    
    }
    
Built with Hugo
Theme Stack designed by Jimmy