码迷,mamicode.com
首页 > 编程语言 > 详细

Java注解

时间:2021-04-26 13:48:19      阅读:0      评论:0      收藏:0      [点我收藏+]

标签:doc   interface   生成   rri   方法   特殊   rgba   extend   member   

1、注解的本质

 在注解使用之前,Java各大框架(Spring,Hibernate等)使用xml来实现其松耦合方式的配置。但是随着项目越来越多,xml内容越来越复杂,维护成本越累越高。因此,业界提出采用一种标记式的高耦合配置方式,叫做注解。开发者可以在方法、类、字段属性等需要配置的地方进行注解。

关于注解和xml两种不同的配置模式,各有争论,各有优劣。注解可以提供更大的便捷高效,易于维护、修改,但耦合度高。而xml刚好相反。在Annotation接口定义的源码中有开头有这么一句话,描述了注解的本质。

The common interface extended by all annotation types //所有的注解类都继承自这个普通接口(Annotation)。

技术图片

再看注解@override的定义:

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
public @interface Override {
}

其实实质上就是:

public interface Override extends Annotation{
    
}

所以,注解的本质就是一个继承了Annotation接口的接口(可以反汇编任意一个注解类进行验证)。

一个注解需要有对应的解析代码。一个类或方法的注解的解析有两种形式:

  • 一种式编译期直接扫描(适用于JDK内置注解);
  • 一种运行期反射(适用于自定义注解)。

2、元注解

元注解是指用于注解的注解,通常用在注解的定义上,如:

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
public @interface Override {
}

上述@Override注解的定义中,@Target,@Retention就是元注解。元注解一般用于指定某个注解生命周期以及作用目标等信息。

Java有如下几个元注解:

  • @Target:注解的作用目标。
  • @Retention:注解的生命周期。
  • @Documented:注解是否应当被包含在JavaDoc文档中。
  • @Inherited:是否允许子类继承该注解。

其中,@Target用于指明被修饰的注解最终可以作用的目标是谁,即指明被修饰的注解用来修饰方法、类还是字段属性。

@Target定义如下:

技术图片

可以通过如下方式为value传值。

@Target(value = {ElementType.FIELD})

此时被@Target注解修饰的注解只能作用在成员字段上,不能用于修饰方法或者类。ElementType 为一个枚举类型,取值如下:

ElementType.TYPE,         //允许被修饰的注解作用在类、接口和枚举上
ElementType.FIELD,        //允许作用在属性字段上
ElementType.METHOD,        //允许作用在方法上
ElementType.PARAMETER,     //允许作用在方法参数上
ElementType.CONSTRUCTOR,    //允许作用在构造器上
ElementType.LOCAL_VARIABLE,  //允许作用在本地局部变量上
ElementType.ANNOTATION_TYPE,  //允许作用在注解上
ElementType.PACKAGE,      //允许作用在包上

 @Retention用于指明当前注解的生命周期。定义如下:

技术图片

同样的,它也有一个value属性。RetentionPolicy 同样是一个枚举值。

RetentionPolicy.SOURCE     //当前注解编译期可见,不会写入 class 文件
RetentionPolicy.CLASS     //类加载阶段丢弃,会写入 class 文件
RetentionPolicy.RUNTIME    //永久保存,可以反射获取

3、Java内置三大注解

除上述四种元注解外,JDK预定了另外三种注解:

  • @Override
  • @Deprecated
  • @SuppressWarnings

@Override定义

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
public @interface Override {
}

它没有任何属性,所以不存储任何其它信息。只能作用于方法之上。编译结束后将被丢弃。是一种典型的标记式注解,仅被编译器可见。

@Deprecated定义:

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

仍是一种标记式注解,永远存在,课修饰所有类型(目前不再推荐使用)。

@SuppressWarnings用来压制Java的警告。定义如下:

@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();
}

它有一个 value 属性需要你主动的传值,value 代表的就是需要被压制的警告类型。

比如下列一段代码:

 public static void main(String[] args) {
        Date data = new Date(2021,4,23);    
 }

程序启动时编译器会报一个警告。

Warning:(15, 21) java: java.util.Date中的Date(int,int,int)已过时

此时,如果不希望程序启动时,编译器检查代码中过时的方法,则可以使用@SuppressWarnings注解,并给它的属性输入一个参数来压制编译器的检查。

@SuppressWarning(value = "deprecated")
public static void main(String[] args) {
    Date date = new Date(2018, 7, 11);
}

4、注解与反射

通过前文可知,注解本质上时继承了Annotation接口的接口。另外,我们可以从虚拟机的层面来分析注解的本质。

首先自定义一个注解类型:

@Target(value = {ElementType.FIELD,ElementType.METHOD,ElementType.TYPE})
@Retention(value = RetentionPolicy.RUNTIME)
public @interface Hello {
    String value();
}

上述定义指明这个注解只能修饰字段和方法,并且该注解永久存活,以便我们反射获取。

虚拟机规范定义了一系列和注解相关的属性表。无论是字段、方法或是类本身,如果被注解修饰了,就可以被写进字节码文件。属性表有以下几种:

  • RuntimeVisibleAnnotations:运行时可见的注解
  • RuntimeInVisibleAnnotations:运行时不可见的注解
  • RuntimeVisibleParameterAnnotations:运行时可见的方法参数注解
  • RuntimeInVisibleParameterAnnotations:运行时不可见的方法参数注解
  • AnnotationDefault:注解类元素的默认值

上述属性表显示了注解在字节码文件中存储方式。因此对于一个类或接口,Class类中提供了下列方式用于反射:

  • getAnnotation:返回指定的注解
  • isAnnotationPresent:判定当前元素是否被指定注解修饰
  • getAnnotations:返回所有的注解
  • getDeclaredAnnotation:返回本元素的指定注解
  • getDeclaredAnnotations:返回本元素的所有注解,不包含父类继承而来的

用例:

首先设置一个虚拟器启动参数,用于捕获JDK动态代理类。

-Dsun.misc.ProxyGenerator.saveGeneratedFiles=true

然后在main函数中编码如下:

@Hello(value = "hello!")
public class Tester {
    public static void main(String[] args) {
        //获取Tester类上的注解对象
        Hello annotation = Tester.class.getAnnotation(Hello.class);
        //调用注解对象的value方法,并打印到控制台
        System.out.println(annotation.value());
    }
}
//输出结果:hello!

因为注解本质上继承了Annotation接口的接口,所有通过反射即getAnnotation方法去获取注解类的实例时,其实JDK是通过动态代理截至生成一个实现注解的代理类。所有我们可以进行调试。

技术图片

找到临时代理类$Proxy1,反编译代码如下:

技术图片

从反编译代码中可以看到动态代理类$Proxy1实现了Hello接口,并重新其所有方法,包括value方法及接口Hello从Annotation接口继承而来的方法。

在看Hello的字节码

技术图片

Hello字节码显示确实继承了Annotation的接口。在flag字段中,我们可以看到,有个ACC_ANNOTATION标记,说明是一个注解,因此注解本质就是继承了Annotation的特殊接口。

 5、总结

注解本质就是继承了Annotation的特殊接口,其具体实现类是Java运行时生成的动态代理类。通过动态代理对象调用自定义注解(接口)的方法,会最终调用AnnotationInvocationHandler的invoke方法。改方法会从memberValues这个map中索引出对应的值。而memberValues的来源是Java厂里池。

Java注解

标签:doc   interface   生成   rri   方法   特殊   rgba   extend   member   

原文地址:https://www.cnblogs.com/zhongqifeng/p/14693074.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!