注解概念:
简单理解注解就是一种标记,常用于代替冗余的配置(XML,properties),又或者是编译器进行的一些检查如JDK自带的Override、Deprecated等。可以直白的来讲有注解和没有注解本质上不影响程序的运行。注解的作用在于「注解的处理程序」,注解处理程序通过捕获被注解标记的代码然后进行一些处理,这就是注解工作的方式。
注解本质:所谓的注解其实就是一个实现了Annotation的接口,而我们通过反射获取到的实际上是通过JDK动态代理生成的代理类,这个类实现了我们的注解接口
Java中进行定义注解:
Java中进行定义注解很简单,直接进行使用@interface即可完成注解的定义。
public @interface PrintMsg {
}
自定义注解的使用:
@PrintMsg
public class AnnotationTest {
public static void main(String[] args) {
System.out.println("annotation test OK!");
}
}
我们发现写与不写这个注解的效果是相同的,这也印证了我们说的注解只是一种「标记」,有它没它并不影响程序的运行。
元注解:
元注解:对注解进行注解,也就是对注解进行标记,元注解的背后处理逻辑由apt
tool提供,对注解的行为做出一些限制,例如生命周期,作用范围等等。
@Retention:
用于描述注解的生命周期,表示注解在什么范围有效,它有三个取值,如下表所示:
@Target:
用于描述注解作用的「对象类型」,这个就非常多了,如下表所示:
@Documented:
将注解元素加入到javadoc之中。
@inherited:
如果被这个注解标记了,被标记的类、接口会继承父类、接口的上面的注解.
@Repeatable
表示该注解可以进行重复标记。
注解属性:
除了元注解之外,我们还能给注解添加属性,注解中的属性以无参方法的形式定义,方法名为属性名,返回值为成员变量的类型,还是以上述注解为例:
首先给这个注解加点细节,生命周期改为Runtime,使得运行期存在可以被我们获取。
@Retention(RetentionPolicy.RUNTIME)
public @interface PrintMsg {
int count() default 1;
String name() default "my name is PrintMsg";
}
@PrintMsg(count = 2020)
public class AnnotationTest {
public static void main(String[] args) {
//通过反射获取该注解
PrintMsg annotation = AnnotationTest.class.getAnnotation(PrintMsg.class);
System.out.println(annotation.count());
System.out.println(annotation.name());
}
}
输出如下:
2020
my name is PrintMs
注解分类:
按照注解的生命周期以及处理方式的不同,通常将注解分为「运行时注解」和「编译时注解」
1、运行时注解的本质是实现了Annotation接口的特殊接口,JDK在运行时为其创建代理类,注解方法的调用实际是通过AnnotationInvocationHandler的invoke方法,AnnotationInvocationHandler其中维护了一个Map,Map中存放的是方法名与返回值的映射,对注解中自定义方法的调用其实最后就是用方法名去查Map并且放回的一个过程
2、编译时注解通过注解处理器来支持,而注解处理器的实际工作过程由JDK在编译期提供支持,有兴趣可以看看javac的源码。
运行时注解详解:
之前我们说注解是一种标记,只是针对注解的作用而言,而Java语言层面注解到底是什么呢?以JSL中的一段话开头。
An annotation type declaration specifies a new annotation type, a special kind of interface type. To distinguish an annotation type declaration from a normal interface declaration, the keyword interface is preceded by an at-sign (@).
简单来说就是,注解只不过是在interface前面加了@符号的特殊接口,那么不妨以PrintMsg.class开始来看看,通过javap反编译的到信息如下:
public interface com.hustdj.jdkStudy.annotation.PrintMsg extends java.lang.annotation.Annotation
minor version: 0
major version: 52
flags: (0x2601) ACC_PUBLIC, ACC_INTERFACE, ACC_ABSTRACT, ACC_ANNOTATION
this_class: #1 // com/hustdj/jdkStudy/annotation/PrintMsg
super_class: #3 // java/lang/Object
interfaces: 1, fields: 0, methods: 2, attributes: 2
Constant pool:
#1 = Class #2 // com/hustdj/jdkStudy/annotation/PrintMsg
#2 = Utf8 com/hustdj/jdkStudy/annotation/PrintMsg
#3 = Class #4 // java/lang/Object
#4 = Utf8 java/lang/Object
#5 = Class #6 // java/lang/annotation/Annotation
#6 = Utf8 java/lang/annotation/Annotation
#7 = Utf8 count
#8 = Utf8 ()I
#9 = Utf8 AnnotationDefault
#10 = Integer 1
#11 = Utf8 name
#12 = Utf8 ()Ljava/lang/String;
#13 = Utf8 my name is PrintMsg
#14 = Utf8 SourceFile
#15 = Utf8 PrintMsg.java
#16 = Utf8 RuntimeVisibleAnnotations
#17 = Utf8 Ljava/lang/annotation/Retention;
#18 = Utf8 value
#19 = Utf8 Ljava/lang/annotation/RetentionPolicy;
#20 = Utf8 RUNTIME
{
public abstract int count();
descriptor: ()I
flags: (0x0401) ACC_PUBLIC, ACC_ABSTRACT
AnnotationDefault:
default_value: I#10
public abstract java.lang.String name();
descriptor: ()Ljava/lang/String;
flags: (0x0401) ACC_PUBLIC, ACC_ABSTRACT
AnnotationDefault:
default_value: s#13
}
SourceFile: "PrintMsg.java"
RuntimeVisibleAnnotations:
0: #17(#18=e#19.#20)
从第一行就不难看出,注解是一个继承自Annotation接口的接口,它并不是一个类,那么getAnnotation()拿到的到底是什么呢?不难想到,通过动态代理生成了代理类,是这样的嘛?通过启动参数-Dsun.misc.ProxyGenerator.saveGeneratedFiles=true
或者在上述代码中添加:
System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles","true");
将通过JDK的proxyGenerator生成的代理类保存下来在com.sun.proxy文件夹下面找到这个class文件,通过javap反编译结果如下:
public final class com.sun.proxy.$Proxy1 extends java.lang.reflect.Proxy implements com.hustdj.jdkStudy.annotation.PrintMsg
可以看出JDK通过动态代理实现了一个类继承我们自定义的PrintMsg接口,由于这个方法字节码太长了,看起来头疼,利用idea自带的反编译直接在idea中打开该class文件如下:
public final class $Proxy1 extends Proxy
implements PrintMsg
{
public $Proxy1(InvocationHandler invocationhandler)
{
super(invocationhandler);
}
public final boolean equals(Object obj)
{
try
{
return ((Boolean)super.h.invoke(this, m1, new Object[] {
obj
})).booleanValue();
}
catch(Error _ex) { }
catch(Throwable throwable)
{
throw new UndeclaredThrowableException(throwable);
}
}
public final String name()
{
try
{
return (String)super.h.invoke(this, m3, null);
}
catch(Error _ex) { }
catch(Throwable throwable)
{
throw new UndeclaredThrowableException(throwable);
}
}
public final String toString()
{
try
{
return (String)super.h.invoke(this, m2, null);
}
catch(Error _ex) { }
catch(Throwable throwable)
{
throw new UndeclaredThrowableException(throwable);
}
}
public final int count()
{
try
{
return ((Integer)super.h.invoke(this, m4, null)).intValue();
}
catch(Error _ex) { }
catch(Throwable throwable)
{
throw new UndeclaredThrowableException(throwable);
}
}
public final Class annotationType()
{
try
{
return (Class)super.h.invoke(this, m5, null);
}
catch(Error _ex) { }
catch(Throwable throwable)
{
throw new UndeclaredThrowableException(throwable);
}
}
public final int hashCode()
{
try
{
return ((Integer)super.h.invoke(this, m0, null)).intValue();
}
catch(Error _ex) { }
catch(Throwable throwable)
{
throw new UndeclaredThrowableException(throwable);
}
}
private static Method m1;
private static Method m3;
private static Method m2;
private static Method m4;
private static Method m5;
private static Method m0;
static
{
try
{
m1 = Class.forName("java.lang.Object").getMethod("equals", new Class[] {
Class.forName("java.lang.Object")
});
m3 = Class.forName("com.hustdj.jdkStudy.annotation.PrintMsg").getMethod("name", new Class[0]);
m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]);
m4 = Class.forName("com.hustdj.jdkStudy.annotation.PrintMsg").getMethod("count", new Class[0]);
m5 = Class.forName("com.hustdj.jdkStudy.annotation.PrintMsg").getMethod("annotationType", new Class[0]);
m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]);
}
catch(NoSuchMethodException nosuchmethodexception)
{
throw new NoSuchMethodError(nosuchmethodexception.getMessage());
}
catch(ClassNotFoundException classnotfoundexception)
{
throw new NoClassDefFoundError(classnotfoundexception.getMessage());
}
}
}
总结: 所谓的注解其实就是一个实现了Annotation的接口,而我们通过反射获取到的实际上是通过JDK动态代理生成的代理类,这个类实现了我们的注解接口。
注解调用流程解析:
以$Proxy1的count方法为例:
public final int count()
{
try
{
return ((Integer)super.h.invoke(this, m4, null)).intValue();
}
catch(Error _ex) { }
catch(Throwable throwable)
{
throw new UndeclaredThrowableException(throwable);
}
}
跟进super
public class Proxy implements java.io.Serializable {
protected InvocationHandler h;
}
这个InvocationHandler是谁呢?通过在Proxy(InvocationHandler h)方法上打断点追踪结果如下:
原来我们对于count方法的调用传递给了AnnotationInvocationHandler。
底层调用逻辑:
public Object invoke(Object var1, Method var2, Object[] var3) {
//var4-方法名
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:
//因为我们是count方法,走这个分支
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);
}
//返回var6
return var6;
}
}
}
}
这个memberValues是啥?
private final Map<String, Object> memberValues;
他是一个map,存放的是方法名(String)与值的键值对
这里以count()方法的invoke执行为例。
可以看到它走了default的分支,从上面的map中取到了,我们所定义的2020,那这个memberValues是什么时候解析出来的呢?
通过查看方法调用栈,我们发现在下图这个时候count和name还没有赋值
在方法中加入断点重新调试得到如下结果
2020出现了,再跟进parseMemberValue方法中,再次重新调试
紧接着跟进:
康康javap反编译的字节码中的常量池吧
好巧啊,正好是2020!!
因此发现最后是从ConstantPool中根据偏移量来获取值的,至此另一个疑问也解决了,我们在注解中设置的方法,最终在调用的时候,是从一个以<方法名,属性值>为键值对的map中获取属性值,定义成方法只是为了在反射调用作为参数而已,所以也可以将它看成属性吧。
运行时注解产生的流程如下:
1 对annotation的反射调用使得动态代理创建实现该注解的一个类
2 代理背后真正的处理对象为AnnotationInvocationHandler,这个类内部维护了一个map,这个map的键值对形式为<注解中定义的方法名,对应的属性名>
3 任何对annotation的自定义方法的调用(抛开动态代理类继承自object的方法),最终都会实际调用AnnotatiInvocationHandler的invoke方法,并且该invoke方法对于这类方法的处理很简单,拿到传递进来的方法名,然后去查map
4 Map中memeberValues的初始化是在AnnotationParser中完成的,是勤快的,在方法调用前就会初始化好,缓存在map里面
5 AnnotationParser最终是通过ConstantPool对象从常量池中拿到对应的数据的,再往下ConstantPool对象就不深入了.
编译时注解剖析:
由于编译时注解的很多处理逻辑内化在Javac中,这里不做过多探讨,仅对《深入理解JVM》中的知识点进行梳理和总结。
在JDK5中,Java语言提供了对于注解的支持,此时的注解只在程序运行时发挥作用,但是在JDK6中,JDK新加入了一组插入式注解处理器的标准API,这组API使得我们对于注解的处理可以提前至编译期,从而影响到前端编译器的工作!!常用的Lombok就是通过注解处理器来实现的。
自定义简单注解处理器:
实现自己的注解处理器,首先需要继承抽象类javax.annotation.processing.AbstractProcessor,只有process()方法需要我们实现,process()方法如下:
//返回值表示是否修改Element元素
public abstract boolean process(Set<? extends TypeElement> annotations,RoundEnvironment roundEnv);
- annotations:这个注解处理器处理的注解集合
- roundEnv:当前round的抽象语法树结点,每一个结点都为一个Element,一共有18种Element包含了Java中
的所有元素:
- PACKAGE(包)
- ENUM(枚举)
- CLASS(类)
- ANNOTATION_TYPE(注解)
- INTERFACE(接口)
- ENUM_CONSTANT(枚举常量)
- FIELD(字段)
- PARAMETER(参数)
- LOCAL_VARIABLE(本地变量)
- EXCEPTION_PARAMETER(异常)
- METHOD(方法)
- CONSTRUCTOR(构造方法)
- STATIC_INIT(静态代码块)
- INSTANCE_INIT(实例代码块)
- TYPE_PARAMETER(参数化类型,泛型尖括号中的)
- RESOURCE_VARIABLE(资源变量,try-resource)
- MODULE(模块)
- OTHER(其他)
此外还有一个重要的实例变量processingEnv,它提供了上下文环境,需要创建新的代码,向编译器输出信息,获取其他工具类都可以通过它。
实现一个简单的编译器注解处理器也非常简单,继承AbstractProcessor实现process()方法,在process()方法中实现自己的处理逻辑即可,此外需要两个注解配合一下:
@SupportedAnnotationTypes:该注解处理器处理什么注解
@SupportedSourceVersion:注解处理器支持的语言版本
demo:
@SupportedAnnotationTypes("com.hustdj.jdkStudy.annotation.PrintMsg")
@SupportedSourceVersion(SourceVersion.RELEASE_8)
public class PrintNameProcessor extends AbstractProcessor {
@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
Messager messager = processingEnv.getMessager();
for (Element element : roundEnv.getRootElements()) {
messager.printMessage(Diagnostic.Kind.NOTE,"my name is "+element.toString());
}
//不修改语法树,返回false
return false;
}
}
输出:
G:\ideaIU\ideaProjects\cookcode\src\main\java>javac com\hustdj\jdkStudy\annotation\PrintMsg.java
G:\ideaIU\ideaProjects\cookcode\src\main\java>javac com\hustdj\jdkStudy\annotation\PrintNameProcessor.java
G:\ideaIU\ideaProjects\cookcode\src\main\java>javac -processor com.hustdj.jdkStudy.annotation.PrintNameProcessor com\hustdj\jdkStudy\annotation\AnnotationTest.java
警告: 来自注释处理程序 'com.hustdj.jdkStudy.annotation.PrintNameProcessor' 的受支持 source 版本 'RELEASE_8' 低于 -source '1.9'
注: my name is com.hustdj.jdkStudy.annotation.AnnotationTest
1 个警告