注解本质和原理
注解的本质就是,定义了一个接口实现了java.lang.annotation.Annotation 接口。我们定义一个注解类,然后进行编译,再反编译。如下,我们定义一个自定义的注解
public @interface MyAno {
int a() default 0;
boolean b() default true;
String c() default "c";
}
然后使用javac将其编译生成class文件,再使用javap 反编译命令,将生成的class文件进行反编译得到如下
public interface com.demo.annotation.MyAno extends java.lang.annotation.Annotation {
public abstract int a();
public abstract boolean b();
public abstract java.lang.String c();
}
所以注解的本质还是接口。
jdk 1.5 内置注解
jdk 有三个内置注解,他们都是编译期注解,即只会存在.java文件中。编译之后就自动去掉了。
- @Override 当一个接口的实现类,覆盖父类的方法,上面添加该注解。
- @Deprecated 添加在方法上,表名该方法已经废弃
- @SuppressWarnings() 相当于忽略警告(接受一个数组类型的参数,忽略的警告类型),
如下图
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ED9Cg6vF-1584887157656)(https://raw.githubusercontent.com/ligengithub/pictest/master/suppressWarning.png)]
使用了@SuppressWarnings(“deprecation”) 之后,过期的方法就不会显示警告。
注解定义和应用场景
注解有三大应用场景
- 生成文档,如 @Document 注解 配合@see,@param,@return 等注解,可以使用javadoc命令,可以直接生成api文档。
- 编译检查,如@Override
- 通过自定义注解,配置属性,配合反射。在运行期获取注解的属性值,做相关用处。
元注解
所谓元注解就是,用来标注注解的注解。有下面四种。
- @Target
指定该注解的修饰的范围,参数有如下取值
1.CONSTRUCTOR:用于描述构造器
2.FIELD:用于描述域
3.LOCAL_VARIABLE:用于描述局部变量
4.METHOD:用于描述方法
5.PACKAGE:用于描述包
6.PARAMETER:用于描述参数
7.TYPE:用于描述类、接口(包括注解类型) 或enum声明
定义了target 之后,如果使用注解 标记的位置和Target指定的不一致会编译报错,这样也可以避免我们将注解添加到错误的位置
- @Rentention 生命周期
1.SOURCE:在源文件中有效(即源文件保留)
2.CLASS:在class文件中有效(即class保留)
3.RUNTIME:在运行时有效(即运行时保留)
当如果你想使用自定义注解,在程序运行中,获取注解的属性值,需要将生命周期设置位RUNTIME
-
@Documented 标记注解,没有属性,标记之后,可以使用javaDoc 生成api文档
-
@Inherited @nbsp;可被继承 也是一个标记注解,如果你使用@Inherited 标记了一个注解A。
那么被注解A 标注的类,的子类,也会被A标记。
自定义注解
自定义注解,最大的作用就是配置功能。定义自定义注解的属性,然后在程序运行中通过读取注解的属性值来改变程序的行为。一个简单的自定义注解如下。 在接口中我们称之为方法名的,在注解中我们称之为属性。
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
public @interface MyAno2 {
// 属性返回值类型 可以是所有的基本类型
int a();
boolean b();
// 引用类型
String c();
// 注解类型
MyAno d();
// 枚举类型
EnumDemo f() default EnumDemo.A;
// 数组类型,可以是上面任何一中类型的数组
String[] e();
}
注解定义好了,怎么用呢,如下。
@MyAno2(a = 1, b = true, c = "xixi", d = @MyAno(), f = EnumDemo.B, e = {"1", "2"})
public class Test {
public int a;
public boolean b;
public String c;
public MyAno d;
public EnumDemo f;
public String[] e;
@Override
public String toString() {
return "Test{" +
"a=" + a +
", b=" + b +
", c='" + c + '\'' +
", d=" + d +
", f=" + f +
", e=" + Arrays.toString(e) +
'}';
}
}
然后,通过反射,可以获取到注解的属性值。。然后我们就可以拿属性值来做任何我们想做的事情。
public static void testAnoOnClass() throws IllegalAccessException, InstantiationException {
Class<Test> testClass = Test.class;
MyAno2 annotation = testClass.getAnnotation(MyAno2.class); // 获取类上的注解
Test test = testClass.newInstance();
test.a = annotation.a();
test.b = annotation.b();
test.c = annotation.c();
test.d = annotation.d();
test.e = annotation.e();
test.f = annotation.f();
System.out.println(test);
}
上面是定义在类上面的,下面再举个定义再属性上的例子
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAno3 {
// 属性返回值类型 可以是所有的基本类型
String value() default "13412";
}
public class Entry {
@MyAno3("hahahhaha")
private String value;
public String getValue() {
return value;
}
public void setValue(String value) {
this.value = value;
}
@Override
public String toString() {
return "Entry{" +
"value='" + value + '\'' +
'}';
}
}
public static void testOnMethod() throws NoSuchFieldException {
Entry entry = new Entry();
Field field = entry.getClass().getDeclaredField("value"); // 获取属性上的注解
System.out.println(field.getAnnotation(MyAno3.class).value());
}
这里需要注意的是
- 1 注解在不同的位置,获取注解的方法不同,比如注解在类上,可以直接使用该类的class对象获取注解。定义在属性上,需要先通过class对象获取到该属性,然后使用属性的getAnnotation()方法。其它部位类似。
- 当注解只有一个属性的时候,我们通常将属性名字定义为value,这样在使用注解的时候,可以直接如下,不用指定属性名。
@MyAno3("hahahhaha")
总结:注解本质上是定于i了一个接口,这个接口中的方法,我们称之为属性。我们通过设置属性的值,来将我们的参数,传递进代码中。 所以自定义注解通常用在做一些自定义的配置中。
简单的一个demo
试下一个简单的demo,对@Check注解标注的函数,进行测试。如果测试函数抛出异常则将异常记录到文件。
https://github.com/ligengithub/java2020.git