一、什么是注解
概念:说明程序,给计算机看的
定义:注解(Annotation),也叫元数据。一种代码级别的说明。它是JDK1.5及以后版本引入的一个特性,与类、接口、枚举是在同一个层次。它可以声明在包、类、字段、方法、局部变量、方法参数等的前面,用来对这些元素进行说明,注释。
作用分类:
- 编写文档:通过代码里标识的元数据生成文档【生成文档doc文档】
- 代码分析:通过代码里标识的元数据对代码进行分析【使用反射】
- 编译检查:通过代码里标识的元数据让编译器能够实现基本的编译检查【Override】
二、JDK 中预定义的注解
- @Override:定义在 java.lang.Override 中,只适用于修饰方法,表示一个方法是否打算重写超类中的另一个方法声明,检测被该注解标注的方法是否是继承自父类(接口)的
public class AnnoDemo01 {
@Override // 重写父类的方法
public String toString() {
return super.toString();
}
}
- @Deprecated:定义在 java.lang.Deprecated 中,可以用于修饰方法,属性,类,表示不鼓励使用这样的元素,通常是因为危险或者存在更好的选择,表示该注解标注的东西已经过时
@Deprecated //表示不推荐使用,但是可以使用或者存在更好的替换方式
public void show1(){
}
- @SuppressWarnings:定义在 java.lang.SuppressWarnings 中,用来抑制编译时的警告信息,该注解需要添加一个参数才能正确使用,参数是已经定义好的
@SuppressWarnings("all")
// 编辑器会有一些警告信息的提示,通过该注解可以让其不显示
public class AnnoDemo01 {}
三、自定义注解
1. 注解的本质
创建一个注解文件编译之后反编译
public @interface MyAnno {
... 属性列表
}
public interface MyAnno extends java.lang.annotation.Annotation {
}
// 其实创建一个注解 @interface 就是一个接口继承了 Annotation类
2. 接口中的抽象方法
public @interface MyAnno {
// 注解的属性,成员方法
String value() default "111";
}
要求:
-
方法(属性)返回值类型:基本数据类型、String、枚举、注解、以上类型的数组,其他都不行
-
定义了属性,在使用时需要给属性赋值
- 如果定义属性时,使用 default 关键字给属性默认值初始化值,则使用注解时,可以不进行属性的赋值。
- 如果只有一个属性需要赋值,并且属性的名称是 value,则value可以省略,直接定义值即可。
- 如果是数组赋值时,值使用 {} 包裹,如果数组中只有一个值,则省略 {}。
@MyAnno("aa")
public class Member {
}
3. 元注解
用于描述注解的注解,元注解作用就是负责注解其他注解,Java 定义了 4 个标准的 meta-annotation 类型,他们被用来提供对其他 annotation 类型作说明
这些类型和它们所支持的类在 java.lang.annotation 包中可以找到
3.1 @Target
描述注解的使用范围,即被描述的注解可以用在什么地方
- 自定义一个注解
@Target(ElementType.TYPE)
public @interface MyAnno {
}
-
测试类中测试
类上可以使用,方法上不能使用
- TYPE:作用于类上
- METHOD:作用于方法上
- FIELD:作用于成员变量上
3.2 @Retention
表示需要在什么级别保存该注解信息,用于描述注解的声明周期(source < class < runtime)
// RUNTIME 类型,会被保留到 class 字节码文件中,并被 JVM 读取到
@Retention(RetentionPolicy.RUNTIME)
// CLASS 类型,会被保留到 class 字节码文件中,不会被 JVM 读取到
@Retention(RetentionPolicy.CLASS)
// SOURCE 类型,不会保留到 class 字节码文件中
@Retention(RetentionPolicy.SOURCE)
3.3 @Document
描述注解是否被抽取到 API 文档中
// 注解信息
@Documented
@Target({ElementType.TYPE,ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnno {
}
// 分别在类和方法上使用该注解
@MyAnno
public class DemoTest {
@MyAnno
public void show(){
}
}
使用 cmd 进入到该文件夹下执行命令:javadoc DemoTest 生成注解相关的文档信息
3.4 @Inherited
描述注解是否可以被子类继承
// 定义一个注解,使用该关键字修饰
@Inherited
public @interface MyAnno {
}
// 在父类上使用该注解
@MyAnno
public class Father {
}
// 子类继承父类,那么子类也有父类使用的注解信息
public class Son extends Father{
}
四、解析使用注解
获取注解中定义的属性值
- 创建一个注解
/**
* 描述需要执行的类型和方法名
*/
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface Pro {
String className();
String methodName();
}
- 创建一个类,有一个 show() 方法输出
public class Demo01 {
public void show() {
System.out.println("Demo01 show ...");
}
}
- 创建测试类,通过反射获得指定 Demo01 类的 show() 方法
@Pro(className = "com.lss.anno.demo07.Demo01",methodName = "show")
public class ReflectTest {
public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException {
// 解析注解
// 1. 获取该类的字节码文件对象
Class<ReflectTest> clzz = ReflectTest.class;
// 2. 获取该类上的注解对象
// 其实就是在内存中生成了一个该注解接口的子类实现对象
/**
* 其实就是在内存中生成了一个该注解接口的子类实现对象
* public class ProImpl implements Pro{
* public String className() {
* return "com.lss.Demo07.Demo01";
* }
* public String methodName() {
* return "show";
* }
* }
*/
Pro annotation = clzz.getAnnotation(Pro.class);
// 3. 调用注解对象中定义的抽象方法,获取返回值
String className = annotation.className();
String methodName = annotation.methodName();
// 4. 加载类到内存中
Class<?> cls = Class.forName(className);
// 5. 创建对象
Object o = cls.newInstance();
// 6. 获取方法对象
Method method = cls.getMethod(methodName);
method.invoke(o);
}
}
- 获取注解定义的位置的对象(Class,Method,Field)
- 获取指定的注解
- 调用注解中的抽象方法获取配置的属性值
五、异常检测小案例
通过自定义注解的方式检测一个类中的几个方法是否有异常,如果有异常则将异常信息写入到 bug.txt 文件,如果没有则正常执行
- 首先创建一个自定义注解
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Check {
}
- 创建一个类,包含3个方法,2个有异常,一个无异常
public class Calc {
@Check // 通过 注解标记检测的方法
public void create() {
String str = null;
str.toLowerCase();
}
@Check
public void delete() {
int i = 1 / 0;
}
@Check
public void show() {
System.out.println("没有发生异常的类");
}
}
- 测试类
public class CalcTest {
public static void main(String[] args) throws IOException {
//1. 获取 Calc 对象的字节码文件
Calc calc = new Calc();
Class clzz = calc.getClass();
// 将异常信息写到 bug.txt 文件中
BufferedWriter bw = new BufferedWriter(new FileWriter("bug.txt"));
// 2. 获得所有方法
Method[] methods = clzz.getMethods();
for (Method method : methods) {
if (method.isAnnotationPresent(Check.class)) {
try {
method.invoke(calc);
} catch (Exception e) {
// 有异常进行写出
bw.write(method.getName() + "方法发生了异常");
bw.newLine();
bw.write("异常名称:" + e.getCause().getClass().getSimpleName());
bw.newLine();
bw.write("异常原因:" + e.getCause().getMessage());
bw.newLine();
}
}
}
bw.flush();
bw.close();
}
}
学习参考视频: www.bilibili.com/video/BV1Vt…