Java注解(Annotation)的基本原理以及实现自定义注解

简介: 在我们使用springboot的时候我们知道因为注解的存在,使得我们的开发变得格外的方便、快捷。之前的文章Spring常用注解大全,值得你的收藏!!!对于spring中各类注解也进行过介绍。然而注解也并不是因为spring框架的兴起才出现的,而是很早就已经在java中被使用。

在我们使用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的结构:


4d65ee70b6984d308e508b14a9d67b41~tplv-k3u1fbpfcp-zoom-in-crop-mark_1304_0_0_0.webp.jpg


由上面的内容可以得出以下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());
        }
    }
}
复制代码


运行结果如下:


e48cdd5c3e3f4a47bbcbd1a20e4927cf~tplv-k3u1fbpfcp-zoom-in-crop-mark_1304_0_0_0.webp.jpg


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


//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);
    }
}
复制代码


输出结果如下:


f19adbc1fc544574971de3cdfbd886ef~tplv-k3u1fbpfcp-zoom-in-crop-mark_1304_0_0_0.webp.jpg


d58209bba925438b9a777914b8a53790~tplv-k3u1fbpfcp-zoom-in-crop-mark_1304_0_0_0.webp.jpg


feea53ef362e4fdbbf92f710ab8253d3~tplv-k3u1fbpfcp-zoom-in-crop-mark_1304_0_0_0.webp.jpg


这篇文章主要介绍了一些常用的注解,以及注解的源码,然后通过注解的一些特性和属性,我们可以自定义注解。

目录
相关文章
|
2月前
|
Java
在 Java 中捕获和处理自定义异常的代码示例
本文提供了一个 Java 代码示例,展示了如何捕获和处理自定义异常。通过创建自定义异常类并使用 try-catch 语句,可以更灵活地处理程序中的错误情况。
83 1
|
1月前
|
XML Java 编译器
Java注解的底层源码剖析与技术认识
Java注解(Annotation)是Java 5引入的一种新特性,它提供了一种在代码中添加元数据(Metadata)的方式。注解本身并不是代码的一部分,它们不会直接影响代码的执行,但可以在编译、类加载和运行时被读取和处理。注解为开发者提供了一种以非侵入性的方式为代码提供额外信息的手段,这些信息可以用于生成文档、编译时检查、运行时处理等。
65 7
|
2月前
|
Java
在 Java 中,如何自定义`NumberFormatException`异常
在Java中,自定义`NumberFormatException`异常可以通过继承`IllegalArgumentException`类并重写其构造方法来实现。自定义异常类可以添加额外的错误信息或行为,以便更精确地处理特定的数字格式转换错误。
46 1
|
1月前
|
Java
java实现从HDFS上下载文件及文件夹的功能,以流形式输出,便于用户自定义保存任何路径下
java实现从HDFS上下载文件及文件夹的功能,以流形式输出,便于用户自定义保存任何路径下
88 34
|
19天前
|
监控 Java API
探索Java NIO:究竟在哪些领域能大显身手?揭秘原理、应用场景与官方示例代码
Java NIO(New IO)自Java SE 1.4引入,提供比传统IO更高效、灵活的操作,支持非阻塞IO和选择器特性,适用于高并发、高吞吐量场景。NIO的核心概念包括通道(Channel)、缓冲区(Buffer)和选择器(Selector),能实现多路复用和异步操作。其应用场景涵盖网络通信、文件操作、进程间通信及数据库操作等。NIO的优势在于提高并发性和性能,简化编程;但学习成本较高,且与传统IO存在不兼容性。尽管如此,NIO在构建高性能框架如Netty、Mina和Jetty中仍广泛应用。
28 3
|
19天前
|
安全 算法 Java
Java CAS原理和应用场景大揭秘:你掌握了吗?
CAS(Compare and Swap)是一种乐观锁机制,通过硬件指令实现原子操作,确保多线程环境下对共享变量的安全访问。它避免了传统互斥锁的性能开销和线程阻塞问题。CAS操作包含三个步骤:获取期望值、比较当前值与期望值是否相等、若相等则更新为新值。CAS广泛应用于高并发场景,如数据库事务、分布式锁、无锁数据结构等,但需注意ABA问题。Java中常用`java.util.concurrent.atomic`包下的类支持CAS操作。
52 2
|
2月前
|
存储 算法 Java
大厂面试高频:什么是自旋锁?Java 实现自旋锁的原理?
本文详解自旋锁的概念、优缺点、使用场景及Java实现。关注【mikechen的互联网架构】,10年+BAT架构经验倾囊相授。
大厂面试高频:什么是自旋锁?Java 实现自旋锁的原理?
|
2月前
|
Java
Java之CountDownLatch原理浅析
本文介绍了Java并发工具类`CountDownLatch`的使用方法、原理及其与`Thread.join()`的区别。`CountDownLatch`通过构造函数接收一个整数参数作为计数器,调用`countDown`方法减少计数,`await`方法会阻塞当前线程,直到计数为零。文章还详细解析了其内部机制,包括初始化、`countDown`和`await`方法的工作原理,并给出了一个游戏加载场景的示例代码。
Java之CountDownLatch原理浅析
|
1月前
|
Java 编译器 数据库
Java 中的注解(Annotations):代码中的 “元数据” 魔法
Java注解是代码中的“元数据”标签,不直接参与业务逻辑,但在编译或运行时提供重要信息。本文介绍了注解的基础语法、内置注解的应用场景,以及如何自定义注解和结合AOP技术实现方法执行日志记录,展示了注解在提升代码质量、简化开发流程和增强程序功能方面的强大作用。
85 5
时间轮-Java实现篇
在前面的文章《[时间轮-理论篇](https://developer.aliyun.com/article/910513)》讲了时间轮的一些理论知识,然后根据理论知识。我们自己来实现一个简单的时间轮。