草庐IT

看到最后,你才发现Java中注解原来是这么做到的!

JAVA旭阳 2023-04-13 原文

​概述

注解想必大家在项目中经常使用,比如Spring框架中常用的一些注解:@Controller、@Service、@RequestMapping等等,它是JDK1.5及以后版本引入的一个特性。它可以声明在包、类、字段、方法、局部变量、方法参数等的前面,用来对这些元素进行说明,注释。那么你知道JDK什么是元注解吗?注解有哪些分类吗?以及注解在Java中最本质究竟是什么东西,是如何实现的呢?

注解的分类

根据注解的使用场景,主要分为三类,元注解、内置注解和自定义注解。

元注解

用于定义注解的注解,通常用于注解的定义上,标明该注解的使用范围、生效范围等。简而言之,元注解是用来修饰注解的。

@Retention

指定注解信息保留到哪个阶段,分别为源代码阶段、编译Class阶段、运行阶段。

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Retention {
/**
* Returns the retention policy.
* @return the retention policy
*/
RetentionPolicy value();
}
  • SOURCE: 保留在源代码java中,被编译器丢弃,也就是说在class文件中不包含注解信息,通常用来标记源码,引起大家的注意,比如自定义一个注解例如@ThreadSafe,用来标识一个类时线程安全的。
  • CLASS: 编译后的class文件中包含注解信息,但是会被jvm丢弃
  • RUNTIME: 注解信息在运行期(JVM)保留(.class也有),可以通过反射机制读取注解的信息

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

具体可选的枚举如下:

public enum ElementType {
/** 适用范围:类、接口、注解类型,枚举类型enum */
TYPE,
/** 作用于类属性 (includes enum constants) */
FIELD,
/** 作用于方法 */
METHOD,
/** 作用于参数声明 */
PARAMETER,
/** 作用于构造函数声明 */
CONSTRUCTOR,
/** 作用于局部变量声明 */
LOCAL_VARIABLE,
/** 作用于注解声明 */
ANNOTATION_TYPE,
/** 作用于包声明 */
PACKAGE,
/** 作用于类型参数(泛型参数)声明 */
TYPE_PARAMETER,
/** 作用于使用类型的任意语句(不包括class) */
TYPE_USE
}

@Inherited

加上该注解的注解,表示可以被标注的类子类继承,比如A上标记了带有@Inherited的注解,那么类B继承了A, 那么B也会有这个注解,默认情况下注解是不支持继承的。

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Inherited {
}

@Document

将此注解包含在 javadoc 中 ,它代表着此注解会被javadoc工具提取成文档。

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Documented {
}

@Repeatable

1.8中加入的元注解,用来标记是否可以重复标记。

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Repeatable {
/**
* Indicates the <em>containing annotation type</em> for the
* repeatable annotation type.
* @return the containing annotation type
*/
Class<? extends Annotation> value();
}

内置注解

java提供了一些内置注解,可以配合编译器来检查代码的正确性, 我们可以关注他们的元注解。

@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

用来关闭编译器输出的警告信息

@Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE})
@Retention(RetentionPolicy.SOURCE)
public @interface SuppressWarnings {
String[] value();
}

@FunctionalInterface

java8中引入,标记是一个函数式接口,也就是说有且只有一个抽象方法的接口

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface FunctionalInterface {}

自定义注解

注解遵循的格式一般如下:

//元注解部分 xxxx
@Retention(xxxx)
@Target(xxxx)
public @interface 注解名 {
返回值 属性名() 默认值;
返回值 属性名() 默认值;
}
  • 返回值支持的类型如下:java的8种基础类型(不支持包装类型)、String、Class、Enum、Annotation、以及上面类型的数组。
  • 默认值可选,非必有。

举个项目中自定义的栗子:

@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
public @interface DataAuthorize {
/**
* 资产ID
* @return
*/
String assetId();

/**
* 资产类型
* @return
*/
String assetType();

/**
* 权限代码
* @return
*/
String authCode() default "";

/**
* 使用的类型
* @return
*/
Class[] useType();
}

使用反射操作注解

大部分情况下,我们的项目或者开源框架中都定义了大量的注解,而且都是@Retention(RetentionPolicy.RUNTIME)运行时阶段,我们可以通过反射获取注解中的信息,所以整体遵循下面的一个范式。

  1. 自定义注解
  2. 扫描注解
  3. 通过反射获取注解的信息,执行相应的逻辑。

下面我们重点使用下如何用反射来获取注解的信息。

  1. 定义target是注解的注解
@Inherited
@Retention( value = RetentionPolicy.RUNTIME)
@Target(value = {ElementType.ANNOTATION_TYPE})
public @interface AnnoTest {

String value() default "anno";

}
  1. 定义一个几乎全量信息的注解
@AnnoTest("alvinAnno")
@Inherited
@Retention( value = RetentionPolicy.RUNTIME)
@Target(value = {ElementType.TYPE_USE,ElementType.PACKAGE,ElementType.FIELD,
ElementType.TYPE_PARAMETER,ElementType.CONSTRUCTOR,ElementType.LOCAL_VARIABLE})
@Documented
public @interface FullAnnoTest {

String value() default "FullAnnoTest";

}
  1. 定义测试类和反射代码
@FullAnnoTest("package")
package com.alvin.java.core.anno;

public class ParentObj {
}



@FullAnnoTest("testAnnoReflect")
public class TestAnnoReflect<@FullAnnoTest("parameter") T > extends @FullAnnoTest("parent")ParentObj {

@FullAnnoTest("constructor")
TestAnnoReflect() {
}

//注解字段域
private @FullAnnoTest("name") String name;
//注解泛型字段域
private @FullAnnoTest("value") T value;
//注解通配符
private @FullAnnoTest("list") List<@FullAnnoTest("generic") ?> list;
//注解方法
@FullAnnoTest("method") //注解方法参数
public String hello(@FullAnnoTest("methodParameter") String name)
throws @FullAnnoTest("Exception") Exception { // 注解抛出异常
//注解局部变量,现在运行时暂时无法获取(忽略)
@FullAnnoTest("result") String result;
result = "siting";
System.out.println(name);
return result;
}

public static void main(String[] args) throws Exception {

TestAnnoReflect<String> TestAnnoReflect = new TestAnnoReflect<> ();
Class<TestAnnoReflect<Object>> clazz = (Class<TestAnnoReflect<Object>>) TestAnnoReflect.getClass();
//class的注解
Annotation[] annotations = clazz.getAnnotations();
FullAnnoTest testTmp = (FullAnnoTest) annotations[0];
System.out.println("修饰TestAnnoReflect.class注解value: "+testTmp.value());
//构造器的注解
Constructor<TestAnnoReflect<Object>> constructor = (Constructor<TestAnnoReflect<Object>>) clazz.getDeclaredConstructors()[0];
testTmp = constructor.getAnnotation(FullAnnoTest.class);
System.out.println("修饰构造器的注解value: "+testTmp.value());
//继承父类的注解
AnnotatedType annotatedType = clazz.getAnnotatedSuperclass();
testTmp = annotatedType.getAnnotation(FullAnnoTest.class);
System.out.println("修饰继承父类的注解value: "+testTmp.value());
//注解的注解
AnnoTest AnnoTest = testTmp.annotationType().getAnnotation(AnnoTest.class);
System.out.println("修饰注解的注解AnnoTest-value: "+AnnoTest.value());
//泛型参数 T 的注解
TypeVariable<Class<TestAnnoReflect<Object>>> variable = clazz.getTypeParameters()[0];
testTmp = variable.getAnnotation(FullAnnoTest.class);
System.out.println("修饰泛型参数T注解value: "+testTmp.value());
//普通字段域 的注解
Field[] fields = clazz.getDeclaredFields();
Field nameField = fields[0];
testTmp = nameField.getAnnotation(FullAnnoTest.class);
System.out.println("修饰普通字段域name注解value: "+testTmp.value());
//泛型字段域 的注解
Field valueField = fields[1];
testTmp = valueField.getAnnotation(FullAnnoTest.class);
System.out.println("修饰泛型字段T注解value: "+testTmp.value());
//通配符字段域 的注解
Field listField = fields[2];
AnnotatedParameterizedType annotatedPType = (AnnotatedParameterizedType)listField.getAnnotatedType();
testTmp = annotatedPType.getAnnotation(FullAnnoTest.class);
System.out.println("修饰泛型注解value: "+testTmp.value());
//通配符注解 的注解
AnnotatedType[] annotatedTypes = annotatedPType.getAnnotatedActualTypeArguments();
AnnotatedWildcardType annotatedWildcardType = (AnnotatedWildcardType) annotatedTypes[0];
testTmp = annotatedWildcardType.getAnnotation(FullAnnoTest.class);
System.out.println("修饰通配符注解value: "+testTmp.value());
//方法的注解
Method method = clazz.getDeclaredMethod("hello", String.class);
annotatedType = method.getAnnotatedReturnType();
testTmp = annotatedType.getAnnotation(FullAnnoTest.class);
System.out.println("修饰方法的注解value: "+testTmp.value());
//异常的注解
annotatedTypes = method.getAnnotatedExceptionTypes();
testTmp = annotatedTypes[0].getAnnotation(FullAnnoTest.class);
System.out.println("修饰方法抛出错误的注解value: "+testTmp.value());
//方法参数的注解
annotatedTypes = method.getAnnotatedParameterTypes();
testTmp = annotatedTypes[0].getAnnotation(FullAnnoTest.class);
System.out.println("修饰方法参数注解value: "+testTmp.value());
//包的注解
Package p = Package.getPackage("com.alvin.java.core.anno");
testTmp = p.getAnnotation(FullAnnoTest.class);
System.out.println("修饰package注解value: "+testTmp.value());
TestAnnoReflect.hello("hello");
}

}
  1. 查看对应的执行结果
修饰TestAnnoReflect.class注解value: testAnnoReflect
修饰构造器的注解value: constructor
修饰继承父类的注解value: parent
修饰注解的注解AnnoTest-value: alvinAnno
修饰泛型参数T注解value: parameter
修饰普通字段域name注解value: name
修饰泛型字段T注解value: value
修饰泛型注解value: list
修饰通配符注解value: generic
修饰方法的注解value: method
修饰方法抛出错误的注解value: Exception
修饰方法参数注解value: methodParameter
修饰package注解value: package
hello

注解的本质和底层实现

大家有没有想过注解的本质是什么?

我们先通过反编译查看注解生成的字节码,可以通过javap -v FullAnnoTest.class查看如下:

可以看到,我们的注解是继承自Annotation接口。

public interface Annotation {
boolean equals(Object obj);

int hashCode();

String toString();

/**
* Returns the annotation type of this annotation.
* @return the annotation type of this annotation
*/
Class<? extends Annotation> annotationType();
}

所以注解相当于一个语法糖一样,可以方便我们使用,本质上它是继承自Annotation的一个接口。

那大家有没有想过它的实现类在哪里?比如下面的代码,获取到注解,按照上面的解释,它是一个接口,那调用value()方法时,它具体调用的哪个实现类呢?我们并没有写实现类啊.....

答案当然就是动态代理生成的实现类。

AnnoTest annoTest = testTmp.annotationType().getAnnotation(AnnoTest.class);
System.out.println("修饰注解的注解AnnoTest-value: "+annoTest.value());

我们可以在启动参数添加如下命令可以查看生成的代理类:-Dsun.misc.ProxyGenerator.saveGeneratedFiles=true

执行后,生成代理类如下,

代理大致的代码如下:

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

public $Proxy2(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 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 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("com.alvin.java.core.anno.FullAnnoTest").getMethod("annotationType");
m0 = Class.forName("java.lang.Object").getMethod("hashCode");
m3 = Class.forName("com.alvin.java.core.anno.FullAnnoTest").getMethod("value");
} catch (NoSuchMethodException var2) {
throw new NoSuchMethodError(var2.getMessage());
} catch (ClassNotFoundException var3) {
throw new NoClassDefFoundError(var3.getMessage());
}
}
}

我们看value()​方法,这里调用了super.h对象,也就是InvocationHandler​对象,而我们注解用的是AnnotationInvocationHandler这个子类,我们在invoke方法中打个断点,就明白了~~

有关看到最后,你才发现Java中注解原来是这么做到的!的更多相关文章

  1. java - 等价于 Java 中的 Ruby Hash - 2

    我真的很习惯使用Ruby编写以下代码:my_hash={}my_hash['test']=1Java中对应的数据结构是什么? 最佳答案 HashMapmap=newHashMap();map.put("test",1);我假设? 关于java-等价于Java中的RubyHash,我们在StackOverflow上找到一个类似的问题: https://stackoverflow.com/questions/22737685/

  2. java - 从 JRuby 调用 Java 类的问题 - 2

    我正在尝试使用boilerpipe来自JRuby。我看过guide从JRuby调用Java,并成功地将它与另一个Java包一起使用,但无法弄清楚为什么同样的东西不能用于boilerpipe。我正在尝试基本上从JRuby中执行与此Java等效的操作:URLurl=newURL("http://www.example.com/some-location/index.html");Stringtext=ArticleExtractor.INSTANCE.getText(url);在JRuby中试过这个:require'java'url=java.net.URL.new("http://www

  3. java - 我的模型类或其他类中应该有逻辑吗 - 2

    我只想对我一直在思考的这个问题有其他意见,例如我有classuser_controller和classuserclassUserattr_accessor:name,:usernameendclassUserController//dosomethingaboutanythingaboutusersend问题是我的User类中是否应该有逻辑user=User.newuser.do_something(user1)oritshouldbeuser_controller=UserController.newuser_controller.do_something(user1,user2)我

  4. java - 什么相当于 ruby​​ 的 rack 或 python 的 Java wsgi? - 2

    什么是ruby​​的rack或python的Java的wsgi?还有一个路由库。 最佳答案 来自Python标准PEP333:Bycontrast,althoughJavahasjustasmanywebapplicationframeworksavailable,Java's"servlet"APImakesitpossibleforapplicationswrittenwithanyJavawebapplicationframeworktoruninanywebserverthatsupportstheservletAPI.ht

  5. Observability:从零开始创建 Java 微服务并监控它 (二) - 2

    这篇文章是继上一篇文章“Observability:从零开始创建Java微服务并监控它(一)”的续篇。在上一篇文章中,我们讲述了如何创建一个Javaweb应用,并使用Filebeat来收集应用所生成的日志。在今天的文章中,我来详述如何收集应用的指标,使用APM来监控应用并监督web服务的在线情况。源码可以在地址 https://github.com/liu-xiao-guo/java_observability 进行下载。摄入指标指标被视为可以随时更改的时间点值。当前请求的数量可以改变任何毫秒。你可能有1000个请求的峰值,然后一切都回到一个请求。这也意味着这些指标可能不准确,你还想提取最小/

  6. 【Java 面试合集】HashMap中为什么引入红黑树,而不是AVL树呢 - 2

    HashMap中为什么引入红黑树,而不是AVL树呢1.概述开始学习这个知识点之前我们需要知道,在JDK1.8以及之前,针对HashMap有什么不同。JDK1.7的时候,HashMap的底层实现是数组+链表JDK1.8的时候,HashMap的底层实现是数组+链表+红黑树我们要思考一个问题,为什么要从链表转为红黑树呢。首先先让我们了解下链表有什么不好???2.链表上述的截图其实就是链表的结构,我们来看下链表的增删改查的时间复杂度增:因为链表不是线性结构,所以每次添加的时候,只需要移动一个节点,所以可以理解为复杂度是N(1)删:算法时间复杂度跟增保持一致查:既然是非线性结构,所以查询某一个节点的时候

  7. 【Java入门】使用Java实现文件夹的遍历 - 2

    遍历文件夹我们通常是使用递归进行操作,这种方式比较简单,也比较容易理解。本文为大家介绍另一种不使用递归的方式,由于没有使用递归,只用到了循环和集合,所以效率更高一些!一、使用递归遍历文件夹整体思路1、使用File封装初始目录,2、打印这个目录3、获取这个目录下所有的子文件和子目录的数组。4、遍历这个数组,取出每个File对象4-1、如果File是否是一个文件,打印4-2、否则就是一个目录,递归调用代码实现publicclassSearchFile{publicstaticvoidmain(String[]args){//初始目录Filedir=newFile("d:/Dev");Datebeg

  8. java - 为什么 ruby​​ modulo 与 java/other lang 不同? - 2

    我基本上来自Java背景并且努力理解Ruby中的模运算。(5%3)(-5%3)(5%-3)(-5%-3)Java中的上述操作产生,2个-22个-2但在Ruby中,相同的表达式会产生21个-1-2.Ruby在逻辑上有多擅长这个?模块操作在Ruby中是如何实现的?如果将同一个操作定义为一个web服务,两个服务如何匹配逻辑。 最佳答案 在Java中,模运算的结果与被除数的符号相同。在Ruby中,它与除数的符号相同。remainder()在Ruby中与被除数的符号相同。您可能还想引用modulooperation.

  9. java - Ruby 相当于 Java 的 Collections.unmodifiableList 和 Collections.unmodifiableMap - 2

    Java的Collections.unmodifiableList和Collections.unmodifiableMap在Ruby标准API中是否有等价物? 最佳答案 使用freeze应用程序接口(interface):Preventsfurthermodificationstoobj.ARuntimeErrorwillberaisedifmodificationisattempted.Thereisnowaytounfreezeafrozenobject.SeealsoObject#frozen?.Thismethodretur

  10. ruby - Hanami link_to 助手只呈现最后一个元素 - 2

    我是HanamiWorld的新人。我已经写了这段代码:moduleWeb::Views::HomeclassIndexincludeWeb::ViewincludeHanami::Helpers::HtmlHelperdeftitlehtml.headerdoh1'Testsearchengine',id:'title'hrdiv(id:'test')dolink_to('Home',"/",class:'mnu_orizontal')link_to('About',"/",class:'mnu_orizontal')endendendendend我在模板上调用了title方法。htm

随机推荐