JAVA 注解 - Annotation 学习
本文目的: 项目开发过程中遇到自定义注解, 想要弄清楚其原理, 但是自己的基础知识不足以支撑自己去探索此问题, 所以先记录问题, 然后补充基础知识, 然后解决其问题. 记录此学习过程.
项目中遇到的注解:
- // 使用注解的地方
- @ServiceScan({"com.sinosoft.lis.pubfun"})
- public class CodeQuerySQL {}
- // 注解类 ServiceScan
- @Target({ElementType.TYPE})
- @Retention(RetentionPolicy.RUNTIME)
- public @interface ServiceScan {
- String[] value() default {};
- }
- // 这个 com.sinosoft.lis.pubfun 包下的类
- @CodeQuery
- public interface CodeQuery_Framework {}
- // 注解类 CodeQuery
- @Target({ElementType.TYPE})
- @Retention(RetentionPolicy.RUNTIME)
- public @interface CodeQuery {
- }
问题描述: 开发中, 我们需要自己新建一个 codequeryframework_nb 类, 这个类是这样使用的, 放在 com.sinosoft.lis.pubfun 包下就行, 然后自定义方法, 就会自动被扫描到, 然后会自动加载定义的接口方法类, 去实现我们的查询下拉的功能. 注解使用正确, 包放在正确的位置就可以使用了, 但是为什么不会和之前的 codequeryframework 冲突? 具体是怎么实现的, 我们组内的成员都没搞明白. 我决定把注解这个知识点往深处挖一挖.
我们的问题:
功能这是怎么实现的.
为什么不会和之前创建的类冲突.
其实就是, 怎么实现的.
学习目的:
能够解释: 为什么这个类是通过注解如何实现的
了解注解是什么, 注解怎么用, 注解的实现原理
注解基础知识补充:
学习过程:
首先, 先找一个资料, 大概的对注解有一定的认识. https://www.runoob.com/w3cnote/java-annotation.html
查询一些博主的博文, 看看他们都提到哪些大的点, 基本上都是一样的, 所以就能定位到自己需要首先了解哪些是需要学习和了解的
对于过程中的疑问, 进行查漏补缺. 为自己解惑
其他人提到的知识点: java5, 元注解, 自定义注解, 注解的实现, 注解的属性, 注解的作用, 在反射中使用注解
注解的本质
- //「java.lang.annotation.Annotation」接口中有这么一句话, 用来描述『注解』.
- The common interface extended by all annotation types
所有的注解类型都继承自这个普通的接口 (Annotation)
我们先随便点开一个 JDK 内的注解, 查看一下是如何定义的
- @Target(ElementType.METHOD)
- @Retention(RetentionPolicy.SOURCE)
- public @interface Override {
- }
这是注解 @Override 的定义, 其实它本质上就是:
- public interface Override extends Annotation{
- }
没错, 注解的本质就是一个继承了 Annotation 接口的接口. 有关这一点, 你可以去反编译任意一个注解类, 你会得到结果的.
为什么要用注解?
这里找到了两位博主的解析, 感觉简单易懂很到位. 先来一个整体上的认识.
在平时不知道我们是否都用过便利贴, 在一张纸上写好几句话, 贴在我们需要的地方. 还有一个情况, 大多数人都叫我们程序猿 (钱多话少死得快), 这也是给我们贴了一个标签. 像这两种情况基本上就是注解. 你可以把这两种情况联想到代码的注解上. 比如我们定义了一个方法, 这个方法要实现加法的运算, 那么我们就可以定义一个 @ADD 标签. 表示这个方法就是实现加法的. 我们程序员一看到这个 @ADD, 就能很容易理解这个方法是干嘛的. 简单而言. 注解就是对于代码中某些鲜活个体的贴上去的一张标签. 简化来讲, 注解如同一张标签. 因为, 如果你之前还未正式的学习过注解, 你就可以把他当成便利贴标签就好了, 这能帮你理解注解的大部分内容.
以前,『xml』是各大框架的青睐者, 它以松耦合的方式完成了框架中几乎所有的配置, 但是随着项目越来越庞大,『xml』的内容也越来越复杂, 维护成本变高. 于是就有人提出来一种标记式高耦合的配置方式,『注解』. 方法上可以进行注解, 类上也可以注解, 字段属性上也可以注解, 反正几乎需要配置的地方都可以进行注解. 关于『注解』和『xml』两种不同的配置模式, 争论了好多年了, 各有各的优劣, 注解可以提供更大的便捷性, 易于维护修改, 但耦合度高, 而 xml 相对于注解则是相反的. 追求低耦合就要抛弃高效率, 追求效率必然会遇到耦合. 本文意不再辨析两者谁优谁劣, 而在于以最简单的语言介绍注解相关的基本内容.
元注解
元注解的作用:
『元注解』是用于修饰注解的注解, 通常用在注解的定义上
- @Target(ElementType.METHOD)
- @Retention(RetentionPolicy.SOURCE)
- public @interface Override {
- }
这是我们 @Override 注解的定义, 你可以看到其中的 @Target,@Retention 两个注解就是我们所谓的『元注解』,『元注解』一般用于指定某个注解生命周期以及作用目标等信息.
那么元注解分别有哪些
// 目前 jdk 官方提供的元注解有 4 个
@Target: 定义注解的作用目标
@Retention: 定义注解的生命周期
@Documented: 定义注解是否应当被包含在 JavaDoc 文档中
@Inherited: 定义是否允许子类继承该注解
元注解详解
@Target - 用于指明被修饰的注解最终可以作用的目标是谁, 也就是指明, 你的注解到底是用来修饰方法的? 修饰类的? 还是用来修饰字段属性的. 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();
- }
我们可以通过以下的方式来为这个 value 传值:
@Target(value = {ElementType.FIELD})
被这个 @Target 注解修饰的注解将只能作用在成员字段上, 不能用于修饰方法或者类. 其中, ElementType 是一个枚举类型, 有以下一些值:
- public enum ElementType {
- /** Class, interface (including annotation type), or enum declaration */
- TYPE, // 允许被修饰的注解作用在类, 接口和枚举上
- /** Field declaration (includes enum constants) */
- FIELD, // 允许作用在属性字段上
- /** Method declaration */
- METHOD, // 允许作用在方法上
- /** Formal parameter declaration */
- PARAMETER, // 允许作用在方法参数上
- /** Constructor declaration */
- CONSTRUCTOR, // 允许作用在构造器上
- /** Local variable declaration */
- LOCAL_VARIABLE, // 允许作用在本地局部变量上
- /** Annotation type declaration */
- ANNOTATION_TYPE, // 允许作用在注解上
- /** Package declaration */
- PACKAGE, // 允许作用在包上
- /**
- * Type parameter declaration
- * 表示该注解能写在类型变量的声明语句中 (如: 泛型声明).
- * @since 1.8
- */
- TYPE_PARAMETER,
- /**
- * Use of a type
- * 表示该注解能写在使用类型的任何语句中.
- * @since 1.8
- */
- TYPE_USE
- }
注意: 上述中文翻译为自己翻译的, 如果有错误, 请自行查阅官方文档
最后从 jdk1.8 添加的两个枚举类型的作用, 是通过搜索网络资料查询得来
类型注解: JDK1.8 之后, 关于元注解 @Target 的参数类型 ElementType 枚举值多了两个:
TYPE_PARAMETER 和 TYPE_USE.
在 Java8 之前, 注解只能是在声明的地方所使用, Java8 开始, 注解可以应用在任何地方.
ElementType.TYPE_PARAMETER 表示该注解能写在类型变量的声明语句中 (如: 泛型声明).
ElementType.TYPE_USE 表示该注解能写在使用类型的任何语句中.
@Retention - 标识这个注解怎么保存, 是只在代码中, 还是编入 class 文件中, 或者是在运行时可以通过反射访问. 它的基本定义如下:
- @Documented
- @Retention(RetentionPolicy.RUNTIME)
- @Target(ElementType.ANNOTATION_TYPE)
- public @interface Retention {
- /**
- * Returns the retention policy.
- * @return the retention policy
- */
- RetentionPolicy value();
- }
同样的, 它也有一个 value 属性:
@Retention(value = RetentionPolicy.RUNTIME
这里的 RetentionPolicy 依然是一个枚举类型, 它有以下几个枚举值可取:
- public enum RetentionPolicy {
- /**
- * Annotations are to be discarded by the compiler.
- */
- SOURCE, // 当前注解编译期可见, 不会写入 class 文件
- /**
- * Annotations are to be recorded in the class file by the compiler
- * but need not be retained by the VM at run time. This is the default
- * behavior.
- */
- CLASS, // 类加载阶段丢弃, 会写入 class 文件
- /**
- * Annotations are to be recorded in the class file by the compiler and
- * retained by the VM at run time, so they may be read reflectively.
- *
- * @see java.lang.reflect.AnnotatedElement
- */
- RUNTIME // 永久保存, 可以反射获取
- }
@Retention 注解指定了被修饰的注解的生命周期, 一种是只能在编译期可见, 编译后会被丢弃, 一种会被编译器编译进 class 文件中, 无论是类或是方法, 乃至字段, 他们都是有属性表的, 而 JAVA 虚拟机也定义了几种注解属性表用于存储注解信息, 但是这种可见性不能带到方法区, 类加载时会予以丢弃, 最后一种则是永久存在的可见性.
如何验证生命周期?
- @Target(ElementType.TYPE)
- @Retention(RetentionPolicy.CLASS)
- public @interface TestAnnotation {
- }
- @Target(ElementType.TYPE)
- @Retention(RetentionPolicy.RUNTIME)
- public @interface TestAnnotation2 {
- }
- @Target(ElementType.TYPE)
- @Retention(RetentionPolicy.SOURCE)
- public @interface TestAnnotation3 {
- }
- @TestAnnotation
- @TestAnnotation2
- @TestAnnotation3
- public class TestJava {
- public static void main(String[] args) throws ClassNotFoundException {
- Class<?> testJava = Class.forName("com.sinosoft.lis.pubfun.TestJava");
- Annotation[] annotations = testJava.getAnnotations();
- for (Annotation annotation : annotations) {
- System.out.println(annotation.annotationType());
- }
- }
你明白我的意思吧.
@Inherited - 标记这个注解是继承于哪个注解类 (默认 注解并没有继承于任何子类).
@Documented - 标记这些注解是否包含在用户文档中.
剩下两种类型的注解我们日常用的不多, 也比较简单, 这里不再详细的进行介绍了, 只需要知道他们各自的作用即可.
@Documented 注解修饰的注解, 当我们执行 JavaDoc 文档打包时会被保存进 doc 文档, 反之将在打包时丢弃.
@Inherited 注解修饰的注解是具有可继承性的, 也就说我们的注解修饰了一个类, 而该类的子类将自动继承父类的该注解.
JAVA 提供的三大内置注解
- #### 除了上述四种元注解外, JDK 还为我们预定义了另外三种注解, 它们是:
- 1. @Override
- 2. @Deprecated
- 3. @SuppressWarnings
JAVA 提供的三大内置注解 - 详解
1. @Override 注解想必是大家很熟悉的了, 标记为方法为重写, 它的定义如下:
- /**
- * Indicates that a method declaration is intended to override a
- * method declaration in a supertype. If a method is annotated with
- * this annotation type compilers are required to generate an error
- * message unless at least one of the following conditions hold:
- *
- * <ul><li>
- * The method does override or implement a method declared in a
- * supertype.
- * </li><li>
- * The method has a signature that is override-equivalent to that of
- * any public method declared in {@linkplain Object}.
- * </li></ul>
- *
- * @author Peter von der Ahé
- * @author Joshua Bloch
- * @jls 9.6.1.4 @Override
- * @since 1.5
- */
- @Target(ElementType.METHOD)
- @Retention(RetentionPolicy.SOURCE)
- public @interface Override {
- }
它没有任何的属性, 所以并不能存储任何其他信息. 它只能作用于方法之上, 编译结束后将被丢弃. 所以你看, 它就是一种典型的『标记式注解』, 仅被编译器可知, 编译器在对 java 文件进行编译成字节码的过程中, 一旦检测到某个方法上被修饰了该注解, 就会去匹对父类中是否具有一个同样方法签名的函数, 如果不是, 自然不能通过编译.
2. @Deprecated : 主要用来标记该 Element 已经过时, 基本定义如下
- /**
- * A program element annotated @Deprecated is one that programmers
- * are discouraged from using, typically because it is dangerous,
- * or because a better alternative exists. Compilers warn when a
- * deprecated program element is used or overridden in non-deprecated code.
- *
- * @author Neal Gafter
- * @since 1.5
- * @jls 9.6.3.6 @Deprecated
- */
- @Documented
- @Retention(RetentionPolicy.RUNTIME)
- @Target(value={CONSTRUCTOR, FIELD, LOCAL_VARIABLE, METHOD, PACKAGE, PARAMETER, TYPE})
- public @interface Deprecated {
- }
依然是一种『标记式注解』, 永久存在, 可以修饰所有的类型, 作用是, 标记当前的类或者方法或者字段等已经不再被推荐使用了, 可能下一次的 JDK 版本就会删除. 当然, 编译器并不会强制要求你做什么, 只是告诉你 JDK 已经不再推荐使用当前的方法或者类了, 建议你使用某个替代者.
3. @SuppressWarnings: 主要用来压制 java 的警告, 它的基本定义如下:
- /**
- * Indicates that the named compiler warnings should be suppressed in the
- * annotated element (and in all program elements contained in the annotated
- * element). Note that the set of warnings suppressed in a given element is
- * a superset of the warnings suppressed in all containing elements. For
- * example, if you annotate a class to suppress one warning and annotate a
- * method to suppress another, both warnings will be suppressed in the method.
- *
- * <p>As a matter of style, programmers should always use this annotation
- * on the most deeply nested element where it is effective. If you want to
- * suppress a warning in a particular method, you should annotate that
- * method rather than its class.
- *
- * @author Josh Bloch
- * @since 1.5
- * @jls 4.8 Raw Types
- * @jls 4.12.2 Variables of Reference Type
- * @jls 5.1.9 Unchecked Conversion
- * @jls 5.5.2 Checked Casts and Unchecked Casts
- * @jls 9.6.3.5 @SuppressWarnings
- */
- @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 代表一个什么意思呢, 这个 value 代表的就是需要被压制的警告类型. 例如:
- public static void main(String[] args) {
- Date date = new Date(2019, 12, 27);
- }
这么一段代码, 程序启动时编译器会报一个警告.
Warning:(8, 21) java: java.util.Date 中的 Date(int,int,int) 已过时
而如果我们不希望程序启动时, 编译器检查代码中过时的方法, 就可以使用 @SuppressWarnings 注解并给它的 value 属性传入一个参数值来压制编译器的检查.
- @SuppressWarning(value = "deprecated")
- public static void main(String[] args) {
- Date date = new Date(2019, 12, 27);
- }
这样你就会发现, 编译器不再检查 main 方法下是否有过时的方法调用, 也就压制了编译器对于这种警告的检查.
当然, JAVA 中还有很多的警告类型, 他们都会对应一个字符串, 通过设置 value 属性的值即可压制对于这一类警告类型的检查.
自定义注解:
自定义注解的语法比较简单, 通过类似以下的语法即可自定义一个注解.
- public @interface InnotationName{
- }
当然, 自定义注解的时候也可以选择性的使用元注解进行修饰, 这样你可以更加具体的指定你的注解的生命周期, 作用范围等信息.
注解的属性 && 注解的使用
注解的属性也叫做成员变量. 注解只有成员变量, 没有方法. 注解的成员变量在注解的定义中以 "无形参的方法" 形式来声明, 其方法名定义了该成员变量的名字, 其返回值定义了该成员变量的类型.
- @Target(ElementType.TYPE)
- @Retention(RetentionPolicy.RUNTIME)
- public @interface TestAnnotation {
- int id();
- String msg();
- }
上面代码定义了 TestAnnotation 这个注解中拥有 id 和 msg 两个属性. 在使用的时候, 我们应该给它们进行赋值.
赋值的方式是在注解的括号内以 value="" 形式, 多个属性之前用 , 隔开.
需要注意的是, 在注解中定义属性时它的类型必须是 8 种基本数据类型外加 类, 接口, 注解及它们的数组.
注解中属性可以有默认值, 默认值需要用 default 关键值指定. 比如:
- @Target(ElementType.TYPE)
- @Retention(RetentionPolicy.RUNTIME)
- public @interface TestAnnotation {
- public int id() default -1;
- public String msg() default "Hi";
- }
TestAnnotation 中 id 属性默认值为 -1,msg 属性默认值为 Hi. 它可以这样应用.
- @TestAnnotation()
- public class Test {
- }
因为有默认值, 所以无需要再在 @TestAnnotation 后面的括号里面进行赋值了, 这一步可以省略.
最后, 还需要注意的一种情况是一个注解没有任何属性. 比如
public @interface Perform {}
那么在应用这个注解的时候, 括号都可以省略.
到目前为止: 我仅仅知道注解是如何定义的, 具体用起来是怎么实现的呢?
比如 @override 是怎么去校验的?? 毕竟点开源码, 看它定义起来挺简单的
- @Target(ElementType.METHOD)
- @Retention(RetentionPolicy.SOURCE)
- public @interface Override {
- }
再比如, 我的类上面添加一个 @Documented 注解, 在生成文档的时候就会自动根据我写的 doc 去生成文档吗? 他是怎么实现的? 通过扫描注解类来完成吗?
再比如, 之前用过的 @bean 注解, 我们在 spring 框架使用时候, 在 java 类上定义之后, 就会在加载的时候扫描加载到容器吗? 具体是怎么实现的呢?
我觉得我需要很明白的理解这写问题. 自己预估可能跟其他人提到的反射有关
那么. 带着这些个疑问, 我们继续向下学习.
注解与反射
上述内容我们介绍了注解使用上的细节, 也简单提到,「注解的本质就是一个继承了 Annotation 接口的接口」, 现在我们就来从虚拟机的层面看看, 注解的本质到底是什么.
注解的使用实例
注解运用的地方太多了, 如:
JUnit 这个是一个测试框架, 典型使用方法如下:
- public class ExampleUnitTest {
- @Test
- public void addition_isCorrect() throws Exception {
- assertEquals(4, 2 + 2);
- }
- }
还有例如 ssm 框架, springboot,springcloud 等运用了大量的注解.
总结
算是对注解有了基本的认知. 谈谈自我总结吧.
如果注解难于理解, 你就把它类同于标签, 标签为了解释事物, 注解为了解释代码.
注解的基本语法, 创建如同接口, 但是多了个 @ 符号.
注解的元注解.
注解的属性.
注解主要给编译器及工具类型的软件用的.
注解的提取需要借助于 Java 的反射技术, 反射比较慢, 所以注解使用时也需要谨慎计较时间成本 (需研究反射,!important).
我之前的问题:
我推论, 我之前的问题, 并不是出现在注解上面了.
我之前的疑问的功能是通过注解, 反射来完成的. 注解只是起到了注解该完成的功能.
接下来需要研究的是: 反射.
通过反射机制会扫描出所有被 @CodeQuery 修饰过的类或者接口并以 bean 对象的形式注入到自己的容器中来统一管理, 根据被 @CodeQuery 修饰的接口或者类, 就可以确定了被 @CodeQuery 修饰过得类都有哪些, 遍历所有 Class 文件, 然后可以用反射中的 Method 类来获取所有被 @SQL 修饰过的方法的名字, 通过方法名字就可以在程序运行时调用对应的接口来执行 sql 语句了
参考文献 :
- https://www.cnblogs.com/yangming1996/p/9295168.html
- https://www.cnblogs.com/love-menglong/p/11165469.html
- https://www.runoob.com/w3cnote/java-annotation.html
- https://blog.csdn.net/tainxiawuti/article/details/99644352
https://www.cnblogs.com/skywang12345/ 《大佬》
扩展作业
Class 源码查阅并了解里面的内置方法, 如里面提供的有查看反射类的注解方法:
- Class<TestJava> testJavaClass = TestJava.class;
- testJavaClass.getAnnotations();
认识到了底层知识的重要性, 感受到了 Java 底层的力量.
反射的理论知识, 实际应用, 应用场景分析.
来源: https://www.cnblogs.com/wobushitiegan/p/12460575.html