注解

所谓注解,跟注释类似,用来对代码中的类、方法、属性进行说明。

作用

  1. 编写文档:通过代码里面标识的注解来生成文档javadoc命令

  2. 代码分析:可以通过反射机制,得知该类、方法、属性是否有注解。比如可以通过反射,调用getAnnotation函数获取该注解然后进行解析注解中的属性。

  3. 编译检查:通过注解能够让编译器自动进行编译检查,比如@overide可以检查是否是重载函数

    可以暂时理解为一个可以存放数据(注解的属性)的“特殊对象”。他的作用主要体现在可以解析他的属性值。看完《自定义注解》就明白了。

元注解

元注解就是标识注解的注解。也就是系统中定义好的注解,方便我们自定义注解的时候对该自定义注解进行描述。

这里只介绍两种最常用的元注解

@Target

这个元注解表示被描述的注解@Anno的适用范围,也就是说@Anno可以作用于类(TYPE)还是属性(FIELD),又或是方法(METHOD)

@Retention

Retention中文维持,表示被描述的注解的保持时间。我们一般设置为RUNTIME,表示时间是从字节码到JVM读取的时间段都能保持。

自定义注解

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package cn.itcast.example.example01;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target(ElementType.TYPE)//被修饰的注解作用范围
@Retention(RetentionPolicy.RUNTIME)//被修饰的注解保持时间,RUNTIME表示从字节码到jVM读取阶段
public @interface MyAno {
//属性列表,本质是方法
String className();
String methodName();
}

定义注解的格式

  1. 通过@interface表示是一个注解(本质上是一个接口)
  2. 注解内部是属性列表,跟定义成员方法一样,返回值+函数名。
  3. 注解上面是元注解,用来限定该注解的特性,比如作用范围、保持时间等。

解析注解

解析注解就是读取注解的属性。具体步骤

  • 创建字节码对象
  • 字节码对象调用getAnnotation获取

案例1-通过注解获取类名和方法,并执行类中的方法

步骤

  1. 获取字节码对象
  2. 字节码对象调用getAnnotation获取注解对象,然后获取注解对象属性值类名和方法名
  3. 利用反射执行类中方法

代码

MyAno.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package cn.itcast.example.example01;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target(ElementType.TYPE)//被修饰的注解作用范围
@Retention(RetentionPolicy.RUNTIME)//被修饰的注解保持时间,RUNTIME表示从字节码到jVM读取阶段
public @interface MyAno {
//属性列表,本质是方法
String className();
String methodName();
}

Main.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package cn.itcast.example.example01;



@MyAno(className = "cn.itcast.example.domain.Cat",methodName ="shout" )
public class Main {
public static void main(String[] args) throws Exception {
Class<Main> cls = Main.class;//通过字节码获取注解对象
MyAno ano = cls.getAnnotation(MyAno.class);//获取注解对象
String cn = ano.className();
String mn = ano.methodName();
Class<?> cls1 = Class.forName(cn);
Object o = cls1.getConstructor().newInstance();
cls1.getMethod(mn).invoke(o);

}
}

Cat.java

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
package cn.itcast.example.domain;

public class Cat {
private String name;

public Cat() {
}

public Cat(String name) {

this.name = name;
}

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}
public void shout(){
System.out.println("喵喵喵");
}
}

Dog.java

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
package cn.itcast.example.domain;

public class Dog {
private String name;

public Dog(String name) {
this.name = name;
}

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

public Dog() {
}
public void shout(){
System.out.println("哇哦哇哦");
}
}


案例2-通过注解实现测试框架,测试Calculator中的各个方法

步骤

  1. 自定义一个方法注解
  2. 在需要测试的方法上加上注解
  3. 在测试类中利用反射机制获取Calculator字节码对象,获取该对象的所有方法对象。
  4. 方法对象调用getAnnotation获取注解,根据注解有无执行该方法对象。(主要利用了反射机制)

代码

Calculator.java

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
package cn.itcast.example.example02;

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...");
}
}

Check.java

1
2
3
4
5
6
7
8
9
10
11
12
package cn.itcast.example.example02;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Check {
}

TestCheck.java

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
40
41
42
43
44
45
46
47
package cn.itcast.example.example02;

import java.io.BufferedWriter;
import java.io.FileWriter;
import java.io.Writer;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

public class TestCheck {
public static void main(String[] args) throws Exception{
Calculator calculator = new Calculator();
Class cls = calculator.getClass();
Method[] declaredMethods = cls.getDeclaredMethods();
// FileWriter fw = new FileWriter("bug1.txt");

BufferedWriter fw = new BufferedWriter(new FileWriter("bug.txt"));
int sum=0,erorNum=0;
for (Method declaredMethod : declaredMethods) {
// System.out.println(declaredMethod);
Check annotation = declaredMethod.getAnnotation(Check.class);
//如果有注解
if(null!=annotation){
sum++;
try{
declaredMethod.invoke(calculator);
}catch (Exception e){
fw.write(declaredMethod.getName()+"出异常了");
fw.newLine();
fw.write(e.getCause().toString());
fw.newLine();
fw.write("---------------");
fw.newLine();
erorNum++;
}

}

}
fw.write("一共测试了"+sum+"个函数");
fw.newLine();
fw.write("出错了"+erorNum+"个函数");
fw.newLine();
fw.flush();
fw.close();
}
}

总结

实际开发过程中我们一般是使用注解,一般不会去自定义注解,但是我们需要知道注解本质就是一个接口,在接口中定义一些方法列表(在注解中称为属性列表)。

可以利用反射机制获取注解,从而解析注解得到里面的属性。