标签:doc interface 生成 rri 方法 特殊 rgba extend member
在注解使用之前,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接口的接口(可以反汇编任意一个注解类进行验证)。
一个注解需要有对应的解析代码。一个类或方法的注解的解析有两种形式:
元注解是指用于注解的注解,通常用在注解的定义上,如:
@Target(ElementType.METHOD) @Retention(RetentionPolicy.SOURCE) public @interface Override { }
上述@Override注解的定义中,@Target,@Retention就是元注解。元注解一般用于指定某个注解生命周期以及作用目标等信息。
Java有如下几个元注解:
其中,@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 //永久保存,可以反射获取
除上述四种元注解外,JDK预定了另外三种注解:
@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); }
通过前文可知,注解本质上时继承了Annotation接口的接口。另外,我们可以从虚拟机的层面来分析注解的本质。
首先自定义一个注解类型:
@Target(value = {ElementType.FIELD,ElementType.METHOD,ElementType.TYPE}) @Retention(value = RetentionPolicy.RUNTIME) public @interface Hello { String value(); }
上述定义指明这个注解只能修饰字段和方法,并且该注解永久存活,以便我们反射获取。
虚拟机规范定义了一系列和注解相关的属性表。无论是字段、方法或是类本身,如果被注解修饰了,就可以被写进字节码文件。属性表有以下几种:
上述属性表显示了注解在字节码文件中存储方式。因此对于一个类或接口,Class类中提供了下列方式用于反射:
用例:
首先设置一个虚拟器启动参数,用于捕获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的特殊接口。
注解本质就是继承了Annotation的特殊接口,其具体实现类是Java运行时生成的动态代理类。通过动态代理对象调用自定义注解(接口)的方法,会最终调用AnnotationInvocationHandler的invoke方法。改方法会从memberValues这个map中索引出对应的值。而memberValues的来源是Java厂里池。
标签:doc interface 生成 rri 方法 特殊 rgba extend member
原文地址:https://www.cnblogs.com/zhongqifeng/p/14693074.html