注解

注解

何谓注解

Annotation (注解) 是 Java5 开始引入的新特性,可以看作是一种特殊的注释,主要用于修饰类、方法或者变量,提供某些信息供程序在编译或者运行时使用。

注解本质是一个继承了Annotation 的特殊接口:

1
2
3
4
5
6
7
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
public @interface Override {
}

public interface Override extends Annotation{
}

作用分类

编写文档

通过代码里标识的注解生成文档【生成文档doc文档】
创建java文件 Annotation.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
/**
* 注解javadoc演示
*
* @author pale
* @version 1.0
* @since 1.5
*/
public class AnnoDemo1 {
/**
* 计算两数和
* @param a 整数
* @param b 整数
* @return 两数和
*/
public int add(int a,int b){
return a + b;
}
}

命令行输出javadoc AnnoDemo1.java

image-20240309174037362

生成了doc文档

image-20240309174113258

编译期直接扫描

编译器在编译 Java 代码的时候扫描对应的注解并处理,比如某个方法使用@Override 注解,编译器在编译的时候就会检测当前的方法是否重写了父类对应的方法。

JDK中预定义的一些注解

@Override :检测被该注解标注的方法是否是继承自父类(接口)的

@Deprecated:该注解标注的内容,表示已过时

@SuppressWarnings:压制警告,一般传递参数all @SuppressWarnings(“all”)

运行期通过反射处理

像框架中自带的注解(比如 Spring 框架的 @Value@Component)都是通过反射来进行处理的。

自定义注解

1
2
3
public @interface 注解名称{
属性列表;
}

注解的属性

注解可以包含一些属性,这些属性是注解的一部分,用于提供更多的信息或配置。

注解的属性定义类似于接口的方法,可以包含返回类型和参数列表

  • 属性可以是基本数据类型、枚举、字符串、Class类型、注解类型,以及这些类型的数组形式。

  • 属性的返回值

    • 使用注解时,可以不进行属性的赋值
    • 如果只有一个属性需要赋值,并且属性的名称是value,则value可以省略,直接定义值即可
    • 数组赋值时,值使用{}包裹。如果数组中只有一个值,则{}可以省略
    • 如果没有为属性提供值,将使用默认值,通过default赋予默认值
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public enum Person {
P1,P2,P3;
}

//自定义注解
public @interface MyAnno {
int age();
Person per();
MyAnno2 anno2();
String[] strs();
String name() default "张三"; //默认值
}

//给注解赋值
@MyAnno(age = 12,per = Person.P1, anno2 = @MyAnno2,strs = {"aaa","bbb"})
public class Worker {
}

元注解

用于描述注解的注解,注解本身也可以被注解,这种注解称为元注解。元注解用于定义注解的行为,例如指定注解的生命周期、作用目标等

@Target:描述注解能够作用的位置,@Target({ElementType.属性, ElemnetType.属性,…})

ElementType取值:

  • TYPE:可以作用于类上
  • METHOD:可以作用于方法上
  • FIELD:可以作用于成员变量上

@Retention:描述注解被保留的阶段:@Retention(RetentionPolicy.属性)

RetentionPolicy取值:

  • SOURCE: 源码阶段
  • CLASS : 类对象阶段
  • RUNTIME:运行阶段,当前被描述的注解,会保留到class字节码文件中,并被JVM读取到,一般都写这一种

@Documented:描述注解是否被抽取到api文档中

@Inherited:描述注解是否被子类继承

1
2
3
4
5
6
7
@Target({ElementType.TYPE,ElementType.METHOD,ElementType.FIELD}) //注解可以作用于类,方法和成员变量上
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
public @interface MyAnno3 {

}

AnnotatedElement接口

是Java反射API中的一个接口,定义了用于获取关于程序元素(类、方法、字段等)上注解信息的一组方法。AnnotatedElement 接口的实现类主要包括 Class 类、Method 类、Constructor 类、Field 类等

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public interface AnnotatedElement {
// 判断指定类型的注解是否存在于元素上
boolean isAnnotationPresent(Class<? extends Annotation> annotationClass);

// 获取指定类型的注解
<T extends Annotation> T getAnnotation(Class<T> annotationClass);

// 获取所有的注解
Annotation[] getAnnotations();

// 获取所有的元注解
Annotation[] getDeclaredAnnotations();
}

运行时解析注解

  • 获取注解定义的位置的对象的Class类型,这个对象可以是类,方法,或成员变量(Class,Method,Field)
  • 获取指定的注解,Class.getAnnotation(Class< T> annotationClass):参数为注解的Class类型
  • 调用注解中的抽象方法获取配置的属性值

现有一个类和方法

1
2
3
4
5
public class Demo1 {
public void show(){
System.out.println("demo1...show");
}
}

写一个自定义注解

1
2
3
4
5
6
7
8
9
/**
* 描述需要执行的类名和方法名
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Pro {
String className();
String methodName();
}

写一个需要使用注解的类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
//指定注解的属性
@Pro(className = "annotation.Demo1",methodName = "show")
public class ReflectTest {
public static void main(String[] args) throws Exception {
/*
前提:不能改变该类的任何代码。可以创建任意类的对象,可以执行任意方法
*/

//1.解析注解
//1.1获取被注解作用的类的字节码文件(Class)对象
Class<ReflectTest> reflectTestClass = ReflectTest.class;


//2,获取上面的注解对象
//其实就是就是在内存中生成一个该注解接口的子类实现对象
Pro proimpl = reflectTestClass.getAnnotation(Pro.class);

//3.调用注解对象中定义的抽象方法,获取返回值
String className = proimpl.className();
String methodName = proimpl.methodName();
System.out.println(className); //annotation.Demo1
System.out.println(methodName); //show


//3.加载该类进内存
Class cls = Class.forName(className);
//4.创建对象
Object obj = cls.newInstance();
//5.获取方法对象
Method method = cls.getMethod(methodName);
//6.执行方法
method.invoke(obj); //demo1...show
}
}

其实就是在内存中生成了一个该注解接口的子类实现对象

1
2
3
4
5
6
7
8
public class ProImpl implements Pro{
public String className(){
return "cn.itcast.annotation.Demo1";
}
public String methodName(){
return "show";
}
}

检查注解是否存在

pubulic boolean isAnnotationPresent(Class<? extends Annotation> annotationClass):用于检查特定的注解是否应用于类、方法、字段或其他程序元素。

当主方法执行后,会自动自行被检测的所有方法(加了Check注解的方法),判断方法是否有异常,记录到文件中

1
2
3
4
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Check {
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
public class Calculator {
//加法
@Check
public void add(){
String str = null;
str.toString();
System.out.println("1 + 0 =" + (1 + 0));
}
//减法
@Check
public void sub(){
System.out.println("1 - 0 =" + (1 - 0));
}
//乘法
@Check
public void mul(){
System.out.println("1 * 0 =" + (1 * 0));
}
//除法
@Check
public void div(){
System.out.println("1 / 0 =" + (1 / 0));
}

public void show(){
System.out.println("永无bug...");
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
public class TestCheck {
public static void main(String[] args) throws IOException {
//1.创建计算器对象
Calculator c = new Calculator();
//2.获取字节码文件对象
Class cls = c.getClass();
//3.获取所有方法对象
Method[] methods = cls.getMethods();

int number = 0;//出现异常的次数
//新建一个文件流对象
BufferedWriter bw = new BufferedWriter(new FileWriter("bug.txt"));

for (Method method : methods) {
//4.判断方法上是否有Check注解,isAnnotationPresent(class)方法
if(method.isAnnotationPresent(Check.class)){
//5.有,执行
try {
method.invoke(c);
} catch (Exception e) {
//6.捕获异常
//记录到文件中
number ++;
bw.write(method.getName()+ " 方法出异常了");
bw.newLine();
bw.write("异常的名称:" + e.getCause().getClass().getSimpleName());
bw.newLine();
bw.write("异常的原因:"+e.getCause().getMessage());
bw.newLine();
bw.write("--------------------------");
bw.newLine();
}
}
}
bw.write("本次测试一共出现 "+number+" 次异常");
bw.flush();
bw.close();
}
}

生成了bug.txt

image-20240309183222583

注解
http://example.com/2023/06/26/Java/Java进阶/注解/
作者
PALE13
发布于
2023年6月26日
许可协议