注解的介绍
注解的概念
注解(annotation),也叫元数据。一种代码级说明。它是JDK1.5及以后版本引入的一个特性,与接口,类,枚举是同一个层次。它可以声明在 包,类,字段,方法,局部变量,方法参数等的前面,用来对这些元素进行说明,注释。(说明程序,给计算机看的)
注解的分类
1.编写文档:通过代码里的注解生成文档(生成文档doc文档)
2.代码分析:通过代码里的注解对代码进行分析(使用于反射)
3.编译检查:通过代码里的注解让编译器能够实现基本的编译检查(Override)
JDk中预定义了许多注解,我们在编写代码中经常用到的有 :@Override,用于检测该注解的方法是否是继承子父类接口; @Deprecated,该注解标识的内容会被系统认定为过期方法或过期类。通俗的将就是原有的方法已经在性的JDK版本中得到升级,旧的方法考虑到,某些用户正在用老版本的jdk不兼容新的方法,便保留老方法标识为已过期。具体表现形式就是在方法名中划一横,但其功能还是能够使用的。@SuppressWarning:压制警告,一般传参all。用于消除程序中的无用警告。
自定义注解介绍
注解除了JDK预定义的注解之外,我们还可以自己定义低级的注解。注解定义的格式如下:
格式
元注解
public @interface 注解名称{}
我们同构将上述代码文件的字节码文件进行反编译我们会发现,注解的本质就是一个接口,该接口默认继承了Annotation接口而已。
接口中的成员变量等我们称之为属性注解的属性是有要求的:
1.属性返回值类型只能是下面类型
*基本数据类型
*String
*枚举
*注解
*以上类型的数组
2.定义了属性,在使用注解时要给属性赋值
*如果定义属性时,使用default关键字给属性默认初始化值,这在注释中,可以不給该属性赋值。
*如果只用一个属性需要赋值,并且属性名称时value,则value可以省略,直接定义数值
*数组赋值时,值使用大括号包裹。枚举赋值:per=枚举类名.枚举成员,注解赋值:a=@注解名
如下代码就是没有赋值属性与赋值属性的注解的使用:
//自定义注解
package com.Javaweb.demoannotation;
public @interface MYzhu {
int i();
String name()default "张三";
}
//注解的调用代码
@MYzhu(i=1)//注解属性赋值
public class demo01 extends person{
}
自定义注解除了注解内部的属性,还有一个就是元注解,元注解是用于描述注解的注解,其本质上还是注解。元注解一共有三个分别如下:
1.@Target:描述注解能够作用的位置,属性只有一个value[]是一个枚举类型的数组
ElementType取值:
TYPE:可以作用于类上
METHOD:可以作用于方法上
FIELD:可以作用于成员变量上
2.@Retention:描述注解被保留的阶段,属性只有一个value是一个枚举类型的赋值
@Retention(RetentionPolicy.RUNTIME):当前被描述的注解,会保留到class字节码中,并被JVM读取到
3.@Documented:描述注解是否被抽取到API文档中
4.@Inherited:描述注解是否能被子类继承
自定义注解的使用
在前面我们介绍了反射,并举例使用反射实现任意类的的类名获取以及方法的调用,例子中是通过配置文件来实现字节码的获取。在这里注解能通过注解的属性实现类名方法名的获取,而且在实际运用中注解的使用更为方便。
首先我们需要自定义注解,注解的属性有两个,一个是类名,一个是方法名。注解的定义范围是类或者方法。
//自定义注解
package com.Javaweb.demoannotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/*
* 描述执行的类名个方法名*/
@Target({ElementType.TYPE,ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface pro {
String className();
String methodName();
}
然后我们通过程序使用的注解获取注解定义的属性值,步骤如下
1.获取注解定义位置的对象(class,methode,field)
2.获取指定的注解getAnnotation(Class)
其实就是在内存中生成一该注解接口的个子类对象
public class PorImpl implements Pro(){
public String className(){
return "com.Javaweb.reflectman.person";
}
public String methodName(){
return "eat";
}
}
3.调用注解的中抽象方法获取配置的属性值
实现代码如下:
package com.Javaweb.demoannotation;
import java.io.InputStream;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.Properties;
import static java.lang.Class.forName;
@pro(className = "com.Javaweb.reflectman.person",methodName = "eat")
public class demoExample {
public static void main(String[] args) throws Exception {
//1.获取注解
//1.1获取该类的字节码文件对象
Class<demoExample> re=demoExample.class;
//2.获取上面的注解对象
pro anntion= re.getAnnotation(pro.class);//其实是在内存中生成的一个该注解接口的子类实现对象
//3.调用注解对象中的抽象方法,获取返回值
String className=anntion.className();
String methodName=anntion.methodName();
//加载进内存
Class cls= forName(className);
//4.创建对象
Object obj= cls.newInstance();//过时方法
//5.获取对象方法
Method me= cls.getMethod(methodName);
//6.执行方法
me.invoke(obj);
//方法的注解解析
demoExample de=new demoExample();
de.p();
}
@pro(className = "com.Javaweb.reflectman.person",methodName = "eat")
public void p() throws Exception {
//获取该类的class文件
Class re=demoExample.class;
//获取该类被注解的方法
Method p=re.getMethod("p");
//生成注解对象
pro c= p.getAnnotation(pro.class);
//得到类路径以及方法名
String name=c.className();
String method=c.methodName();
//将该类加载进内存
Class cls =forName(name);
//生成类对象
Object obj=cls.newInstance();
//获取方法对象
Method m=cls.getMethod(method);
//执行方法
m.invoke(obj);
}
}
在代码中我们一共实现了两种不同位置的注解获取属性值,一个注解在类上,一个注解在方法上,后者比前者只是多了一步方法的获取而已。
练习题
要求:实现下述代码中有注解部分的检测以及错误的抛出:
package com.Javaweb.demoannotation.test;
public class Calculator {
@Check
public void add(){
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....");
}
}
实现方法:
package com.Javaweb.demoannotation.test;
import java.io.BufferedWriter;
import java.io.FileWriter;
import java.io.IOException;
import java.lang.management.BufferPoolMXBean;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
/*
* 简单的测试框架
* 当主方法执行后,会自动执行被检查的所有方法(加了check注解的方法),判断是否有异常,自动记录到文档中*/
public class techercheck {
public static void main(String[] args) throws Exception {
//1.创建计算机对象
Calculator cal=new Calculator();
//2.获取计算机字节码问价
Class cs=cal.getClass();
//3.获取字节码文件的所有方法
Method[] m= cs.getMethods();
int num=0;//出现异常的次数
BufferedWriter bw=new BufferedWriter(new FileWriter("bug.txt"));//创建文件抛出异常
for (Method method : m) {
//4.判断方法上是否有check注解
if(method.isAnnotationPresent(Check.class)){
//有,就执行
try {
method.invoke(cal);
} catch (Exception e) {
e.printStackTrace();
//6.捕获异常
//7.记录到文件中
num++;
bw.write(method.getName()+"方法出异常了");
bw.newLine();
bw.write("异常的名称:"+e.getCause().getClass().getSimpleName() );
bw.newLine();
bw.write("异常的原因:"+e.getCause().getMessage());
bw.write("==================================");
}
}
}
bw.write("本次测试异常次数:"+num);
bw.flush();
bw.close();
}
}
最后的实现结果是抛出一个bug.txt文件记录所有的错误。