IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> Java知识库 -> Java注解(Annotation)的基本原理以及实现自定义注解 -> 正文阅读

[Java知识库]Java注解(Annotation)的基本原理以及实现自定义注解

在我们使用springboot的时候我们知道因为注解的存在,使得我们的开发变得格外的方便、快捷。之前的文章Spring常用注解大全,值得你的收藏!!!对于spring中各类注解也进行过介绍。然而注解也并不是因为spring框架的兴起才出现的,而是很早就已经在java中被使用。
Java 注解(Annotation)又称 Java 标注,是从 Jdk1.5 开始被添加到 Java中 的。Java 中的类、方法、变量、参数和包等都可以被标注。和 Javadoc 不同,Java 标注可以通过反射获取标注内容。在编译器生成类文件时,标注可以被嵌入到字节码中。Java 虚拟机可以保留标注内容,在运行时可以获取到标注内容 。当然它也支持自定义 Java 标注。Java 注解是用于为 Java 代码提供元数据。作为元数据,注解不直接影响代码的执行,但也有一些类型的注解实际上可以用于这一目的。
学习注解可以更好地理解注解是怎么工作的,更加方便我们日常的开发,提高工作的效率。

一、常用注解

在java.lang包下存在着10个基本的Annotation,其中有3个注解是非常常见的,它们分别是:
@Override :检查该方法是否是重写方法。如果发现其父类,或者是引用的接口中并没有该方法时,会报编译错误。

@Deprecated :标记过时方法。如果使用该方法,会报编译警告。

@SuppressWarnings :指示编译器去忽略注解中声明的警告。

这三个注解是在java.lang中,可以作用下代码上。它们也是我们日常开发中也经常见到的,或许见过了就见过了,知道如何使用,但是注解是如何工作呢?在这之前我们需要先了解下元注解。
元注解可以理解为注解的注解,它是作用在注解中,方便我们使用注解实现想要的功能。元注解分别有@Retention、 @Target、 @Document、 @Inherited和@SafeVarargs(JDK1.7加入)、@FunctionalInterface(JDK1.8加入)、@Repeatable(JDK1.8加入)7种。它们是在java.lang.annotation 中,可以作用在其他注解上:

@Retention:标注这个注解怎么保存,是只在代码中,还是编入class文件中,或者是在运行时可以通过反射访问。比如:


/**注解Repeatable源码*/
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Repeatable {
    /**
     * Indicates the <em>containing annotation type</em> for the
     * repeatable annotation type.
     * @return the containing annotation type
     */
    Class<? extends Annotation> value();
}

@Documented:标记这些注解是否包含在用户文档中。

@Target:标记这个注解应该是哪种 Java 成员。

@Inherited:标记这个注解是继承于哪个注解类(默认 注解并没有继承于任何子类)

从 Java 7 开始,又添加了3 个注解:

@SafeVarargs - Java 7 开始支持,忽略任何使用参数为泛型变量的方法或构造函数调用产生的警告。

@FunctionalInterface - Java 8 开始支持,标识一个匿名函数或函数式接口。

@Repeatable - Java 8 开始支持,标识某注解可以在同一个声明上使用多次。

二、注解Annotation源码

Annotation接口源码:


package java.lang.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.
 *
 * More information about annotation types can be found in section 9.6 of
 * <cite>The Java&trade; Language Specification</cite>.
 *
 * The {@link java.lang.reflect.AnnotatedElement} interface discusses
 * compatibility concerns when evolving an annotation type from being
 * non-repeatable to being repeatable.
 *
 * @author  Josh Bloch
 * @since   1.5
 */
public interface Annotation {
    /**
     * Returns true if the specified object represents an annotation
     * that is logically equivalent to this one.  In other words,
     * returns true if the specified object is an instance of the same
     * annotation type as this instance, all of whose members are equal
     * to the corresponding member of this annotation, as defined below:
     * <ul>
     *    <li>Two corresponding primitive typed members whose values are
     *    <tt>x</tt> and <tt>y</tt> are considered equal if <tt>x == y</tt>,
     *    unless their type is <tt>float</tt> or <tt>double</tt>.
     *
     *    <li>Two corresponding <tt>float</tt> members whose values
     *    are <tt>x</tt> and <tt>y</tt> are considered equal if
     *    <tt>Float.valueOf(x).equals(Float.valueOf(y))</tt>.
     *    (Unlike the <tt>==</tt> operator, NaN is considered equal
     *    to itself, and <tt>0.0f</tt> unequal to <tt>-0.0f</tt>.)
     *
     *    <li>Two corresponding <tt>double</tt> members whose values
     *    are <tt>x</tt> and <tt>y</tt> are considered equal if
     *    <tt>Double.valueOf(x).equals(Double.valueOf(y))</tt>.
     *    (Unlike the <tt>==</tt> operator, NaN is considered equal
     *    to itself, and <tt>0.0</tt> unequal to <tt>-0.0</tt>.)
     *
     *    <li>Two corresponding <tt>String</tt>, <tt>Class</tt>, enum, or
     *    annotation typed members whose values are <tt>x</tt> and <tt>y</tt>
     *    are considered equal if <tt>x.equals(y)</tt>.  (Note that this
     *    definition is recursive for annotation typed members.)
     *
     *    <li>Two corresponding array typed members <tt>x</tt> and <tt>y</tt>
     *    are considered equal if <tt>Arrays.equals(x, y)</tt>, for the
     *    appropriate overloading of {@link java.util.Arrays#equals}.
     * </ul>
     *
     * @return true if the specified object represents an annotation
     *     that is logically equivalent to this one, otherwise false
     */
    boolean equals(Object obj);

    /**
     * Returns the hash code of this annotation, as defined below:
     *
     * <p>The hash code of an annotation is the sum of the hash codes
     * of its members (including those with default values), as defined
     * below:
     *
     * The hash code of an annotation member is (127 times the hash code
     * of the member-name as computed by {@link String#hashCode()}) XOR
     * the hash code of the member-value, as defined below:
     *
     * <p>The hash code of a member-value depends on its type:
     * <ul>
     * <li>The hash code of a primitive value <tt><i>v</i></tt> is equal to
     *     <tt><i>WrapperType</i>.valueOf(<i>v</i>).hashCode()</tt>, where
     *     <tt><i>WrapperType</i></tt> is the wrapper type corresponding
     *     to the primitive type of <tt><i>v</i></tt> ({@link Byte},
     *     {@link Character}, {@link Double}, {@link Float}, {@link Integer},
     *     {@link Long}, {@link Short}, or {@link Boolean}).
     *
     * <li>The hash code of a string, enum, class, or annotation member-value
     I     <tt><i>v</i></tt> is computed as by calling
     *     <tt><i>v</i>.hashCode()</tt>.  (In the case of annotation
     *     member values, this is a recursive definition.)
     *
     * <li>The hash code of an array member-value is computed by calling
     *     the appropriate overloading of
     *     {@link java.util.Arrays#hashCode(long[]) Arrays.hashCode}
     *     on the value.  (There is one overloading for each primitive
     *     type, and one for object reference types.)
     * </ul>
     *
     * @return the hash code of this annotation
     */
    int hashCode();

    /**
     * Returns a string representation of this annotation.  The details
     * of the representation are implementation-dependent, but the following
     * may be regarded as typical:
     * <pre>
     *   &#064;com.acme.util.Name(first=Alfred, middle=E., last=Neuman)
     * </pre>
     *
     * @return a string representation of this annotation
     */
    String toString();

    /**
     * Returns the annotation type of this annotation.
     * @return the annotation type of this annotation
     */
    Class<? extends Annotation> annotationType();
}

RetentionPolicy源码:


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,   /* Annotation信息仅存在于编译器处理期间,编译器处理完之后就没有该Annotation信息了  */
    /**
     * 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,   /* 编译器将Annotation存储于类对应的.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    /* 编译器将Annotation存储于class文件中,并且可由JVM读入 */
}

ElementType源码:

package java.lang.annotation;
/**
 * The constants of this enumerated type provide a simple classification of the
 * syntactic locations where annotations may appear in a Java program. These
 * constants are used in {@link Target java.lang.annotation.Target}
 * meta-annotations to specify where it is legal to write annotations of a
 * given type.
 *
 * <p>The syntactic locations where annotations may appear are split into
 * <em>declaration contexts</em> , where annotations apply to declarations, and
 * <em>type contexts</em> , where annotations apply to types used in
 * declarations and expressions.
 *
 * <p>The constants {@link #ANNOTATION_TYPE} , {@link #CONSTRUCTOR} , {@link
 * #FIELD} , {@link #LOCAL_VARIABLE} , {@link #METHOD} , {@link #PACKAGE} ,
 * {@link #PARAMETER} , {@link #TYPE} , and {@link #TYPE_PARAMETER} correspond
 * to the declaration contexts in JLS 9.6.4.1.
 *
 * <p>For example, an annotation whose type is meta-annotated with
 * {@code @Target(ElementType.FIELD)} may only be written as a modifier for a
 * field declaration.
 *
 * <p>The constant {@link #TYPE_USE} corresponds to the 15 type contexts in JLS
 * 4.11, as well as to two declaration contexts: type declarations (including
 * annotation type declarations) and type parameter declarations.
 *
 * <p>For example, an annotation whose type is meta-annotated with
 * {@code @Target(ElementType.TYPE_USE)} may be written on the type of a field
 * (or within the type of the field, if it is a nested, parameterized, or array
 * type), and may also appear as a modifier for, say, a class declaration.
 *
 * <p>The {@code TYPE_USE} constant includes type declarations and type
 * parameter declarations as a convenience for designers of type checkers which
 * give semantics to annotation types. For example, if the annotation type
 * {@code NonNull} is meta-annotated with
 * {@code @Target(ElementType.TYPE_USE)}, then {@code @NonNull}
 * {@code class C {...}} could be treated by a type checker as indicating that
 * all variables of class {@code C} are non-null, while still allowing
 * variables of other classes to be non-null or not non-null based on whether
 * {@code @NonNull} appears at the variable's declaration.
 *
 * @author  Joshua Bloch
 * @since 1.5
 * @jls 9.6.4.1 @Target
 * @jls 4.1 The Kinds of Types and Values
 */
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  /*使用类型*/
}

通过以上可以总结出Annotation的结构:
在这里插入图片描述
由上面的内容可以得出以下Annotation具有以下特性:

1、Annotation就是个接口,而注解本身就是Annotation接口的子接口。ElementType是枚举类型,它用来指定Annotation 的类型。RetentionPolicy也是枚举类型,它用来指定 Annotation的策略。可以理解为不同 RetentionPolicy 类型的 Annotation 的作用域不同。

2、1 个 Annotation 和 1 个 RetentionPolicy 关联。即每个Annotation对象,都会有唯一的RetentionPolicy属性;1 个 Annotation 和 1~n 个 ElementType 关联,即每个 Annotation 对象,可以有若干个 ElementType 属性。

3、Annotation 有许多实现类,包括:Deprecated, Documented, Inherited, Override 等等。

总结:Annotation的每个实现类,都和1个 RetentionPolicy 关联并且和多个个 ElementType 关联。

三、自定义注解

通过上面的介绍,使用元注解我们就可以自己来声明自定义注解了。

定义注解:

//就x像定义一个接口一样,只不过它多了一个@
public @interface MyTestAnnotation {
}

上面这种没有任何成员变量的注解称作为标记注解,@Overried就是一个标记注解。

注解的作用就是给类、方法注入信息,所以注解也可以声明成员变量,带成员变量的注解叫做元数据Annotation,在注解中定义成员变量,语法类似于声明方法。

public @interface MyTestAnnotation {
    //定义了两个成员变量
    String username();
    int age();
}

在注解声明属性的时候,给出默认值。那么在修饰的时候,就可以不用具体指定了。

public @interface MyTestAnnotation {
    //定义了两个成员变量
    String username() default "江夏";;
    int age() default 20;
}

注意:注解属性类型只能是以下的几种类型

1、基本数据类型

2、String

3、枚举类型

4、注解类型

5、Class类型

上面就已经自定义了一个基本的注解了,那么如何使用注解呢?

//注解拥有什么属性,在修饰的时候就要给出相对应的值,
@MyTestAnnotation (name="江夏",age = 20)
public class User{
}

像上面的代码中注解有多个属性,则可以在注解括号中用“,”号隔开分别给对应的属性赋值。

如果注解上只有一个属性,并且属性的名称为value,那么在使用的时候,我们可以不写value,直接赋值给它就行


public @interface MyTestAnnotation {
    String value();
}

使用注解,可以不指定value,直接赋值


@MyTestAnnotation("江夏")
public void User() {

}

上面是如何定义注解,放在哪,而使用注解的关键就是注解属性的提取,获取属性的值也是使用注解的目的。

获取注解属性使用的是反射,这主要有三个基本的方法:


/**是否存在对应 Annotation 对象*/
  public boolean isAnnotationPresent(Class<? extends Annotation> annotationClass) {
        return GenericDeclaration.super.isAnnotationPresent(annotationClass);
    }

 /**获取 Annotation 对象*/
    public <A extends Annotation> A getAnnotation(Class<A> annotationClass) {
        Objects.requireNonNull(annotationClass);

        return (A) annotationData().annotations.get(annotationClass);
    }
 /**获取所有 Annotation 对象数组*/   
 public Annotation[] getAnnotations() {
        return AnnotationParser.toArray(annotationData().annotations);
    }    

下面结合前面的例子,我们来获取一下注解属性,在获取之前我们自定义的注解必须使用元注解@Retention(RetentionPolicy.RUNTIME):

package com.jiang.AnnotationPackage;

import java.lang.annotation.*;

@Documented
@Inherited
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface MyTestAnnotation {
    String name() default "江夏";
    int age() default 18;
}

package com.jiang.AnnotationPackage;

/**
 * 声明一个类,使用自定义注解
 */

@MyTestAnnotation(name = "江夏",age = 20)
public class AnnotationUser {
}

package com.jiang.AnnotationPackage;
public class AnnotationDemo {
    public static void main(String[] args) {
        /**
         * 获取类注解属性
         */
        Class<AnnotationUser> userClass = AnnotationUser.class;
        /**是否存在对应 Annotation 对象*/
        boolean annotationPresent = userClass.isAnnotationPresent(MyTestAnnotation.class);

        if(annotationPresent){
            /**获取 Annotation 对象*/
            MyTestAnnotation myTestAnnotation = userClass.getAnnotation(MyTestAnnotation.class);
            System.out.println("姓名是:"+myTestAnnotation.name());
            System.out.println("年龄是:"+myTestAnnotation.age());
        }
    }
}

运行结果如下:
图片
通过上述方法获取了属性信息之后,就可以把注解上的信息注入到方法上了。这里也是使用了反射。主要步骤如下:

  //1、反射出该类的方法
  Class classA = AnnotationDemo2.class;
  Method method = classA .getMethod("say", String.class, int.class);

  //2、通过该方法得到注解上的具体信息
  MyTestAnnotation annotation = method.getAnnotation(MyTestAnnotation.class);
  String name = annotation.username();
  int age = annotation.age();

  //将注解上的信息注入到方法上
  Object o = classA.newInstance();
  method.invoke(o, name, age);

例子如下:


package com.jiang.AnnotationPackage;

import java.lang.annotation.*;
/**
 * 声明一个自定义注解
 */
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface MyTestAnnotation2 {
    int result() default 50;
}

package com.jiang.AnnotationPackage;

import java.lang.reflect.Method;

/**
 * 自定义注解在方法上的使用
 */
public class MyTestAnnotationDemo2 {

    /**
     * @param number 猜数的大小
     */
    @MyTestAnnotation2(result = 85)
    public static void guess(int number){
        System.out.println(processGuess(number));
    }

    private static String processGuess(int number){
        try {
            Method guessnumber = MyTestAnnotationDemo2.class.getDeclaredMethod("guess",int.class);
            boolean annotationPresent = guessnumber.isAnnotationPresent(MyTestAnnotation2.class);
            if(annotationPresent){
                MyTestAnnotation2 annotation2 = guessnumber.getAnnotation(MyTestAnnotation2.class);
                if(number>annotation2.result()){
                    return "猜的数字大于指定数字";
                }else if (number==annotation2.result()){
                    return "猜的数字等于指定数字";
                }else{
                    return "猜的数字小于指定数字";
                }
            }
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        }
        return "猜测程序有误";
    }

    public static void main(String[] args) {
        guess(85);
        //guess(84);
        //guess(86);
    }
}

输出结果如下:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
这篇文章主要介绍了一些常用的注解,以及注解的源码,然后通过注解的一些特性和属性,我们可以自定义注解。

如果觉得本文写的不错,就点个三连吧。
如果觉得文章有不足之处需要改正的地方欢迎指出!
感兴趣的同学也可以扫描关注下方微信公众号,可以免费获取海量学习资源哦。
在这里插入图片描述

  Java知识库 最新文章
计算距离春节还有多长时间
系统开发系列 之WebService(spring框架+ma
springBoot+Cache(自定义有效时间配置)
SpringBoot整合mybatis实现增删改查、分页查
spring教程
SpringBoot+Vue实现美食交流网站的设计与实
虚拟机内存结构以及虚拟机中销毁和新建对象
SpringMVC---原理
小李同学: Java如何按多个字段分组
打印票据--java
上一篇文章      下一篇文章      查看所有文章
加:2021-07-15 16:02:30  更:2021-07-15 16:03:13 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年12日历 -2024/12/18 16:31:44-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码