一、概念
1. 定义
Annotation(注解)是JDK5.0及以后版本引入的。它可以用于创建文档,跟踪代码中的依赖性,甚至执行基本编译时检查。注解是以 ‘@注解名’ 在代码中存在的,根据注解参数的个数,我们可以将注解分为:标记注解、单值注解、完整注解三类。它们都不会直接影响到程序的语义,只是作为注解(标识)存在,我们可以通过反射机制编程实现对这些元数据(注解)的访问。另外,你可以在编译时选择代码里的注解是否只存在于源代码级,或者它也能在class文件中出现。
2. 作用分类
如果要对于的作用进行分类,大致可分为三类:
- 编写文档:通过代码里标识的注解生成文档。
- 代码分析:通过代码里标识的注解对代码进行分析。
- 编译检查:通过代码里标识的注解让编译器能实现基本的编译检查。
二、JDK中预定义的一些注解
@Override: 检测被该注解标注的方法是否是继承自父类(父接口)的。
@Deprecated: 该注解标注的内容,表示已过时。
@SuppressWarnings: 压制警告。一般传递参数 ‘all’ 。
三、自定义注解
1. 注解的格式及本质:
格式:
@元注解
public @interface 注解名称 { }
本质:
注解本质上就是一个接口,该接口默认继承Annotation接口。
public interface 注解名称 extends java.lang.annotation.Annotation( ) { }
2. 属性
接口中的抽象方法。
要求:
(1) 属性的返回值类型:
- 基本数据类型
- String
- 枚举
- 注解
- 以上类型对应的数组
(2)定义了属性,使用时需要给属性赋值:
- 可以在定义属性时使用 ‘default’ 给属性初始化值。
- 如果只有一个属性需要赋值,在赋值时可以直接写属性值即可,不需要写 ‘属性名 = 属性值’ 。
- 数组赋值时,使用 ‘{ }’ 包裹,若数组中只需要赋一个值,则 ‘{ }’ 可以省略。
3. 元注解
用于描述注解的注解。
@Target: 描述注解能够作用的位置。
属性 ‘ElementType[ ] value’ 的常用属性值:
- ElementType.TYPE:表示可以作用于类上。
- ElementType.METHOD:表示可以作用于方法上。、
- ElementType.FIELD:表示可以作用于成员变量上。
@Retention: 描述注解被保留的阶段。
属性 ‘RetentionPolicy value’ 的属性值:
- RetentionPolicy.SOURCE:被描述的注解只保留在源文件,当Java文件编译成class文件的时候,注解被遗弃。
- RetentionPolicy.CLASS:被描述的注解被保留到class文件,但jvm加载class文件时候被遗弃。
- RetentionPolicyRUNTIME:被描述的注解不仅被保存到class文件中,jvm加载class文件之后,仍然存在。(自定义注解一般取这个值)
@Documented: 描述注解是否被抽取到API文档中。
@Inherited: 描述注解是否被子类继承。
四、在程序中使用(解析)注解
获取注解中定义的属性值。
步骤:
- 获取注解定义位置的字节码对象(Class、Field、Method、Constructor等)。
- 获取指定的注解(*.getAnnotation(Class))。’ * '表示步骤1中获取的对象。
- 调用注解中的抽象方法获取注解的属性值。
自定义注解:
package com.banmingi.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* @auther 半命i 2019/12/6
* @description 描述需要执行的类名和方法名.
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Pro {
String className();
String methodName();
}
解析注解(注解使用的位置是在类上,所以用):
package com.banmingi.annotation;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
/**
* @auther 半命i 2019/12/6
* @description
*/
@Pro(className = "com.banmingi.annotation.Demo1",methodName = "show")
public class AnnotationTest {
public static void main(String[] args) throws Exception {
//1. 解析注解
//1.1. 获取该类的字节码文件对象
Class<AnnotationTest> annotationTestClass = AnnotationTest.class;
//1.2. 获取注解对象
//其实就是在内存中去生成了该注解接口的子类实现对象(意思就是以下的Pro类实现了Pro注解(注解的本质是接口))
Pro an = annotationTestClass.getAnnotation(Pro.class);
//1.3. 调用注解对象中定义的抽象方法(属性),获取返回值
String className = an.className();
System.out.println(className); //com.banmingi.annotation.Demo1
String methodName = an.methodName();
System.out.println(methodName); //show
}
}
五、使用注解实现简单的框架
设计一个框架,检测方法在运行的过程中是否出现异常。
定义Check注解:
package com.banmingi.annotation.demo;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* @auther 半命i 2019/12/6
* @description
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Check {
}
被测试的类:
package com.banmingi.annotation.demo;
/**
* @auther 半命i 2019/12/6
* @description 自定义的计算器类
*/
public class Calculator {
//加法
@Check
public void add() {
Integer integer = null;
integer.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...");
}
}
解析程序:
package com.banmingi.annotation.demo;
import java.io.BufferedWriter;
import java.io.FileWriter;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
/**
* @auther 半命i 2019/12/6
* @description 简单的测试框架,当主方法执行后,会自动执行被检测的所有方法
* 检测加了Check注解的方法,判断是否有异常,若无异常则执行方法,若有异常则把异常记录到bug.txt文件中
*/
public class TestCheck {
public static void main(String[] args) throws IOException {
//1. 创建计算器对象
Calculator calculator = new Calculator();
//2. 获取字节码文件对象
Class cls = calculator.getClass();
//3. 获取Calculator的所有方法
Method[] methods = cls.getMethods();
//出现异常的次数
int num = 0;
BufferedWriter bw = new BufferedWriter(new FileWriter("bug.txt"));
for (Method method : methods) {
//4. 判断方法上是否有Check注解
if(method.isAnnotationPresent(Check.class)) {
//5. 有Check注解,执行方法
try {
method.invoke(calculator);
} catch (Exception e) {
//6. 捕获异常 把异常记录到bug.txt文件中
num++;
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("本次测试一共出现" + num + "次异常");
bw.flush();
bw.close();
}
}
执行结果:
bug.txt:
六、小结
- 以后大多数时候,我们会使用框架的各种注解,而不是自定义注解。
- 注解给谁用:编译器、解析程序
- 注解并不是程序的一部分,可以理解为程序的一个标签。