注解入门介绍

237 阅读7分钟

注解入门介绍

1.概述

Annotation中文翻译过来就是注解、解释的意思。我们常用的@Resource @PostMapping等都是框架自定义的注解 ,注解一般拥有自己的属性,我们在使用注解的时候可以给注解传参,然后通过反射获取到注解,进而再拿到我们传入的值。所以注解一般会和反射搭配使用。

注解和class interface一样,也是一种类型,它是在 Java SE 5.0 版本中开始引入的概念。

我们通过@interface来自定义注解,如下就是注解的定义方式。一定注意需要在interface前加上@,否则它就是接口而不是注解。

import java.lang.annotation.*;
​
/**
 * ORM映射属性注解,将Java属性映射成表结构
 * @author Pymjl
 * @version 1.0
 * @date 2022/4/26 19:34
 **/
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.FIELD})
@Inherited
public @interface FieldsAnnotation {
    String value() default "";
​
    String type() default "varchar";
​
    int length() default -1;
​
}
​

这就是一个常见的注解定义,至于其他的参数有些什么用下面我们会详细讲到

2.元注解

元注解是放在注解上的注解,可能有点绕,你可以理解为元注解是对你自定义的注解进行解释和功能上的限定的.

元注解总共有五个:@Retention @Documented @Target @Inherited @Repeatable

@Retention

Retention 翻译过来就是保留期的意思。当 @Retention 应用到一个注解上的时候,它解释说明了这个注解的的存活时间。我们定义使用它的时候需要传入一个枚举参数RetentionPolicy,jdk给我们自定义了三种策略SOURCE CLASS RUNTIME

package java.lang.annotation;
​
/**
 * Annotation retention policy.  The constants of this enumerated type
 * describe the various policies for retaining annotations.  They are used
 * in conjunction with the {@link Retention} meta-annotation type to specify
 * how long annotations are to be retained.
 *
 * @author  Joshua Bloch
 * @since 1.5
 */
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,
​
    /**
     * 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.
     *
     * @see java.lang.reflect.AnnotatedElement
     */
    RUNTIME
}
​

通过源码我们知道:

  • SOURCE只在源码阶段保留,在编译器进行编译时它将被丢弃忽视
  • CLASS只被保留到编译进行的时候,它并不会被加载到 JVM 中
  • RUNTIME注解可以保留到程序运行的时候,它会被加载进入到 JVM 中,所以在程序运行时可以获取到它们

我们自定义注解时通常只会使用RUNTIME,因为我们后面还需要反射去获取到注解,进而获取里面的值

@Documented

它的作用是能将注解的属性加入到Javadoc中

@Target

这个注解是限定我们自定义的注解将被用于哪个地方,我们在使用的时候同样需要传入一个枚举参数ElementType

我们先通过看源码了解这个枚举类有哪些参数,分别有什么用

package java.lang.annotation;
​
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
}

通过源码我们知道:

  • TYPE可以给一个类型进行注解,比如类,接口,枚举
  • FIELD可以给一个字段属性(包括枚举常量)注解
  • METHOD可以给方法进行注解
  • PARAMETER 可以给一个方法内的参数进行注解
  • CONSTRUCTOR可以给构造方法进行注解
  • LOCAL_VARIABLE可以给局部变量进行注解
  • ANNOTATION_TYPE可以给一个注解进行注解
  • PACKAGE可以给一个包进行注解
  • TYPE_PARAMETER可以给一个类型参数进行注解(1.8新加入)
  • TYPE_USE类型使用声明(1.8新加入)

另外,我们使用@Target时可以传入多个枚举,比如:

@Target({ElementType.TYPE, ElementType.FIELD})

我们只需要加一个大括号将其括起来就行了

@Inherited

它表示使用了@Inherited注解的类,在被子类继承时,子类也会继承该注解。比如:

注解 Test 被 @Inherited 修饰,之后类 A 被 Test 注解,类 B 继承 A,类 B 也拥有 Test 这个注解

@Repeatable

@Repeatable 是 Java 1.8 才加进来的,所以算是一个新的特性。

@Repeatable是给注解指定一个容器,让其他的注解来存放它,举个例子

这是一个动物注解,拥有属性值type

package cuit.epoch.pymjl.annotations;
​
import java.lang.annotation.Repeatable;
​
/**
 * @author Pymjl
 * @version 1.0
 * @date 2022/4/30 15:09
 **/
@Repeatable(Animals.class)
public @interface Animal {
    String type() default "";
}

这是指定的容器注解,用来存放Animal注解里面的值,容器注解必须要有一个为Animal数组的属性值

package cuit.epoch.pymjl.annotations;
​
/**
 * 这是一个容器注解,用来存放其他的注解,
 * 默认需要一个与要存放的注解类型匹配的数组value属性值
 *
 * @author Pymjl
 * @version 1.0
 * @date 2022/4/30 15:08
 **/
public @interface Animals {
    Animal[] value();
}

接下来我们来定义一个动物园类,用注解来声明有哪些动物

package cuit.epoch.pymjl.annotations;
​
/**
 * @author Pymjl
 * @version 1.0
 * @date 2022/4/30 15:12
 **/
@Animal(type = "cat")
@Animal(type = "dog")
@Animal(type = "elephant")
public class Zoo {
}

3.注解属性

public @interface FieldsAnnotation {
    String value() default "";
​
    String type() default "varchar";
​
    int length() default -1;
​
}

如上,那是注解的属性,而不是方法。我们可以使用default为其指定一个默认的值,如果不使用default我们在使用注解的时候必须给他传入参数,否则会报错

需要注意的是,在注解中定义属性时它的类型必须是 8 种基本数据类型外加 类、接口、注解及它们的数组。

4.Java自带的注解

Java其实也自带了一些注解,下面我们就来介绍一下一些常用的注解

@Deprecated

这个元素是用来标记过时的元素,想必大家在日常开发中经常碰到。编译器在编译阶段遇到这个注解时会发出提醒警告,告诉开发者正在调用一个过时的元素比如过时的方法、过时的类、过时的成员变量

@Override

这个大家应该很熟悉了,提示子类要复写父类中被 @Override 修饰的方法

@SuppressWarnings

阻止警告的意思。之前说过调用被 @Deprecated 注解的方法后,编译器会警告提醒,而有时候开发者会忽略这种警告,他们可以在调用的地方通过 @SuppressWarnings 达到目的

我们可以选择的为其传入参数,比如:

@SuppressWarnings("unchecked")
@SuppressWarnings("all")

@FunctionalInterface

函数式接口注解,这个是 Java 1.8 版本引入的新特性。函数式编程很火,所以 Java 8 也及时添加了这个特性。

5.反射

上面我们只是介绍了如何定义注解,却没有介绍如何使用介绍。我相信很多小伙伴看到现在都有一个疑惑,我们使用注解的时候向其传入了参数,那剩下的额外的功能是怎么实现的呢,我们又是如何获取到这些传入参数的呢。这就需要用到另外一个知识点了,反射。反射相关的知识点如果还不是很熟悉建议再去看看其他人的介绍,我这里就不做赘述

下面我写一个简单的获取注解参数的Demo:

package cuit.epoch.pymjl.annotations;

import lombok.extern.log4j.Log4j2;

import java.lang.annotation.Annotation;
import java.lang.reflect.Field;

/**
 * 通过反射获取注解
 *
 * @author Pymjl
 * @version 1.0
 * @date 2022/4/29 23:49
 **/
@Log4j2
@SuppressWarnings("all")
public class AnnotationsTest {
    public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException {
        Class<?> clazz = Class.forName("cuit.epoch.pymjl.annotations.entity.Student");
        //通过反射获取注解,这是只能获取类上面的注解
        log.info("获取类的注解");
        Annotation[] annotations = clazz.getAnnotations();
        for (Annotation annotation : annotations) {
            System.out.println(annotation);
        }
        //获取注解的属性值
        log.info("获取类的属性值");
        TableAnnotation tableAnnotation = clazz.getAnnotation(TableAnnotation.class);
        System.out.println("value="+tableAnnotation.value());
        //获取属性的注解
        //先获取对应的属性
        Field name = clazz.getDeclaredField("name");
        //再获取属性上的注解
        FieldsAnnotation nameAnnotation = name.getAnnotation(FieldsAnnotation.class);
        System.out.println(nameAnnotation.value());
        System.out.println(nameAnnotation.length());
        System.out.println(nameAnnotation.type());
    }
}

我们可以通过反射获取到注解,进而再获取注解里面的属性,然后我们就可以做一些其他的额外的操作

本篇文章的完整代码样例可以通过以下链接获取:gitee