注解
从JDK5开始,Java增加对元数据的支持,也就是注解,注解是一种代码级别的说明。注解与注释是有一定区别的,可以把注解理解为代码里的特殊标记,这些标记可以在编译,类加载,运行时被读取,并执行相应的处理。通过注解开发人员可以在不改变原有代码和逻辑的情况下在源代码中嵌入补充信息。
作用分类
- 编写文档:通过代码里标识的注解生成doc文档
- 代码分析:通过代码里标识的注解进行代码分析(使用反射)
- 编译检查:通过代码里标识的注解对代码进行基本的编译检查
JDK内置注解
- @Override: 检测被注解的方法是否继承自父类/父接口,不是则报错
- @Deprecated: 将该注解标注的内容标识为已过时的内容
- @SuppressWarnings: 压制警告, 指示编译器去忽略注解中声明的警告。(一般传递参数”all”压制所有警告)
- @SafeVarargs: Java 7 开始支持,忽略任何使用参数为泛型变量的方法或构造函数调用产生的警告。
- @FunctionalInterface: Java 8 开始支持,标识一个匿名函数或函数式接口。
自定义注解
代码语言:javascript复制//自定义注解的格式
元注解...
public @interface 注解名称(){}
注解的本质
注解本质就是一个接口,该接口默认继承java.lang.annotation.Annotation接口
下面的内容就是通过将public @interface MyAnnotation { }
经过编译与反编译后重新得到的Java文件,可以清楚的看到注解的本质
public interface MyAnnotation extends java.lang.annotation.Annotation {}
编译与反编译的过程
注解的属性
在注解接口中定义的抽象的成员方法
要求
属性的返回值类型只能是下列取值:
- 基本数据类型
- String类
- 枚举
- 注解
- 以上类型的数组
定义属性后,在使用时需要给属性赋值(或者在定义时利用default()传入默认值)
如果只有一个属性需要赋值,且属性名为value,则value可以省略,直接传入值即可
数组赋值时,使用大括号{}包裹值,如果数组中只有一个值,大括号可以省略
元注解
用于描述注解的注解
- @Target:描述注解能够作用的位置(ElementType常见取值:TYPE表示能够作用于类上,METHOD表示能够作用于方法上,FIELD表示能够作用于成员变量上)
- @Retention:描述注解被保留的阶段(Java代码的三个阶段SOURCE:源代码阶段,CLASS:类对象阶段,RUNTIME:运行时阶段) 一般情况下自定义注解都选择运行时阶段
- @Documented:描述注解是否被抽取到API文档中
- @Inherited:描述注解是否被子类继承
在程序中使用(解析)注解
获取注解中定义的属性值
首先获取注解定义位置的对象,因为注解可以定义在类,方法或成员变量上,所以需要获取对应变量,例如:注解定义在类上,就需要获取该类对象,然后通过类对象的getAnnotation方法获取到指定的注解,随后只需要调用注解的属性值(抽象方法)即可获取到我们所配置的属性值
定义注解:
代码语言:javascript复制/**
* 描述需要执行的类名和方法名
*/
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
public @interface Pro {
String className();
String methodName();
}
调用注解并获取属性值
代码语言:javascript复制@Pro(className = "cn.ywrby.domain.Person",methodName = "MessageOfPerson")
public class ReflectTest2 {
public static void main(String[] args) throws Exception {
//解析注解
//获取本类的字节码文件对象
Class<ReflectTest2> MyCls=ReflectTest2.class;
//获取本类指定的注解
//其实就是在内存中生成一个该注解接口的子类实现对象,该子类复写了注解的属性,返回值就是我们定义注解时传入的值
Pro annotation=MyCls.getAnnotation(Pro.class);
//调用注解对象中定义的抽象方法(属性),获取返回值
String className=annotation.className();
String methodName=annotation.methodName();
//加载该类进内存,并且创建对象
Class cls=Class.forName(className);
//创建对象
Object object=cls.getDeclaredConstructor().newInstance();
//获取方法对象
Method method=cls.getMethod(methodName);
//执行方法
method.invoke(object);
}
}
简单案例:完成一个简单的测试框架
自定义注解
代码语言:javascript复制@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Check {
}
使用注解
代码语言:javascript复制public class Calculator2 {
@Check
public void add(){
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 ignore(){
System.out.println("永不报错,不被检查");
}
}
进行测试
代码语言:javascript复制/**
* 简单的测试框架
*
* 主方法执行后,自动检测所有加上@Check注解的方法,判断方法有无异常,并记录在文件中
*/
public class Calculator2Test {
public static void main(String[] args) throws IOException {
int errorNum=0; //出现异常的次数
BufferedWriter bw=new BufferedWriter(new FileWriter("bug.txt"));
//创建Calculator2对象
Calculator2 cal=new Calculator2();
//获取字节码文件对象
Class<Calculator2> cls=Calculator2.class;
//获取所有方法
Method[] methods=cls.getMethods();
//判断是否有@Check注解
for(Method method:methods){
//isAnnotationPresent方法判断当前方法上是否有指定的注解
if(method.isAnnotationPresent(Check.class)){
//存在则执行方法,不存在则不执行该方法
try {
method.invoke(cal);
} catch (Exception e) {
//发现异常,捕获异常并记录在文件中
errorNum =1;
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("本次一共出现" errorNum "个异常");
bw.flush();
bw.close();
}
}
运行效果
代码语言:javascript复制//窗口显示结果
1 0=1
1-0=1
1*0=0
//bug.txt文件中显示内容
div方法出现异常!
异常的名称:ArithmeticException
异常的原因/ by zero
--------------------------------------
本次一共出现1个异常
小结
- 以后大部分情境下,我们是注解的使用者而不是自定义
- 注解的目的是给编译器或解析程序使用
- 注解不是程序的一部分,注解的存在更类似于标签的作用