1.什么是注解
定义: 注解(Annotation),也叫元数据。一种代码级别的说明。它是JDK1.5及以后版本引入的一个特性,与类、接口、枚举是在同一个层次。它可以声明在包、类、字段、方法、局部变量、方法参数等的前面,用来对这些元素进行说明,注释
Annotation的作用
- ①编写文档:通过代码里标识的注解生成文档【生成文档doc文档】
- ②代码分析:通过代码里标识的注解对代码进行分析【使用反射】
- ③编译检查:通过代码里标识的注解让编译器能够实现基本的编译检查【Override】
Annotation的格式:
- 注解是以”@注释名”在代码中存在的,还可以添加一些参数值,
- 例如: @SuppressWarnings(value=”unchecked”)
Annotation在哪里使用?
- 可以附加在 package, class, method,feld等上面,相当于给他们添加了额外的辅助信息,
我们可以通过反射机制编程实现对这些元数据的访问
- Annotation的应用结构图
注解就相当于一个你的源程序中要调用的一个类,要在源程序中应用某个注解,得先准备好了这个注解类,就像你要调用某个类,得先要开发好这个类
2.JDK内置注解
@Override: 定义在 java.land.Override中,此注释只适用于修辞方法,表示一个方法声明打算重写超类中的另一个方法声明@Deprecated:定义在 java.lang.Deprecated中,此注释可以用于修辞方法,属性,类,表示不鼓励程序员使用这样的元素,通常是因为它很危险或者存在更好的选择@SuppressWarnings:定义在java.lang.SuppressWarnings中,用来抑制编译时的警告信息口与前两个注释有所不同你需要添加一个参数才能正确使用,这些参数都是已经定义好了的,我们选择性的使用就好了@SuppressWarnings("all") 忽略所有@SuppressWarnings("unchecked") 忽略安全检查@SuppressWarnings("deprecation") 忽略过时@SuppressWarnings("rawtypes") 忽略类型安全@SuppressWarnings("null") 忽略空指针@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的继承无效
示例定义如下注解
package annotation;import java.lang.annotation.*;/**元注解:用于描述注解的注解* @Target:描述注解能够作用的位置* @Retention:描述注解被保留的阶段* @Documented:描述注解是否被抽取到api文档中* @Inherited:描述注解是否被子类继承**/@Target({ElementType.TYPE,ElementType.METHOD,ElementType.FIELD})@Retention(RetentionPolicy.RUNTIME)@Documented@Inheritedpublic @interface MyAnno3 {}
4.自定义注解
关键字:@interface
格式:
public @interface 注解名称{属性列表;}
本质:注解本质上就是一个接口,该接口默认继承Annotation接口
*public interface MyAnno extends java.lang.annotation.Annotation {}
属性:接口中的抽象方法
要求:
1. 属性的返回值类型有下列取值
基本数据类型
String
枚举
注解
* 以上类型的一维数组
2. 定义了属性,在使用时需要给属性赋值<br /> 1. 如果定义属性时,使用default关键字给属性默认初始化值,则使用注解时,可以不进行属性的赋值。<br /> 2. 如果只有一个属性需要赋值,并且属性的名称是value,则value可以省略,直接定义值即可。<br /> 3. 数组赋值时,值使用{}包裹。如果数组中只有一个值,则{}可以省略
定义示例
package annotation;public @interface MyAnno {int value();Person per();String[] strs();MyAnno2 anno2();String name() default "张三";}
示例
将反射中, 可以创建任意类的对象,可以执行任意方法 进行从优化,从注解中读取MyAnno1 an = reflectTestClass.getAnnotation(MyAnno1.class);
package annotation;import java.lang.reflect.Method;/*** 框架类*/@MyAnno1(className = "annotation.Demo1",methodName = "show")public class ReflectTest {public static void main(String[] args) throws Exception {/*前提:不能改变该类的任何代码。可以创建任意类的对象,可以执行任意方法*///1.解析注解//1.1获取该类的字节码文件对象Class<ReflectTest> reflectTestClass = ReflectTest.class;//其实就是在内存中生成了一个该注解接口的子类实现对象/*public class ProImpl implements Pro{public String className(){return "cn.itcast.annotation.Demo1";}public String methodName(){return "show";}}*///2.获取上边的注解对象, 并调用注解对象中定义的抽象方法,获取返回值MyAnno1 an = reflectTestClass.getAnnotation(MyAnno1.class);String className = an.className();String methodName = an.methodName();System.out.println("注解获取到的className: " + className);System.out.println("注解获取到的methodName: " + methodName);//3.加载该类进内存Class cls = Class.forName(className);//4.创建对象Object obj = cls.newInstance();//5.获取方法对象Method method = cls.getMethod(methodName);//6.执行方法method.invoke(obj);}}
运行结果
通过反射获取到注解的Class对象, 而其中的属性返回参数, 即使用注解时传入的参数
//其实就是在内存中生成了一个该注解接口的子类实现对象public class ProImpl implements Pro{public String className(){return "cn.itcast.annotation.Demo1";}public String methodName(){return "show";}}
5.处理注解
因为注解定义后也是一种class,所有的注解都继承自java.lang.annotation.Annotation,
因此读取注解,需要使用反射API。
Java提供的使用反射API读取Annotation的方法包括:
判断某个注解是否存在于Class、Field、Method或Constructor:
Class.isAnnotationPresent(Class)Field.isAnnotationPresent(Class)Method.isAnnotationPresent(Class)Constructor.isAnnotationPresent(Class)
例如:
// 判断@Report是否存在于Person类: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注解的类才会进行执行, 执行后有异常的会将原因输出的文件中
