共计 3773 个字符,预计需要花费 10 分钟才能阅读完成。
看源码的时候, 看到注解,还是有些不理解的地方。于是打算从头学习注解
注解的定位
首先要回答的的第一个问题是注解的定位是什么? 带着这个问题我去翻了官方写的指导书《The Java™ Tutorials》,在注释这一节的开始,有如下文字:
Annotations, a form of metadata, provide data about a program that is not part of the program itself. Annotations have no direct effect on the operation of the code they annotate. —《The Java™ Tutorials》
注解是一种元数据,提供关于程序的信息,但并不是程序的有机组成,注解并不对被它修饰的代码直接起作用。
那么有啥用处呢?
《The Java™ Tutorials》:
-
Information for the compiler — Annotations can be used by the compiler to detect errors or suppress warnings.
- 给程序员提供一些信息,比如说注释可以给程序提示过时警告或者显示一些错误 (@SuppressWarnings 和 @Override 请出来说话)
-
Compile-time and deployment-time processing — Software tools can process annotation information to generate code, XML files, and so forth.
- 在编译时和部署时被处理,一些工具可以根据注解提供的信息来产生代码,XML 文件等等.(@Data 注解出来挨打),@Data 注解是 Lombok 插件锁提供,适用于这样的场景: 你写了一些属性,但是你不想写 get 和 set 方法,你就只需要在类上打上 @Data 注解,Lombok 就可以自动帮你产生 get 和 set 方法、无参构造。
-
Runtime processing — Some annotations are available to be examined at runtime.
- 作用于运行时,一些注解能够在运行时被检测到。(@Controller,@Autowired 出来挨打)
@Controller 是 SpringMVC 提供的注解,一个类打上 @Controller 注解后,成为一个控制器。
那该怎么用呢?
如下所示我定义了一个注解:
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Inherited
public @interface SimpleAnnotation {String name() default "";
}
从 @interface 来看,注解更像是一个接口 (事实上它就是一个接口,后文会这一点进行详细阐释,现在我们先关注怎么用),@Rentention、@Target、@Document、@Inherited 是 Java 提供的元注解,怎么理解为元注解呢?就像是建筑师的建房子用的砖头一样,这些元注解就是 Java 提供给我们的砖头。
我们将注解中的 name 理解为属性,String 限制了赋值的类型,default 意味你没给注解中的属性赋值的时候,此时该属性为默认值。
-
@Rentention 定义注解在哪里可用,这个属性在赋值是必须为 RetentionPolicy 类型,RetentionPolicy 是一个枚举类型, 有三个值
- SOURCE(源代码)
- CLASS(字节码可以理解为编译时,但是在运行时不会被保留)
- RUNTIME (运行时)
-
@Target 定义注解可以加在哪里 (例如方法上、字段上),这个属性所赋的值必须为 ElementType,ElementType 也是一个枚举类型。
- TYPE(类、接口、注解、枚举声明)
- FIELD(字段声明,包括枚举常量中)
- METHOD(方法上)
- PARAMETER(参数上)
- CONSTRUCTOR(构造函数上)
- LOCAL_VARIABLE(局部变量)
-
ANNOTATION_TYPE
- Annotation type declaration 这是 JDK 的注释,我看的有点懵。单独拿出来说
- PACKAGE(包声明上)
- TYPE_PARAMETER(1.8 引入) : 类型变量,泛型声明
- TYPE_USE(1.8 引入): 声明类型的地方
当注解未指定 Target 值时,此注解可以使用任何元素之上。
- @Documented
该注解将会,Javadoc 工具会将此注解标记元素的注解信息包含在 javadoc 中。默认, 注解信息不会包含在 Javadoc 中。
- @ Inherited
如果一个注解被 @Inherited 修饰,那么被该注解修饰的类的子类也能获得该注解。 - @ Repeatable
该注解解决了, 可以让同一个位置存放多个相同的注解。但是它还需要一个注解,充当容器。
简单的例子:
@Repeatable(SimpleAnnotation.class)
public @interface SimpleAnnotationDemo01 {String value();
}
这个 SimpleAnnotation 也是一个注解,其中的属性是一个 SimpleAnnotationDemo01 的数组,相当于 SimpleAnnotationDemo01 存储了 SimpleAnnotation 在一个地方多次使用的值。
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Inherited
public @interface SimpleAnnotation {SimpleAnnotationDemo01[] value() ;}
关于 ANNOTATION_TYPE、TYPE_PARAMETER
- ANNOTATION_TYPE: 该类型标识该注解只能用于修饰注解, 也就是说只能用于注释上,也就是说该注解成为了元注解
- TYPE_PARAMETER 类型参数,声明泛型时: 如下图所示:
public class TestAnnotation {
private int name;
public <@SimpleAnnotation(name="T type") T> T getName(T t) {return t;}
}
注解究竟是什么?
带着这个疑问,我将注解打在类上,并执行编译后,通过 java p -c(不知道这个命令,请去看我以前的文章: 两个 java 命令和一道看起来比较简单的面试题) 来看其反汇编之后的字节码,
示例代码:
@SimpleAnnotationDemo01(value = "01")
@SimpleAnnotationDemo01(value = "01")
public class TestAnnotation {public static void main(String[] args) {System.out.println("hello world");
}
}
反汇编后的字节码:
public interface com.cxk.annotation.SimpleAnnotationDemo01 extends java.lang.annotation.Annotation {public abstract java.lang.String value();
}
所以这个注解本质上还是接口喽。那这个方法怎么理解啊? 我传进去值相当于调方法喽,可这是一个接口啊!
那你为啥不弄成属性啊,费这个劲,弄成方法,有些理解不了啊。
所以是动态代理喽,本篇的主题注解,有关动态代理,会专门有一篇文章来进行介绍。注解本质上是一种接口,在运行时,加入你的注解是能够保存到运行时,那么 JVM 就会帮你创建你的注解的实例。
那么你有什么证据呢?
首先设置一个 JVM 的虚拟机参数中可以帮助我们捕捉 JDK 动态代理类:
-Dsun.misc.ProxyGenerator.saveGeneratedFiles=true
代码如下:
@SimpleAnnotationDemo01(value = "01")
@SimpleAnnotationDemo01(value = "01")
public class TestAnnotation {public static void main(String[] args) throws NoSuchMethodException {
Class<TestAnnotation> clzz = TestAnnotation.class;
System.out.println(clzz.getAnnotations());
}
}
InvocationHandler 是一个接口,那么它的实例是哪一个呢?我们打断点来看下.
代码如下:
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@Documented
@Inherited
public @interface SimpleDemo01 {String name() default "";
}
原来是 AnnotationInvocationHandler 的实例啊.
参考书籍和博客如下:
- JAVA 注解的基本原理
- Java 注解——Repeatable
- 《Java Tutorial》
- 《Java 编程思想》