也许是全网最通俗易懂的注解文章[Java注解机制]

也许是全网最通俗易懂的注解文章[Java注解机制]

前言:

在学注解的时候,看了网上很多关于注解的文章,发现大多让我看得云里雾里的,特此查阅资料,学习理解,自己打造了一篇注解文章

一段你可能看不懂的注解概念:

从java1.5以后,jdk提供了注解开发,开发者可以自定义注解。Annotation并不影响代码的语义,但是它的工作方式常被用来开发程序工具或者类库,它反过来对正在运行的程序语义有所影响

具体如何影响,下面会说明

在文章的末尾,我会呈上一个案例,既然我们都知道反射与注解是写框架的基础,那么我们就通过反射与注解,自己写一个小框架出来

注解这篇我是在反射机制之后写的,因为反射机制稍微比较复杂一点,在反射中我说过,反射与注解是你学框架必备的知识!如果朋友你还没学过反射,可以参考

[Java]会SSM不懂反射?不可以奥

这是我从Java8官方文档截出来的图

所有注解类实现了Annotation这个接口

如我们常见的Overide注解,Overide是一个类,它实现了Annotation

来看看Java提供了常见注解,都是我们耳熟能详的:

  • @override:override属于标识性注解,文档告诉你,这个注解用来表示一个方法属于重写方法,如果你的方法加了这个注解,但该方法并非重写方法,会报错
  • @Deprecated :标记过时方法。如果使用该方法,会报编译警告

以下是标注在注解上的注解,Java称其为元注解,在后面我们会逐个分析

  • @Retention:标识这个注解怎么保存,是只在代码中,还是编入class文件中,或者是在运行时可以通过反射访问

  • @Target:表示被标注的注解可以标注在哪些域(方法?属性?)上

  • @Documented : 标记这些注解是否包含在用户文档中

  • @Inherited - 标记这个注解是继承于哪个注解类(默认 注解并没有继承于任何子类)

    其他注解,请自行查看Java8官方文档

Annotation 架构

  • 1 个 Annotation 和 1 个 RetentionPolicy 关联

  • 1 个 Annotation 和 1~n 个 ElementType 关联

如果你懂一点设计模式,那么会知道,每个Annotation对象,会有一个RetentionPolicy 属性,与多个ElementType 属性

什么是RetentionPolicy

RetentionPolicy从名字猜测我想跟Retention有关系,所以翻看Retention的源码

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Retention {
    /**
     * Returns the retention policy.
     * @return the retention policy
     */
    RetentionPolicy value();
}

得出:

RetentionPolicy是Retention 里面定义的方法(姑且这么叫,毕竟格式吻合)的类型

什么是Retention

从上面我们已经知道了,是元注解

有了这些基本的架构铺垫,下面我就开门见山了

Annotation是什么?

注释是写给程序员看的,那么对应的注解是给计算器看的

Annotation有什么作用?

  • 编写文档:在代码中标识的注解,可以由javadoc命令,一并生成文档,以满足文档的阅读性


  • 代码分析:配合反射通过代码里标识的注解对代码进行分析(后面我会用一个小框架举例)

  • 编译检查:通过代码里标识的注解让编译器能够实现基本的编译检查【如Override】

    如果加了Override的注解,不是重写方法,编译报错

Annotation格式?

		元注解(加在注解上的注解)
		public @interface 注解名称{
			属性列表;
		}

上面这段代码本质是一个接口,该接口默认继承Annotation接口,通过jad反编译,我们可以得到它的反编译代码为:

public interface MyAnno extends java.lang.annotation.Annotation 

属性:

在上面,我将属性称为方法,因为它的格式就是方法的格式,但Java称之为属性

属性有以下要求:

  1. 属性的返回值类型有下列取值
  • 基本数据类型
  • String类型
  • 枚举类型
  • 注解类型
  • 以上类型的数组
public @interface MyAnno {
    int value();
    Person per();  	//枚举类型
    MyAnno2 anno2(); //注解类型
    String[] strs();
}

2.定义了属性,在使用时需要给属性赋值

    //假设给此类添加我们上面定义的注解
@MyAnno(value=12,per = Person.P1,anno2 = @MyAnno2,strs="strs")
public class Worker {

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

元注解

@Target

描述注解能够作用的位置,如作用在方法上?还是类上?

该注解只提供一个属性,Value

Value可以等于:

  • ANNOTATION_TYPE(如上图所示),表明可以使用在注解类型
  • LOCAL_VARIABLE:作用于局部遍历
  • CONSTRUCTOR:作用于构造器

… 不一一列举了,其他的可以根据IDEA带的提示功能找,也可以参考ElementType.java源码查找,源码通过枚举,涵盖了所有可能覆盖的域

@Retention

描述注解被保留的阶段,如是否保留到class字节码文件中,被JVM读取

@Documented:

描述注解是否被抽取到api文档中

@Inherited:

描述注解是否被子类继承

我们接下来关注,如何自定义注解,并使用它

自定义注解

public @interface MyAnn {
	//定义注解的属性,必须要加上()
	String value();
}
  • @interface代表这是一个注解类
  • 注解中可以设置值,在程序运行时配合使用,先不管为什么,后面看
  • 标注了@interface的类将实现Annotation接口

在你的某个类上进行注解

@MyAnn(value = "hello")
public class AnnTest {
    // Omit
}  
  • 你指定的value将传给注解类

以上内容就把注解机制讲得差不多了,下面分享一个小案例,看看是具体如何应用注解来写框架的

当然,其中也用到了反射,此外还用到了一点基础的IO流

简单的测试框架:

某人发给你一段代码,要求你把每个方法测试一遍,看是否发生异常

/**
 * 小明定义的计算器类
 */
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));
    }
    //除法
    //次方法比出异常  因为/0
    @Check
    public void div(){  
        System.out.println("1 / 0 =" + (1 / 0));
    }


    public void show(){
        System.out.println("永无bug...");
    }

}

定义一个注解类

@Retention(RetentionPolicy.RUNTIME) //运行期
@Target(ElementType.METHOD)  //作用在方法上
public @interface Check {
}

编写测试代码

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

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

    public static void main(String[] args) throws IOException {
        //1.创建计算器对象
        Calculator c = new Calculator();
        //2.获取字节码文件对象
        Class cls = c.getClass();
        //3.获取所有方法
        Method[] methods = cls.getMethods();

        int number = 0;//统计出现异常的次数
        //将测试结果写入bug.txt
        BufferedWriter bw = new BufferedWriter(new FileWriter("bug.txt"));

        for (Method method : methods) {
            //4.判断方法上是否有Check注解
            if(method.isAnnotationPresent(Check.class)){
                //5.有,执行
                try {
                    method.invoke(c);
                } catch (Exception e) {
                    //6.捕获异常

                    //记录到文件中(IO流)
                    number ++;

                    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("本次测试一共出现 "+number+" 次异常");

        bw.flush();
   	    bw.close();
    }
}
原创文章 197 获赞 142 访问量 6万+

猜你喜欢

转载自blog.csdn.net/JunSIrhl/article/details/105722510