1.什么是注解

定义: 注解(Annotation),也叫元数据。一种代码级别的说明。它是JDK1.5及以后版本引入的一个特性,与类、接口、枚举是在同一个层次。它可以声明在包、类、字段、方法、局部变量、方法参数等的前面,用来对这些元素进行说明,注释

  • Annotation的作用

    • ①编写文档:通过代码里标识的注解生成文档【生成文档doc文档】
    • ②代码分析:通过代码里标识的注解对代码进行分析【使用反射】
    • ③编译检查:通过代码里标识的注解让编译器能够实现基本的编译检查【Override】
  • Annotation的格式:

    • 注解是以”@注释名”在代码中存在的,还可以添加一些参数值,
    • 例如: @SuppressWarnings(value=”unchecked”)
  • Annotation在哪里使用?

    • 可以附加在 package, class, method,feld等上面,相当于给他们添加了额外的辅助信息,

我们可以通过反射机制编程实现对这些元数据的访问

  • Annotation的应用结构图

注解就相当于一个你的源程序中要调用的一个类,要在源程序中应用某个注解,得先准备好了这个注解类,就像你要调用某个类,得先要开发好这个类
2.png


2.JDK内置注解

  1. @Override: 定义在 java.land.Override中,此注释只适用于修辞方法,表示一个方法声明打算
  2. 重写超类中的另一个方法声明
  3. @Deprecated:定义在 java.lang.Deprecated中,此注释可以用于修辞方法,属性,类,
  4. 表示不鼓励程序员使用这样的元素,通常是因为它很危险或者存在更好的选择
  5. @SuppressWarnings:定义在java.lang.SuppressWarnings中,用来抑制编译时的警告信息
  6. 口与前两个注释有所不同你需要添加一个参数才能正确使用,这些参数都是已经定义好了的,
  7. 我们选择性的使用就好了
  8. @SuppressWarnings("all") 忽略所有
  9. @SuppressWarnings("unchecked") 忽略安全检查
  10. @SuppressWarnings("deprecation") 忽略过时
  11. @SuppressWarnings("rawtypes") 忽略类型安全
  12. @SuppressWarnings("null") 忽略空指针
  13. @SuppressWarnings(value= "unchecked","deprecation"])

3.元注解

元注解的作用就是负责注解其他注解 ,Java定义了4个标准的meta- annotation类型,他们被用来
提供对其他 annotation类型作说明

这些类型和它们所支持的类在 java.lang.annotation包中可以找到
@Target: 用于描述注解的使用范围(即被描述的注解可以用在什么地方) 类上使用,方法上使用,字段上使用

FIELD:字段上可用此注解 METHOD:方法上可以用此注解 TYPE:类/接口上可以使用此注解 CONSTRUCTOR: 构造方法上使用 PARAMETER: 方法参数上使用

@Retention :表示需要在什么級别保存该涯释信息,用于描述注解的生命周期

  • SOURCE< CLASS < RUNTIME

    SOURCE: 注解在源码级别可见 CLASS:注解在字节码文件级别可见 RUNTIME:注解在整个运行阶段都可见

@Document: 说明该注解将被包含在 Javadoc中
@Inherited: 说明子类可以继承父类中的该注解

使用@Inherited定义子类是否可继承父类定义的Annotation@Inherited仅针对@Target(ElementType.TYPE)类型的annotation有效,并且仅针对class的继承,对interface的继承无效

示例定义如下注解

  1. package annotation;
  2. import java.lang.annotation.*;
  3. /**
  4. 元注解:用于描述注解的注解
  5. * @Target:描述注解能够作用的位置
  6. * @Retention:描述注解被保留的阶段
  7. * @Documented:描述注解是否被抽取到api文档中
  8. * @Inherited:描述注解是否被子类继承
  9. *
  10. */
  11. @Target({ElementType.TYPE,ElementType.METHOD,ElementType.FIELD})
  12. @Retention(RetentionPolicy.RUNTIME)
  13. @Documented
  14. @Inherited
  15. public @interface MyAnno3 {
  16. }

4.自定义注解

关键字:@interface
格式:

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

本质:注解本质上就是一个接口,该接口默认继承Annotation接口
*public interface MyAnno extends java.lang.annotation.Annotation {}

属性:接口中的抽象方法
要求:
1. 属性的返回值类型有下列取值
基本数据类型
String
枚举
注解
* 以上类型的一维数组

  1. 2. 定义了属性,在使用时需要给属性赋值<br /> 1. 如果定义属性时,使用default关键字给属性默认初始化值,则使用注解时,可以不进行属性的赋值。<br /> 2. 如果只有一个属性需要赋值,并且属性的名称是value,则value可以省略,直接定义值即可。<br /> 3. 数组赋值时,值使用{}包裹。如果数组中只有一个值,则{}可以省略

定义示例

  1. package annotation;
  2. public @interface MyAnno {
  3. int value();
  4. Person per();
  5. String[] strs();
  6. MyAnno2 anno2();
  7. String name() default "张三";
  8. }

示例

将反射中, 可以创建任意类的对象,可以执行任意方法 进行从优化,从注解中读取
MyAnno1 an = reflectTestClass.getAnnotation(MyAnno1.class);

  1. package annotation;
  2. import java.lang.reflect.Method;
  3. /**
  4. * 框架类
  5. */
  6. @MyAnno1(className = "annotation.Demo1",methodName = "show")
  7. public class ReflectTest {
  8. public static void main(String[] args) throws Exception {
  9. /*
  10. 前提:不能改变该类的任何代码。可以创建任意类的对象,可以执行任意方法
  11. */
  12. //1.解析注解
  13. //1.1获取该类的字节码文件对象
  14. Class<ReflectTest> reflectTestClass = ReflectTest.class;
  15. //其实就是在内存中生成了一个该注解接口的子类实现对象
  16. /*
  17. public class ProImpl implements Pro{
  18. public String className(){
  19. return "cn.itcast.annotation.Demo1";
  20. }
  21. public String methodName(){
  22. return "show";
  23. }
  24. }
  25. */
  26. //2.获取上边的注解对象, 并调用注解对象中定义的抽象方法,获取返回值
  27. MyAnno1 an = reflectTestClass.getAnnotation(MyAnno1.class);
  28. String className = an.className();
  29. String methodName = an.methodName();
  30. System.out.println("注解获取到的className: " + className);
  31. System.out.println("注解获取到的methodName: " + methodName);
  32. //3.加载该类进内存
  33. Class cls = Class.forName(className);
  34. //4.创建对象
  35. Object obj = cls.newInstance();
  36. //5.获取方法对象
  37. Method method = cls.getMethod(methodName);
  38. //6.执行方法
  39. method.invoke(obj);
  40. }
  41. }

运行结果
image.png
通过反射获取到注解的Class对象, 而其中的属性返回参数, 即使用注解时传入的参数

  1. //其实就是在内存中生成了一个该注解接口的子类实现对象
  2. public class ProImpl implements Pro{
  3. public String className(){
  4. return "cn.itcast.annotation.Demo1";
  5. }
  6. public String methodName(){
  7. return "show";
  8. }
  9. }

5.处理注解

因为注解定义后也是一种class,所有的注解都继承自java.lang.annotation.Annotation
因此读取注解,需要使用反射API。

Java提供的使用反射API读取Annotation的方法包括:

判断某个注解是否存在于ClassFieldMethodConstructor

  • Class.isAnnotationPresent(Class)
  • Field.isAnnotationPresent(Class)
  • Method.isAnnotationPresent(Class)
  • Constructor.isAnnotationPresent(Class)

例如:

  1. // 判断@Report是否存在于Person类:
  2. Person.class.isAnnotationPresent(Report.class);

使用反射API读取Annotation:

  • Class.getAnnotation(Class)
  • Field.getAnnotation(Class)
  • Method.getAnnotation(Class)
  • Constructor.getAnnotation(Class)

例如:

// 获取Person定义的@Report注解:
Report report = Person.class.getAnnotation(Report.class);
int type = report.type();
String level = report.level();

使用反射API读取Annotation有两种方法。方法一是先判断Annotation是否存在,如果存在,就直接读取:

Class cls = Person.class;
if (cls.isAnnotationPresent(Report.class)) {
    Report report = cls.getAnnotation(Report.class);
    ...
}

第二种方法是直接读取Annotation,如果Annotation不存在,将返回null

Class cls = Person.class;
Report report = cls.getAnnotation(Report.class);
if (report != null) {
   ...
}

读取方法、字段和构造方法的Annotation和Class类似。但要读取方法参数的Annotation就比较麻烦一点,
因为方法参数本身可以看成一个数组,而每个参数又可以定义多个注解,
所以,一次获取方法参数的所有注解就必须用一个二维数组来表示。

例如,对于以下方法定义的注解:

public void hello(@NotNull @Range(max=5) String name, @NotNull String prefix) {
}

要读取方法参数的注解,我们先用反射获取Method实例,然后读取方法参数的所有注解:

// 获取Method实例:
Method m = ...
// 获取所有参数的Annotation:
Annotation[][] annos = m.getParameterAnnotations();
// 第一个参数(索引为0)的所有Annotation:
Annotation[] annosOfName = annos[0];
for (Annotation anno : annosOfName) {
    if (anno instanceof Range) { // @Range注解
        Range r = (Range) anno;
    }
    if (anno instanceof NotNull) { // @NotNull注解
        NotNull n = (NotNull) anno;
    }
}

案例

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

1.定义一个check注解

package annotation.demo;

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

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

2.定义一个计算器类来使用注解

其中定义2个有异常的方法

/**
* 定义的计算器类
*/
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...");
    }

}

3.定义一个解析注解的类

package annotation.demo;

import java.io.BufferedWriter;
import java.io.FileWriter;
import java.io.IOException;
import java.lang.reflect.Method;

/**
 * 简单的测试框架
 *
 * 当主方法执行后,会自动自行被检测的所有方法(加了Check注解的方法),判断方法是否有异常,记录到文件中
 */
public class TestCheck {
    public static void main(String[] args) throws IOException {
        //1.创建计算器对象
        Calculator calculator = new Calculator();
        //2.获取字节码文件对象
        Class<? extends Calculator> aClass = calculator.getClass();
        //3.获取到所有方法
        Method[] methods = aClass.getMethods();

        int faildNum = 0; //出现异常的数量
        //执行失败写入到文件
        BufferedWriter bw = new BufferedWriter(new FileWriter("bug.txt"));

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

4.执行结果

如预期, 加了@Check注解的类才会进行执行, 执行后有异常的会将原因输出的文件中
image.png