也许是全网最通俗易懂的注解文章[Java注解机制]
前言:
在学注解的时候,看了网上很多关于注解的文章,发现大多让我看得云里雾里的,特此查阅资料,学习理解,自己打造了一篇注解文章
一段你可能看不懂的注解概念:
从java1.5以后,jdk提供了注解开发,开发者可以自定义注解。Annotation并不影响代码的语义,但是它的工作方式常被用来开发程序工具或者类库,它反过来对正在运行的程序语义有所影响
具体如何影响,下面会说明
在文章的末尾,我会呈上一个案例,既然我们都知道反射与注解是写框架的基础,那么我们就通过反射与注解,自己写一个小框架出来
注解这篇我是在反射机制之后写的,因为反射机制稍微比较复杂一点,在反射中我说过,反射与注解是你学框架必备的知识!如果朋友你还没学过反射,可以参考
这是我从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称之为属性
属性有以下要求:
- 属性的返回值类型有下列取值
- 基本数据类型
- 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();
}
}