欢迎大家访问我的博客:https://coder-programming.cn/
为了防止篇幅过长不易阅读,在介绍Java注解时。分为两篇文章进行详细介绍。
内容如下
通过上一篇的介绍Java高级特性——Java注解介绍与底层实现原理(上),概念比较多。虽然多,但是我们也要去理解背下来,说不定哪天你在面试的时候面试官就对你进行发问呢!
对相关的知识点有了初步的后,我们来结合实际联系一下如果自己写自定义注解,并进一步了解其底层原理实现。
我们分别实现4个自定义注解,并进行测试。
-
@MyAnnotationField:自定义成员注解
-
@MyAnnotationMethod:自定义方法注解
-
@MyAnnotationParameter:自定义参数注解
-
@MyAnnotationType:自定义类注解
代码示例
MyAnnotationType.java
/**
-
@author Coder编程
-
@Title: MyAnnotationType
-
@ProjectName simple-framework
-
@Description: 定义一个可以注解在Class,interface,enum上的注解
-
@date 2021/4/22 14:15
*/
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotationType {
/**
-
定义注解的一个元素 并给定默认值
-
@return
*/
String value() default “我是定义在类接口枚举类上的注解元素value的默认值”;
}
MyAnnotationMethod.java
/**
-
@author Coder编程
-
@Title: MyAnnotationMethod
-
@ProjectName simple-framework
-
@Description: 定义一个可以注解在METHOD上的注解
-
@date 2021/4/22 14:17
*/
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotationMethod {
/**
-
定义注解的一个元素 并给定默认值
-
@return
*/
String value() default “我是定义在方法上的注解元素value的默认值”;
}
MyAnnotationField.java
/**
-
@author Coder编程
-
@Title: MyAnnotationField
-
@ProjectName simple-framework
-
@Description: 定义一个可以注解在FIELD上的注解
-
@date 2021/4/22 14:17
*/
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotationField {
/**
-
定义注解的一个元素 并给定默认值
-
@return
*/
String value() default “我是定义在字段上的注解元素value的默认值”;
/**
-
定义注解一个type类型
-
@return
*/
int type();
}
MyAnnotationParameter.java
/**
-
@author Coder编程
-
@Title: MyAnnotationParameter
-
@ProjectName simple-framework
-
@Description: TODO
-
@date 2021/4/22 14:20
*/
@Target({ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotationParameter {
/**
-
定义注解的一个元素 并给定默认值
-
@return
*/
String value() default “我是定义在参数上的注解元素value的默认值”;
/**
-
定义注解类型
-
@return
*/
int type() default 1;
}
测试
package demo.annotation;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
/**
-
@author Coder编程
-
@Title: TestAnnotation
-
@ProjectName simple-framework
-
@Description: TODO
-
@date 2021/4/22 14:20
*/
@MyAnnotationType
public class TestAnnotation {
@MyAnnotationField(type = 1)
private String field = “我是字段”;
@MyAnnotationMethod(“测试方法”)
public void methodTest(@MyAnnotationParameter(“测试参数”)String str){
System.out.println("我是测试方法,Str = " + str);
}
/**
-
获取类上的注解
-
@throws ClassNotFoundException
*/
public static void parseTypeAnnotation() throws ClassNotFoundException {
//获取类上的注解
MyAnnotationType annotation = TestAnnotation.class.getAnnotation(MyAnnotationType.class);
System.out.println(“类上注解”+annotation);
//另外一种方法
Class clazz = Class.forName(“demo.annotation.TestAnnotation”);
/获取到类上的所有注解,只在类上,不包括成员,方法上的注解/
Annotation[] annotations = clazz.getAnnotations();
for (Annotation annotation1 : annotations) {
MyAnnotationType myAnnotationType = (MyAnnotationType)annotation1;
System.out.println(myAnnotationType.value());
}
}
/**
-
获取字段上的注解
-
@throws ClassNotFoundException
*/
public static void parseFieldAnnotation() throws ClassNotFoundException {
Class clazz = Class.forName(“demo.annotation.TestAnnotation”);
Field[] declaredFields = clazz.getDeclaredFields();
for (Field declaredField : declaredFields) {
//判断成员变量中是否有指定注解类型的注解
boolean hasAnnotation = declaredField.isAnnotationPresent(MyAnnotationField.class);
if(hasAnnotation){
MyAnnotationField annotation = declaredField.getAnnotation(MyAnnotationField.class);
System.out.println(annotation.value());
}
}
}
/**
-
获取方法,参数上的注解
-
@throws ClassNotFoundException
-
@throws NoSuchMethodException
*/
public static void parseMethodAnnotation() throws ClassNotFoundException, NoSuchMethodException {
Class clazz = Class.forName(“demo.annotation.TestAnnotation”);
Method methodTest = clazz.getDeclaredMethod(“methodTest”, String.class);
MyAnnotationMethod annotation = methodTest.getAnnotation(MyAnnotationMethod.class);
System.out.println(annotation.value());
Annotation[][] parameterAnnotations = methodTest.getParameterAnnotations();
for (Annotation[] parameterAnnotation : parameterAnnotations) {
for (Annotation annotation1 : parameterAnnotation) {
if(annotation1 instanceof MyAnnotationParameter){
System.out.println(“方法参数上的注解:”+((MyAnnotationParameter) annotation1).value());
}
}
}
}
public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException {
//parseTypeAnnotation();
parseFieldAnnotation();
//parseMethodAnnotation();
}
}
大家可以将以上代码多多动手,实战敲一下,加深印象!上面有涉及到 Java的反射内容,我的文章中好像还没写过,下次补上。不太了解的同学也可以自行查询下。
通过第一篇文章,我们知道了什么是注解,通过上面的自定义注解练习,我们又加深了对注解的印象。可是好像总是感觉少了点什么?注解的工作原理是什么?它是怎么获取到属性的值?注解的本质到底是什么?是接口呢?还是抽象类呢?
我们下来看下我们MyAnnotationType
的类关系图。
可以发现MyAnnotationType
底层是继承了Annotation
接口。当然,我们除了用这种方法,还可以通过查看字节码的方式来查看MyAnnotationType
底层到底是啥。
这里我们的命令是:javap -v MyAnnotation.class
注:-v 是 -verbose 缩写
$ javap -v MyAnnotationType.class
Classfile /E:/Github/simple-framework/target/classes/demo/annotation/MyAnnotationType.class
Last modified 2021-4-23; size 530 bytes
MD5 checksum 142d541c8ff55e25c20881f705f9d876
Compiled from “MyAnnotationType.java”
public interface demo.annotation.MyAnnotationType extends java.lang.annotation.Annotation
minor version: 0
major version: 52
flags: ACC_PUBLIC, ACC_INTERFACE, ACC_ABSTRACT, ACC_ANNOTATION
Constant pool:
#1 = Class #17 // demo/annotation/MyAnnotationType
#2 = Class #18 // java/lang/Object
#3 = Class #19 // java/lang/annotation/Annotation
#4 = Utf8 value
#5 = Utf8 ()Ljava/lang/String;
#6 = Utf8 AnnotationDefault
#7 = Utf8 我是定义在类接口枚举类上的注解元素value的默认值
#8 = Utf8 SourceFile
#9 = Utf8 MyAnnotationType.java
#10 = Utf8 RuntimeVisibleAnnotations
#11 = Utf8 Ljava/lang/annotation/Target;
#12 = Utf8 Ljava/lang/annotation/ElementType;
#13 = Utf8 TYPE
#14 = Utf8 Ljava/lang/annotation/Retention;
#15 = Utf8 Ljava/lang/annotation/RetentionPolicy;
#16 = Utf8 RUNTIME
#17 = Utf8 demo/annotation/MyAnnotationType
#18 = Utf8 java/lang/Object
#19 = Utf8 java/lang/annotation/Annotation
{
public abstract java.lang.String value();
descriptor: ()Ljava/lang/String;
flags: ACC_PUBLIC, ACC_ABSTRACT
AnnotationDefault:
default_value: s#7}
SourceFile: “MyAnnotationType.java”
RuntimeVisibleAnnotations:
0: #11(#4=[e#12.#13])
1: #14(#4=e#15.#16)
很明显,中间写了public interface demo.annotation.MyAnnotationType extends java.lang.annotation.Annotation
因此,可以看出注解的本质是 继承Annotation接口。
接下来我们继续探索。
我们用IDEA断点来调试下我们 parseTypeAnnotation()
方法
为了能更清晰的看到代码中间运行的过程。我们需要在IDEA 启动的时候加入一些参数:
-Djdk.proxy.ProxyGenerator.saveGeneratedFiles=true
//记录JDK动态代理生成器,将生成代理类的文件保留下来
在 VM ptions
中写入
运行完程序后,我们会得到一些文件
我们直接通过IDEA打开 $proxy1 文件。
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//
package com.sun.proxy;
import demo.annotation.MyAnnotationType;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;
public final class $Proxy1 extends Proxy implements MyAnnotationType {
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(“demo.annotation.MyAnnotationType”).getMethod(“annotationType”);
m0 = Class.forName(“java.lang.Object”).getMethod(“hashCode”);
m3 = Class.forName(“demo.annotation.MyAnnotationType”).getMethod(“value”);
} catch (NoSuchMethodException var2) {
throw new NoSuchMethodError(var2.getMessage());
} catch (ClassNotFoundException var3) {
throw new NoClassDefFoundError(var3.getMessage());
}
}
}
小结
走到这里,我们做一次小结:我们通过 IDEA 工具图和字节码看出我们的MyAnnotationType
注解(接口)其实是继承了Annotation
接口的,我们通过断点的方式和设置jdk启动参数查看到了MyAnnotationType
注解的中间过程,它其实 是Java运行时生成的动态代理对象$Proxy1,该类就MyAnnotationType
注解(接口)的具体实现类。
动态代理类$Proxy1是如何处理annotation.value()方法的调用?
这里我们需要去补充一下Java中动态代理的知识。这里暂时不过多介绍,下次补上。需要知道的是:动态代理方法的调用最终会传递给绑定的InvocationHandler实例的invoke方法处理。
我们继续看源码
最后
还有Java核心知识点+全套架构师学习资料和视频+一线大厂面试宝典+面试简历模板可以领取+阿里美团网易腾讯小米爱奇艺快手哔哩哔哩面试题+Spring源码合集+Java架构实战电子书+2021年最新大厂面试题。
odError(var2.getMessage());
} catch (ClassNotFoundException var3) {
throw new NoClassDefFoundError(var3.getMessage());
}
}
}
小结
走到这里,我们做一次小结:我们通过 IDEA 工具图和字节码看出我们的MyAnnotationType
注解(接口)其实是继承了Annotation
接口的,我们通过断点的方式和设置jdk启动参数查看到了MyAnnotationType
注解的中间过程,它其实 是Java运行时生成的动态代理对象$Proxy1,该类就MyAnnotationType
注解(接口)的具体实现类。
动态代理类$Proxy1是如何处理annotation.value()方法的调用?
这里我们需要去补充一下Java中动态代理的知识。这里暂时不过多介绍,下次补上。需要知道的是:动态代理方法的调用最终会传递给绑定的InvocationHandler实例的invoke方法处理。
我们继续看源码
最后
还有Java核心知识点+全套架构师学习资料和视频+一线大厂面试宝典+面试简历模板可以领取+阿里美团网易腾讯小米爱奇艺快手哔哩哔哩面试题+Spring源码合集+Java架构实战电子书+2021年最新大厂面试题。
[外链图片转存中…(img-XuCXIhix-1721144350614)]