Java:annotation注解的简单理解和总结
Posted JMW1407
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Java:annotation注解的简单理解和总结相关的知识,希望对你有一定的参考价值。
Java annotation
注解Annotation
1、Annotation的概述
1.1、定义
注解(Annotation),也叫元数据,是一种代码级别的说明。
它是JDK1.5及以后版本引入的一个特性,与类、接口、枚举是在同一个层次。它可以声明在包、类、字段、方法、局部变量、方法参数等的前面,用来对这些元素进行说明,注释。
其实Annotation就是代码里的特殊标记,它们可以在编译,类加载,运行时被读取,并执行相应的处理,以便于其他工具补充信息或者进行部署。
注意:Annotation能被用来为程序元素(类、方法、成员变量等)设置元素据。Annotaion不影响程序代码的执行,无论增加、删除Annotation,代码都始终如一地执行。如果希望让程序中的Annotation起一定的作用,只有通过解析工具或编译工具对Annotation中的信息进行解析和处理。
1.2、Annotation作用分类
一般来说,元数据可以用于创建文档(根据程序元素上的注释创建文档),跟踪代码中的依赖性(可声明方法是重载,依赖父类的方法),执行编译时检查(可声明是否编译期检测),代码分析。
如下:
- 1、编写文档:通过注解生成API文档(@Documented)
- 2、编译检查:通过注解让编译器实现基本的编译检查(@Override)
- 3、代码分析:通过注解对代码进行分析【反射等】(重点)
1.3、Annotation 架构
从中,我们可以看出:
-
(1) 1 个 Annotation 和 1 个 RetentionPolicy关联。可以理解为:每1个Annotation对象,都会有唯一的RetentionPolicy属性。
-
(2) 1 个 Annotation 和 1~n 个 ElementType 关联。可以理解为:对于每 1 个 Annotation 对象,可以有若干个 ElementType 属性。
-
(3) Annotation 有许多实现类,包括:Deprecated, Documented, Inherited, Override 等等。 Annotation 的每一个实现类,都 “和 1 个 RetentionPolicy 关联” 并且 " 和 1~n 个 ElementType 关联"。
下面,我先介绍框架图的左半边(如下图),即 Annotation, RetentionPolicy, ElementType;然后在就 Annotation 的实现类进行举例说明。
Annotation 组成部分
java Annotation 的组成中,有 3 个非常重要的主干类。它们分别是:
Annotation.java
package java.lang.annotation;
public interface Annotation {
boolean equals(Object obj);
int hashCode();
String toString();
Class<? extends Annotation> annotationType();
}
ElementType.java
package java.lang.annotation;
public enum ElementType {
TYPE, /* 类、接口(包括注释类型)或枚举声明 */
FIELD, /* 字段声明(包括枚举常量) */
METHOD, /* 方法声明 */
PARAMETER, /* 参数声明 */
CONSTRUCTOR, /* 构造方法声明 */
LOCAL_VARIABLE, /* 局部变量声明 */
ANNOTATION_TYPE, /* 注释类型声明 */
PACKAGE /* 包声明 */
}
RetentionPolicy.java
package java.lang.annotation;
public enum RetentionPolicy {
SOURCE, /* Annotation信息仅存在于编译器处理期间,编译器处理完之后就没有该Annotation信息了 */
CLASS, /* 编译器将Annotation存储于类对应的.class文件中。默认行为 */
RUNTIME /* 编译器将Annotation存储于class文件中,并且可由JVM读入 */
}
说明:
(1) Annotation 就是个接口。
“每 1 个 Annotation” 都与 “1 个 RetentionPolicy” 关联,并且与 “1~n 个 ElementType” 关联。可以通俗的理解为:每 1 个 Annotation 对象,都会有唯一的 RetentionPolicy 属性;至于 ElementType 属性,则有 1~n 个。
(2) ElementType 是 Enum 枚举类型,它用来指定 Annotation 的类型。
“每 1 个 Annotation” 都与 “1~n 个 ElementType” 关联。当 Annotation 与某个 ElementType 关联时,就意味着:Annotation有了某种用途。例如,若一个 Annotation 对象是 METHOD 类型,则该 Annotation 只能用来修饰方法。
(3) RetentionPolicy 是 Enum 枚举类型,它用来指定 Annotation 的策略。通俗点说,就是不同 RetentionPolicy 类型的 Annotation 的作用域不同。
“每 1 个 Annotation” 都与 “1 个 RetentionPolicy” 关联。
- a) 若 Annotation 的类型为 SOURCE,则意味着:Annotation 仅存在于编译器处理期间,编译器处理完之后,该Annotation 就没用了。 例如," @Override" 标志就是一个Annotation。当它修饰一个方法的时候,就意味着该方法覆盖父类的方法;并且在编译期间会进行语法检查!编译器处理完后,"@Override"就没有任何作用了。
- b) 若 Annotation 的类型为 CLASS,则意味着:编译器将 Annotation 存储于类对应的 .class 文件中,它是Annotation 的默认行为。
- c) 若 Annotation 的类型为 RUNTIME,则意味着:编译器将 Annotation 存储于 class文件中,并且可由JVM读入。
这时,只需要记住"每 1 个 Annotation" 都与 “1 个 RetentionPolicy” 关联,并且与 “1~n 个 ElementType” 关联。
2、Annotation的语法形式
public @interface 注解名称 {}
我们可以定义一个简单的注解,如下:
package com.hanyxx.annotaion;
/**
* @author xxx
* @description: 自定义注解
*/
public @interface bocai {
}
如果通过 javac命令将注解类编译,然后再通过 javap 命令进行反编译,就会发现:
public interface com.hanyxx.annotaion.bocai extends java.lang.annotation.Annotation {}
注解的本质就是:接口
3、Annotation的分类
java内置了6个基本注解和4个元注解
3.1、基本注解
@Override :检测被该注解标注的方法是继承自父类(接口)的
@Deprecated:该注解标注的内容,表示已过时
@SuppressWarnings:压制警告
其中jdk1.7之后引入了另外两个基本注解:
@SafeVarargs : Java7 开始支持,忽略任何使用参数为泛型变量的方法或构造函数调用产生的警告
@FunctionalInterface : Java8 开始支持,标识一个匿名函数或函数式接口。
@Repeatable: Java8 开始支持,标识某注解可以在同一个声明上使用多次
其中@Override、@Deprecated、@SuppressWarnings、@SafeVarargs
和@FunctionalInterface在java.lang包下
而@Repeatable在java.lang.annotation包下
/**
* @Date 2021/11/13 14:04
* @Version 10.21
* @Author XXX
*/
public class AnnotationTest {
public static void main(String[] args) {
@SuppressWarnings("unused")
int a = 10;
}
@Deprecated
public void myPrint(){
System.out.println("@Deprecated修饰的方法表示已过时");
}
@Override
public String toString(){
return "重写的toString()";
}
}
3.2、元注解
元注解(meta-annotation):定义注解的注解
3.2.1、@Target
@Target:用于描述注解能够作用的范围,可取的值存于ElementType枚举类中。
- 当注解类型声明中没有@Target元注解,则默认为可适用所有的程序元素。如果存在指定的@Target元注解,则编译器强制实施相应的使用限制。
@Target(ElementType.TYPE)
public @interface Table {
/**
* 数据表名称注解,默认值为类名称
* @return
*/
public String tableName() default "className";
}
@Target(ElementType.FIELD)
public @interface NoDBColumn {
}
注解Table 可以用于注解类、接口(包括注解类型) 或enum声明,而注解NoDBColumn仅可用于注解类的成员变量。
3.2.2、@Retention
@Retention:指定所修饰的 Annotation 的生命周期:SOURCE\\CLASS(默认行为)\\RUNTIME只有声明为RUNTIME生命周期的注解,才能通过反射获取。
@Rentention 包含一个 RetentionPolicy 类型的成员变量, 使用@Rentention 时必须为该 value 成员变量指定值:
RetentionPolicy.SOURCE
:在源文件中有效(即源文件保留),编译器直接丢弃这种策略的注释RetentionPolicy.CLASS
:在class文件中有效(即class保留) , 当运行 Java 程序时,
JVM不会保留注解。 这是默认值RetentionPolicy.RUNTIME
:在运行时有效(即运行时保留),当运行 Java程序时, JVM会保留注释。程序可以通过反射获取 该注释。
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Column {
public String name() default "fieldName";
public String setFuncName() default "setField";
public String getFuncName() default "getField";
public boolean defaultDBValue() default false;
}
Column注解的的RetentionPolicy的属性值是RUTIME,这样注解处理器可以通过反射,获取到该注解的属性值,从而去做一些运行时的逻辑处理
3.2.3、@Documented
@Documented:用于指定被该元 Annotation 修饰的 Annotation 类将被javadoc 工具提取成文档。默认情况下,javadoc是不包括注解的。
- 定义为Documented的注解必须设置Retention值为RUNTIME。
@author 标明开发该类模块的作者,多个作者之间使用,分割
@version 标明该类模块的版本
@see 参考转向,也就是相关主题
@since 从哪个版本开始增加的
@param 对方法中某参数的说明,如果没有参数就不能写
@return 对方法返回值的说明,如果方法的返回值类型是void就不能写
@exception 对方法可能抛出的异常进行说明 ,如果方法没有用throws显式抛出的异常就不能写
其中@param @return 和 @exception 这三个标记都是只用于方法的。
@param的格式要求:@param 形参名 形参类型 形参说明
@return 的格式要求:@return 返回值类型 返回值说明
@exception的格式要求:@exception 异常类型 异常说明
@param和@exception可以并列多个
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Column {
public String name() default "fieldName";
public String setFuncName() default "setField";
public String getFuncName() default "getField";
public boolean defaultDBValue() default false;
}
3.2.4、@Inherited:
@Inherited:被它修饰的 Annotation 将具有 继承性。如果某个类使用了被@Inherited 修饰的 Annotation, 则其子类将自动具有该注解。
- 比如:如果把标有@Inherited注解的自定义的注解标注在类级别上,子类则可以继承父类类级别的注解。
注意:
@Inherited annotation类型是被标注过的class的子类所继承。类并不从它所实现的接口继承annotation,方法并不从它所重载的方法继承annotation。
当@Inherited annotation类型标注的annotation的Retention是RetentionPolicy.RUNTIME,则反射API增强了这种继承性。如果我们使用java.lang.reflect去查询一个@Inherited annotation类型的annotation时,反射代码检查将展开工作:检查class和其父类,直到发现指定的annotation类型被发现,或者到达类继承结构的顶层。
/**
*
* @author test
*
*/
@Inherited
public @interface Greeting {
public enum FontColor{ BULE,RED,GREEN};
String name();
FontColor fontColor() default FontColor.GREEN;
}
4、自定义annotation
4 .1、定义说明
- 1、定义新的 Annotation 类型使用 @interface 关键字
- 2、自定义注解自动继承了
java.lang.annotation.Annotation 接口
- 3、Annotation 的成员变量在Annotation定义中以无参数方法的形式来声明。其
方法名和返回值定义了该成员的名字和类型
。我们称为配置参数。类型只能是八种基本数据类型、String类型、Class 类型 、enum 类型 、Annotation 类型 、以上所有类型的 数组
。 - 4、可以在定义 Annotation的成员变量时为其指定初始值,
指定成员变量的初始值可使用 default 关键字
- 5、如果只有一个参数成员,建议使用 参数名为
value
- 6、如果定义的注解含有配置参数,那么使用时必须指定参数值,除非它有默认值。格式是“
参数名 = 参数值
”,如果只有一个参数成员,且名称为value,可以省略“value=” - 7、没有成员定义的 Annotation 称为 标记; 包含成员变量的 Annotation 称为元数据 Annotation
注意:自定义注解必须配上注解的信息处理流程才有意义。
package com.hanyxx.annotaion;
import java.lang.annotation.*;
/**
* @author 菠菜饭团
* @description: 自定义注解
*/
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface BoCai {
String name() default "XXX";
ElementType[] value() default ElementType.TYPE ;
String className();
String methodName();
}
package com.hanyxx.annotaion;
import java.lang.reflect.Method;
/**
* @author XXX
* @description: 通过注解的形式创建任意对象,并且执行任意方法
*/
@BoCai(className="com.hanyxx.annotaion.PlayGame",methodName = "play")
public class AnnotationTest {
public static void main(String[] args) throws Exception {
/**
* <A extends Annotation> A getAnnotation(Class<A> annotationClass) :如果存在该元素的指定类型的注解,则返回这些注解,否则返回 null。
* Annotation[] getAnnotations() : 返回此元素上存在的所有注解。
*/
//1.获取类字节码对象
Class<AnnotationTest> annotationClass = AnnotationTest.class;
//2.获取该类的注解对象(本质是在内存中生成该注解接口的子类实现对象)
BoCai boCai = annotationClass.getAnnotation(BoCai.class);
//3.获取传入的类名和方法名
String className = boCai.className();
System.out.println("获取类名:"+className);
System.out.println("===========================");
String methodName = boCai.methodName();
System.out.println("获取方法名:"+methodName);
System.out.println("===========================");
//4.加载类进内存,并获取方法对象
Class<?> Cls = Class.forName(className);
Method method = Cls.getMethod(methodName);
//5.创建对象实例并调用方法
Object o = Cls.newInstance();
method.invoke(o);
}
}
4.2、实例
MyAnnotation
package com.atguigu.java1;
import java.lang.annotation.*;
import static java.lang.annotation.ElementType.*;
@Inherited
@Repeatable(MyAnnotations.class)
@Retention(RetentionPolicy.RUNTIME)
@Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE,TYPE_PARAMETER,TYPE_USE})
public @interface MyAnnotation {
String value() default "hello";
}
MyAnnotations
package com.atguigu.java1;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import static java.lang.annotation.ElementType.*;
@Inherited
@Retention(RetentionPolicy.RUNTIME)
@Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE})
public @interface MyAnnotations {
MyAnnotation[] value();
}
AnnotationTest
package com.atguigu.java1;
import org.junit.Test;
import java.lang.annotation.Annotation;
import java.util.ArrayList;
import java.util.Date;
/**
* 注解的使用
*
* 1. 理解Annotation:
* ① jdk 5.0 新增的功能
*
* ② Annotation 其实就是代码里的特殊标记, 这些标记可以在编译, 类加载, 运行时被读取, 并执行相应的处理。通过使用 Annotation,
* 程序员可以在不改变原有逻辑的情况下, 在源文件中嵌入一些补充信息。
*
* ③在JavaSE中,注解的使用目的比较简单,例如标记过时的功能,忽略警告等。在JavaEE/android
* 中注解占据了更重要的角色,例如用来配置应用程序的任何切面,代替JavaEE旧版中所遗留的繁冗
* 代码和XML配置等。
*
* 2. Annocation的使用示例
* 示例一:生成文档相关的注解
* 示例二:在编译时进行格式检查(JDK内置的三个基本注解)
@Override: 限定重写父类方法, 该注解只能用于方法
@Deprecated: 用于表示所修饰的元素(类, 方法等)已过时。通常是因为所修饰的结构危险或存在更好的选择
@SuppressWarnings: 抑制编译器警告
* 示例三:跟踪代码依赖性,实现替代配置文件功能
*
* 3. 如何自定义注解:参照@SuppressWarnings定义
* ① 注解声明为:@interface
* ② 内部定义成员,通常使用value表示
* ③ 可以指定成员的默认值,使用default定义
* ④ 如果自定义注解没有成员,表明是一个标识作用。
如果注解有成员,在使用注解时,需要指明成员的值。
自定义注解必须配上注解的信息处理流程(使用反射)才有意义。
自定义注解通过都会指明两个元注解:Retention、Target
4. jdk 提供的4种元注解
元注解:对现有的注解进行解释说明的注解
Retention:指定所修饰的 Annotation 的生命周期:SOURCE\\CLASS(默认行为)\\RUNTIME
只有声明为RUNTIME生命周期的注解,才能通过反射获取。
Target:用于指定被修饰的 Annotation 能用于修饰哪些程序元素
*******出现的频率较低*******
Documented:表示所修饰的注解在被javadoc解析时,保留下来。
Inherited:被它修饰的 Annotation 将具有继承性。
5.通过反射获取注解信息 ---到反射内容时系统讲解
6. jdk 8 中注解的新特性:可重复注解、类型注解
6.1 可重复注解:① 在MyAnnotation上声明@Repeatable,成员值为MyAnnotations.class
② MyAnnotation的Target和Retention等元注解与MyAnnotations相同。
6.2 类型注解:
ElementType.TYPE_PARAMETER 表示该注解能写在类型变量的声明语句中(如:泛型声明)。
ElementType.TYPE_USE 表示该注解能写在使用类型的任何语句中。
*
*/
public class AnnotationTest {
public static void main(String[] args) {
Person p = new Student();
p.walk();
Date date = new Date(2020, 10, 11);
System.out.println(date);
@SuppressWarnings("unused")
int num = 10;
// System.out.println(num);
@SuppressWarnings({ "unused", "rawtypes" })
ArrayList list = new ArrayList();
}
@Test
public void testGetAnnotation(){
Class clazz = Student.class;
Annotation[] annotations = clazz.getAnnotations();
for(int i = 0;i < annotations.length;i++){
System.out.println(annotations[i]);
}
}
}
//jdk 8之前的写法:
//@MyAnnotations({@MyAnnotation(value="hi"),@MyAnnotation(value="hi")})
@MyAnnotation(value="hi")
@MyAnnotation(value="abc")
class Person{
private String name;
private int age;
public Person() {
}
@MyAnnotation
public Person(String name, int age) {
this.name = name;
this.age = age;
}
@MyAnnotation
public void walk(){
System.out.println("人走路");
}
public void eat(){
System.out.println("人吃饭");
}
}
interface Info{
void show();
}
class Student extends Person implements Info{
@Override
public void walk() {
System.out.println("学生走路");
}
public void show() {
}
}
class Generic<@MyAnnotation T>{
public void show() throws @MyAnnotation RuntimeException{
ArrayList<@MyAnnotation String> list = new ArrayList<>();
int num = (@MyAnnotation int) 10L;
}
}
参考
1、https://www.cnblogs.com/Tanyboye/p/9138412.html
2、Java 注解(Annotation)
以上是关于Java:annotation注解的简单理解和总结的主要内容,如果未能解决你的问题,请参考以下文章
Java反射学习总结五(Annotation(注解)-基础篇)
[1] 注解(Annotation)-- 深入理解Java:注解(Annotation)基本概念