java自定义注解的使用和基本原理「建议收藏」

java自定义注解

大家好,又见面了,我是你们的朋友全栈君。

一、定义

  1. Java文件叫做Annotation,用@interface表示。
  2. java中提供了四种元注解,用于创建新的注解,分别是:@Retention、@Target、@Document、@Inherited

二、元注解

  1. @Retention 从源代码中可以看出,主要用于提示注解要保留多长时间
package java.lang.annotation;

/**
 * Indicates how long annotations with the annotated type are to
 * be retained.  If no Retention annotation is present on
 * an annotation type declaration, the retention policy defaults to
 * {@code RetentionPolicy.CLASS}.
 *
 * <p>A Retention meta-annotation has effect only if the
 * meta-annotated type is used directly for annotation.  It has no
 * effect if the meta-annotated type is used as a member type in
 * another annotation type.
 *
 * @author  Joshua Bloch
 * @since 1.5
 * @jls 9.6.3.2 @Retention
 */
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Retention {
    /**
     * Returns the retention policy.
     * @return the retention policy
     */
    RetentionPolicy value();
}

有三种取值:

  1. RetentionPolicy.SOURCE 将会被编译器抛弃

  2. RetentionPolicy.CLASS 注解会被编辑器保留在类文件中,但是会被vm抛弃

  3. RetentionPolicy.RUNTIME 注解会被编辑器保留在类文件中,也会被vm保留,所以可以通过反射读取。

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
}
  1. @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的取值有下面十种:

  1. ElementType.TYPE 用于类,接口(包括注解)或者枚举类型

  2. ElementType.FIELD 用于属性字段包括枚举常量

  3. ElementType.METHOD 用于方法级别

  4. ElementType.PARAMETER 用于参数声明

  5. ElementType.CONSTRUCTOR 用于构造函数声明

  6. ElementType.LOCAL_VARIABLE 用于局部变量声明

  7. ElementType.ANNOTATION_TYPE 用于注解类型声明

  8. ElementType.PACKAGE 用于包声明

  9. ElementType.TYPE_PARAMETER 用于泛型声明

  10. ElementType.TYPE_USE 用于任意类型声明

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
}
  1. @Documented 将注解包含在Javadoc中
package java.lang.annotation;

/**
 * Indicates that annotations with a type are to be documented by javadoc
 * and similar tools by default.  This type should be used to annotate the
 * declarations of types whose annotations affect the use of annotated
 * elements by their clients.  If a type declaration is annotated with
 * Documented, its annotations become part of the public API
 * of the annotated elements.
 *
 * @author  Joshua Bloch
 * @since 1.5
 */
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Documented {
}
  1. @Inherited 允许子类继承父类
package java.lang.annotation;

/**
 * Indicates that an annotation type is automatically inherited.  If
 * an Inherited meta-annotation is present on an annotation type
 * declaration, and the user queries the annotation type on a class
 * declaration, and the class declaration has no annotation for this type,
 * then the class's superclass will automatically be queried for the
 * annotation type.  This process will be repeated until an annotation for this
 * type is found, or the top of the class hierarchy (Object)
 * is reached.  If no superclass has an annotation for this type, then
 * the query will indicate that the class in question has no such annotation.
 *
 * <p>Note that this meta-annotation type has no effect if the annotated
 * type is used to annotate anything other than a class.  Note also
 * that this meta-annotation only causes annotations to be inherited
 * from superclasses; annotations on implemented interfaces have no
 * effect.
 *
 * @author  Joshua Bloch
 * @since 1.5
 * @jls 9.6.3.3 @Inherited
 */
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Inherited {
}

三、自定义注解的使用

  1. 创建一个自定义注解
import java.lang.annotation.*;

/**
 * Created Date: 2019/3/1
 * 创建自定义注解
 */
@Target({ ElementType.METHOD, ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface TestAnnotation {
    
    String value();
}
  1. 通过反射获取注解
public class Test {

	@TestAnnotation(value = "测试方法")
    public static void main(String args[]){
        try {
            Class c=Test.class;
            Method[] methods=c.getDeclaredMethods();
            for(Method method:methods){
                Annotation[] annotations=method.getDeclaredAnnotations();
                for(Annotation annotation:annotations){
                    TestAnnotation testAnnotation= (TestAnnotation) annotation;
                    System.out.println(testAnnotation.value());
                }
            }
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
}

四、web开发中的运用

在web开发中,权限控制非常重要,所以有些接口会限制必须登录之后才能访问,但是个别接口并没有这种限制。一种方式是把需要过滤的接口或者方法配置在文件中,每次请求时在拦截器中根据请求的路径与配置文件中的对比过滤。其实还有另外一种方式就是通过注解方式。

  1. 定义一个注解NoLogin
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface NoLogin {
}
  1. 标注在方法上
  2. 在拦截器中判断方法上是否有NoLogin注解
		public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object o) throws Exception {

        	//支持两种方式过滤  1、注解方式 添加@NoLogin注解
        	HandlerMethod handlerMethod= (HandlerMethod) o;
        	NoLogin noLogin=handlerMethod.getMethod().getDeclaredAnnotation(NoLogin.class);
        	if(null!=noLogin){
            	return true;
       		 }
       	}

五、java内置的注解

除了上述的四个元注解,java还内置了另外三个注解:

  1. @Override 它没有任何的属性,不能存储任何其他信息。它只能作用于方法之上,编译结束后将被丢弃。在java编译器编译成字节码的时候,一旦发现某个方法被这个注解标识过,就会匹配父类中是否存在同一方法,如果不存在就回编译失败。
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
public @interface Override {
}

  1. @Deprecated 弃用的注解
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(value={CONSTRUCTOR, FIELD, LOCAL_VARIABLE, METHOD, PACKAGE, PARAMETER, TYPE})
public @interface Deprecated {
}

  1. @SuppressWarnings 压制警告,比如某段代码中存在过时的方法,那么在编译过程中,会有warn警告,如果不想出现类似的警告,可在方法上添加这个注解。这个注解有一个value的值,这个value表示需要压制的警告类型。
@Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE})
@Retention(RetentionPolicy.SOURCE)
public @interface SuppressWarnings {
    /**
     * The set of warnings that are to be suppressed by the compiler in the
     * annotated element.  Duplicate names are permitted.  The second and
     * successive occurrences of a name are ignored.  The presence of
     * unrecognized warning names is <i>not</i> an error: Compilers must
     * ignore any warning names they do not recognize.  They are, however,
     * free to emit a warning if an annotation contains an unrecognized
     * warning name.
     *
     * <p> The string {@code "unchecked"} is used to suppress
     * unchecked warnings. Compiler vendors should document the
     * additional warning names they support in conjunction with this
     * annotation type. They are encouraged to cooperate to ensure
     * that the same names work across multiple compilers.
     * @return the set of warnings to be suppressed
     */
    String[] value();
}

六、注解的原理

1、java.lang.annotation.Annotation中有这么一句话:The common interface extended by all annotation types 所有的注解都继承于这个接口。怎么理解呢?其实刚才上面例子中的注解可以理解为:

public @interface TestAnnotation extends Annotation{

}

注解的本质就是一个继承了 Annotation 接口的接口

为了方便理解和掌握注解,还是以刚才的TestAnnotation注解为例。在idea中配置启动参数,方便查看代理产生的类,参数如下:

Dsun.misc.ProxyGenerator.saveGeneratedFiles=true

main方法运行结束后,会在/com/sun/proxy目录下生成一个代理类,反编译之后是这样的:
代理类proxy1重写了TestAnnotation的所有方法,包括value()和从Annotation继承来的equals()、hashCode()等方法。


package com.sun.proxy;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;
import org.fy.annotation.TestAnnotation;

public final class $Proxy1 extends Proxy implements TestAnnotation {
    private static Method m1;
    private static Method m2;
    private static Method m4;
    private static Method m0;
    private static Method m3;

    public $Proxy1(InvocationHandler var1) throws  {
        super(var1);
    }

    public final boolean equals(Object var1) throws  {
        try {
            return (Boolean)super.h.invoke(this, m1, new Object[]{var1});
        } catch (RuntimeException | Error var3) {
            throw var3;
        } catch (Throwable var4) {
            throw new UndeclaredThrowableException(var4);
        }
    }

    public final String toString() throws  {
        try {
            return (String)super.h.invoke(this, m2, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    public final Class annotationType() throws  {
        try {
            return (Class)super.h.invoke(this, m4, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    public final int hashCode() throws  {
        try {
            return (Integer)super.h.invoke(this, m0, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    public final String value() throws  {
        try {
            return (String)super.h.invoke(this, m3, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    static {
        try {
            m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
            m2 = Class.forName("java.lang.Object").getMethod("toString");
            m4 = Class.forName("org.fy.annotation.TestAnnotation").getMethod("annotationType");
            m0 = Class.forName("java.lang.Object").getMethod("hashCode");
            m3 = Class.forName("org.fy.annotation.TestAnnotation").getMethod("value");
        } catch (NoSuchMethodException var2) {
            throw new NoSuchMethodError(var2.getMessage());
        } catch (ClassNotFoundException var3) {
            throw new NoClassDefFoundError(var3.getMessage());
        }
    }
}

在看proxy1构造函数中有一个接口InvocationHandler,这个接口的实例化对象又是谁?
java中有一个专门用于注解类型的代理对象AnnotationInvocationHandler,位于sun.reflect.annotation包中。同样是invoke方法用于处理具体的业务。

public Object invoke(Object var1, Method var2, Object[] var3) {
        String var4 = var2.getName();
        Class[] var5 = var2.getParameterTypes();
        if (var4.equals("equals") && var5.length == 1 && var5[0] == Object.class) {
            return this.equalsImpl(var3[0]);
        } else if (var5.length != 0) {
            throw new AssertionError("Too many parameters for an annotation method");
        } else {
            byte var7 = -1;
            switch(var4.hashCode()) {
            case -1776922004:
                if (var4.equals("toString")) {
                    var7 = 0;
                }
                break;
            case 147696667:
                if (var4.equals("hashCode")) {
                    var7 = 1;
                }
                break;
            case 1444986633:
                if (var4.equals("annotationType")) {
                    var7 = 2;
                }
            }

            switch(var7) {
            case 0:
                return this.toStringImpl();
            case 1:
                return this.hashCodeImpl();
            case 2:
                return this.type;
            default:
                Object var6 = this.memberValues.get(var4);
                if (var6 == null) {
                    throw new IncompleteAnnotationException(this.type, var4);
                } else if (var6 instanceof ExceptionProxy) {
                    throw ((ExceptionProxy)var6).generateException();
                } else {
                    if (var6.getClass().isArray() && Array.getLength(var6) != 0) {
                        var6 = this.cloneArray(var6);
                    }

                    return var6;
                }
            }
        }
    }

var2是具体被调用的方法实例。var4获取方法的名称,然后判断是否equals方法,接着用swtich判断是否来自于annotation中的其他三个方法,toString、hashCode、annotationType。如果是这三个方法,则给var7附上特定的值,并且AnnotationInvocationHandler 实例中已经预定义好了这些方法的实现,直接调用即可。

假如var7没有匹配到上面的那四个方法,则会走下面的逻辑:这里有一个 memberValues,它是一个 Map 键值对,键是我们注解属性名称,值就是该属性当初被赋上的值。
如下图所示,在编译时候会对memberValues赋值,把我们定义好的value字段作为key值放入map中。
在这里插入图片描述
在具体执行时,会重新对memberValues赋值,如图所示:
在这里插入图片描述
这样就能从注解map中获取我们需要的值。

最后总结一下注解的工作原理:

首先,我们通过键值对的形式可以为注解属性赋值,像这样:@TestAnnotation(value = “测试方法”)

接着,你用注解修饰某个方法或者属性、类等,编译器将在编译期扫描每个注解,会检查注解是否允许作用在当前位置,如果允许,最后会将注解信息写入元素的属性表。

然后在进行反射的时候,jvm会把所有生命周期在 RUNTIME 的注解取出来放到一个 map 中,并创建一个 AnnotationInvocationHandler 实例,把这个 map 传递给它。虚拟机通过JDK 动态代理机制生成一个目标注解的代理类,并初始化好处理器。

那么这样,一个注解的实例就创建出来了,它本质上就是一个代理类。归纳后就是通过方法名返回注解属性值。

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

发布者:全栈程序员-用户IM,转载请注明出处:https://javaforall.cn/129135.html原文链接:https://javaforall.cn

【正版授权,激活自己账号】: Jetbrains全家桶Ide使用,1年售后保障,每天仅需1毛

【官方授权 正版激活】: 官方授权 正版激活 支持Jetbrains家族下所有IDE 使用个人JB账号...

(0)


相关推荐

  • c语言如何遍历数组,C语言数组遍历

    c语言如何遍历数组,C语言数组遍历C语言数组遍历教程C语言for循环遍历数组详解语法for(i=0;i<count;i++){//arr[i]}说明其中count是数组的元素的个数,此时,数组的每一个元素是arr[i]。C语言while循环遍历数组详解语法inti=0;while(i<count){//arr[i]i++;}说明其中count是数组的元素的个数,此时,数组的每一个元…

  • DataLoader详解

    DataLoader详解对数据集处理虽说很方便但在参数选取和其他的细节方面还容易出问题,尤其是最后一个Batch长度不足,会导致输出维度发生问题,若直接舍去,我还想要全部的数据结果使用方法①创建一个Dataset对象②创建一个DataLoader对象③循环这个DataLoader对象,将xx,xx加载到模型中进行训练train_loader=DataLoader(dataset,batch_size=batch_size,shuffle=shuffle,collate_fn=pad

  • 2021好玩的微信小程序_如何制作微信小程序

    2021好玩的微信小程序_如何制作微信小程序一、前期准备工作1、注册微信小程序开发者账号在官网注册页选择小程序注册即可,账号分为个人版和企业版:个人版相对于企业版注册流程更为简单和宽松。企业版注册需要支付认证费用,一般为300元,个人版不需要,企业版经过微信官方认证,更具有信誉度。个人版更多只是一个展示的平台,企业版可以作为一个完整的平台,个人版不可以做商业性质的小程序,但是企业版可以,个人版不可以开通微信支付。附近的小程序不显示个人版,只能通过搜索,扫码方式找到。个人版不支持快速获取微信用户的手机号。2、下载微信开发者工具微信平

  • 凶残的挖矿脚本,奴役我数千机器!

    凶残的挖矿脚本,奴役我数千机器!本文转载自不正经程序员温馨提示:本文中出现的命令和脚本,不要在自家服务器上随便运行,除非你知道自己在做什么。挖矿是把机器当作奴隶,一刻不停歇的去计算、运转,本质上是个无用的工作。但可惜的是,它能赚钱。用别人的机器去赚钱,更是很多人梦寐以求的,所以挖矿脚本屡禁不止。有钱的地方,就有技术。但反过来并不一定成立。牢记这个准则,就能够心平气和的学习新技术,而不是气急败坏的纠结为啥没钱。1.脚本从哪来?下面是一个http的报文。GET/console/images/%2E%2E%2F

  • sublime激活【2021最新】

    (sublime激活)这是一篇idea技术相关文章,由全栈君为大家提供,主要知识点是关于2021JetBrains全家桶永久激活码的内容https://javaforall.cn/100143.htmlIntelliJ2021最新激活注册码,破解教程可免费永久激活,亲测有效,上面是详细链接哦~2JTX0APX6F-eyJsaWNlb…

  • 使用文本编辑器编写Java源代码

    使用文本编辑器编写Java源代码使用文本编辑器编写Java源代码编写Java应用程序,可以使用任何一个文本编辑器来编写程序的源代码,然后使用JDK搭配的工具进行编译和运行。在这里我将介绍一个使用简单的文本编辑器来开发一个Java应用程序的步骤。打开记事本,在记事本中输入如下代码:publicclassHelloJava{publicstaticvoidmain(String[]args){…

发表回复

您的电子邮箱地址不会被公开。

关注全栈程序员社区公众号