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冲突?具体是怎么实现的,我们组内的成员都没搞明白。我决定把注解这个知识点往深处挖一挖。

我们的问题:

  1. 功能这是怎么实现的。
  2. 为什么不会和之前创建的类冲突。
  3. 其实就是,怎么实现的。

学习目的:

  1. 能够解释:为什么这个类是通过注解如何实现的
  2. 了解注解是什么,注解怎么用,注解的实现原理

注解基础知识补充:

学习过程:

  1. 首先,先找一个资料,大概的对注解有一定的认识。 https://www.runoob.com/w3cnote/java-annotation.html
  2. 查询一些博主的博文,看看他们都提到哪些大的点,基本上都是一样的,所以就能定位到自己需要首先了解哪些是需要学习和了解的
  3. 对于过程中的疑问,进行查漏补缺。为自己解惑

其他人提到的知识点: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:定义是否允许子类继承该注解
元注解详解
  1. @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 表示该注解能写在使用类型的任何语句中。
  1. @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());
} }

你明白我的意思吧。

  1. @Inherited - 标记这个注解是继承于哪个注解类(默认 注解并没有继承于任何子类).
  2. @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&eacute;
* @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 {}

那么在应用这个注解的时候,括号都可以省略。


到目前为止:我仅仅知道注解是如何定义的,具体用起来是怎么实现的呢?

  1. 比如@override是怎么去校验的??毕竟点开源码,看它定义起来挺简单的
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
public @interface Override {
}
  1. 再比如,我的类上面添加一个 @Documented 注解,在生成文档的时候就会自动根据我写的 doc去生成文档吗?他是怎么实现的?通过扫描注解类来完成吗?
  2. 再比如,之前用过的@bean 注解,我们在spring框架使用时候,在java类上定义之后,就会在加载的时候扫描加载到容器吗?具体是怎么实现的呢?
  3. 我觉得我需要很明白的理解这写问题。自己预估可能跟其他人提到的反射有关

那么。带着这些个疑问,我们继续向下学习。


注解与反射

上述内容我们介绍了注解使用上的细节,也简单提到,「注解的本质就是一个继承了 Annotation 接口的接口」,现在我们就来从虚拟机的层面看看,注解的本质到底是什么。

注解的使用实例

注解运用的地方太多了,如:

JUnit 这个是一个测试框架,典型使用方法如下:

public class ExampleUnitTest {
@Test
public void addition_isCorrect() throws Exception {
assertEquals(4, 2 + 2);
}
}

还有例如ssm框架,springboot,springcloud等运用了大量的注解。

总结

算是对注解有了基本的认知。谈谈自我总结吧。

  1. 如果注解难于理解,你就把它类同于标签,标签为了解释事物,注解为了解释代码。
  2. 注解的基本语法,创建如同接口,但是多了个 @ 符号。
  3. 注解的元注解。
  4. 注解的属性。
  5. 注解主要给编译器及工具类型的软件用的。
  6. 注解的提取需要借助于 Java 的反射技术,反射比较慢,所以注解使用时也需要谨慎计较时间成本(需研究反射,!important)。

我之前的问题:

  1. 我推论,我之前的问题,并不是出现在注解上面了。
  2. 我之前的疑问的功能是通过注解,反射来完成的.注解只是起到了注解该完成的功能。
  3. 接下来需要研究的是:反射。

通过反射机制会扫描出所有被@CodeQuery 修饰过的类或者接口并以bean对象的形式注入到自己的容器中来统一管理,根据被@CodeQuery修饰的接口或者类,就可以确定了被@CodeQuery修饰过得类都有哪些,遍历所有Class文件,然后可以用反射中的Method类来获取所有被@SQL修饰过的方法的名字,通过方法名字就可以在程序运行时调用对应的接口来执行sql语句了


参考文献 :

扩展作业

  1. Class源码查阅并了解里面的内置方法,如里面提供的有查看反射类的注解方法:
    Class<TestJava> testJavaClass = TestJava.class;
testJavaClass.getAnnotations();
  1. 认识到了底层知识的重要性,感受到了Java底层的力量。
  2. 反射的理论知识,实际应用,应用场景分析。

深入JAVA注解-Annotation(学习过程)的更多相关文章

  1. 深入学习JAVA注解-Annotation(学习过程)

    JAVA注解-Annotation学习 本文目的:项目开发过程中遇到自定义注解,想要弄清楚其原理,但是自己的基础知识不足以支撑自己去探索此问题,所以先记录问题,然后补充基础知识,然后解决其问题.记录此 ...

  2. Java - 注解 (Annotation)

    Java - 注解 (Annotation)   一.基本的 Annotation     > 使用 Annotation 时要在其前面增加 @符号,并把该 Annotation 当成一个修饰符 ...

  3. Java注解Annotation(一)

    Java注解Annotation(一)——简介 这一章首先简单介绍一下注解,下一章会给出一个注解应用的DEMO. 1. 元注解 元注解的作用是负责注解其他的注解. JDK1.5中,定义了4个标准的me ...

  4. Java注解(Annotation)详解

    转: Java注解(Annotation)详解 幻海流心 2018.05.23 15:20 字数 1775 阅读 380评论 0喜欢 1 Java注解(Annotation)详解 1.Annotati ...

  5. Java注解Annotation与自定义注解详解

    Java注解简介 开发中经常使用到注解,在项目中也偶尔会见到过自定义注解,今天就来探讨一下这个注解是什么鬼,以及注解的应用场景和如何自定义注解. 下面列举开发中常见的注解 @Override:用于标识 ...

  6. 秒懂,Java 注解 (Annotation)你可以这样学 - CSDN博客

    https://blog.csdn.net/briblue/article/details/73824058 文章开头先引入一处图片. 这处图片引自老罗的博客.为了避免不必要的麻烦,首先声明我个人比较 ...

  7. java注解(Annotation)解析

    注解(Annotation)在java中应用非常广泛.它既能帮助我们在编码中减少错误,(比如最常见的Override注解),还可以帮助我们减少各种xml文件的配置,比如定义AOP切面用@AspectJ ...

  8. Java注解Annotation学习

    学习注解Annotation的原理,这篇讲的不错:http://blog.csdn.net/lylwo317/article/details/52163304 先自定义一个运行时注解 @Target( ...

  9. java注解Annotation

    扯扯注解的蛋 为什么学习注解?学习注解有什么好处?学完能做什么? 1.能够读懂别人的代码,特别是框架相关的代码 2.让编程更加简洁,代码更加清晰 3.让别人高看你一眼 注解是java1.5引入的 概念 ...

随机推荐

  1. IntelliJ IDEA 2017.3尚硅谷-----修改类头的文档注释信息

    /** @author shkstart @create ${YEAR}-${MONTH}-${DAY} ${TIME} */ ${PACKAGE_NAME} - the name of the ta ...

  2. [lua]紫猫lua教程-命令宝典-L1-01-05. if判断结构

    L1[if]01. 简单的if判断结构 没什么说得 if得基本结构如下 xxx= ) then testlib.traceprint("1-100") ) then testlib ...

  3. python面向对象封装案例2

    封装 封装 是面向对象编程的一大特点 面向对象编程的 第一步 —— 将 属性 和 方法 封装 到一个抽象的 类 中 外界 使用 类 创建 对象,然后 让对象调用方法 对象方法的细节 都被 封装 在 类 ...

  4. bootstrap的pillbox使用

    使用bootstrap的cameo模版,搭建了一个cms系统,使用pillbox做显示的时候,出现点击×失败的问题. 分析了一下pillbox这个控件的使用方法. pillbox的样例在cameo/f ...

  5. 【算法】dsu on tree初探

    dsu on tree的本质是树上的启发式合并,它利用启发式合并的思想,可以将O(N^2)的暴力优化成O(NlogN),用于不带修改的子树信息查询. 具体如何实现呢?对于一个节点,继承它重儿子的信息, ...

  6. .net core 框架调用顺序

    API -> AppSrv -> IRepository -> Repository ->

  7. C语言程序设计100例之(26):二进制数中1的个数

    例26   二进制数中1的个数 问题描述 如果一个正整数m表示成二进制,它的位数为n(不包含前导0),称它为一个n位二进制数.所有的n位二进制数中,1的总个数是多少呢? 例如,3位二进制数总共有4个, ...

  8. js判断json对象是否包含key

    if(json.hasOwnProperty("KEY")){ }

  9. 城市间紧急救援 Dijkstra

    作为一个城市的应急救援队伍的负责人,你有一张特殊的全国地图.在地图上显示有多个分散的城市和一些连接城市的快速道路.每个城市的救援队数量和每一条连接两个城市的快速道路长度都标在地图上.当其他城市有紧急求 ...

  10. PTA点赞狂魔

     点赞狂魔 (25 分) 微博上有个“点赞”功能,你可以为你喜欢的博文点个赞表示支持.每篇博文都有一些刻画其特性的标签,而你点赞的博文的类型,也间接刻画了你的特性.然而有这么一种人,他们会通过给自己看 ...