什么是注解
注解就是Java注解是代码中的特殊标记,比如@Override、@Test等,作用是:让其他程序根据注解信息决定怎么执行该程序。
注解使用场景
- 比如:Junit框架的@Test注解可以用在方法上,用来标记这个方法是测试方法,被@Test标记的方法能够被Junit框架执行。
- 再比如:@Override注解可以用在方法上,用来标记这个方法是重写方法,被@Override注解标记的方法能够被IDEA识别进行语法检查。
- 注解不光可以用在方法上,还可以用在类上、变量上、构造器上等位置。
上面我们说的@Test注解、@Overide注解是别人定义好给我们用的,将来如果需要自己去开发框架,就需要我们自己定义注解。
自定义注解
就是自己定义注解。
public @interface 注解名称{
public 属性类型 属性名()default 默认值,
}
比如:现在我们自定义一个MyTest注解,注意在idea新建的时候不是class了,而是Annotation
public @interface MyTest1{
String aaa();//一定要带小括号 前面的public可以不写 默认自动帮你写了
boolean bbb() default true; //default true 表示默认值为true,使用时可以不赋值。
String[] ccc();
}
定义好MyTest注解之后,我们可以使用MyTest注解在类上、方法上等位置做标记。注意使用注解时需要加@符号,如下
@MyTest1(aaa="牛魔王",ccc={"HTML","Java"})
public class AnnotationTest1{
@MyTest1(aaa="铁扇公主",bbb=false, ccc={"Python","前端","Java"})
public void test1(){
}
}
注意:注解的属性名如果是value的话,并且只有value没有默认值,使用注解时value名称可以省略。比如现在重新定义一个MyTest2注解
public @interface MyTest2{
String value(); //特殊属性
int age() default 10;
}
定义好MyTest2注解后,再将@MyTest2标记在类上,此时value属性名可以省略,代码如下
@MyTest2("孙悟空") //等价于 @MyTest2(value="孙悟空")
@MyTest1(aaa="牛魔王",ccc={"HTML","Java"})
public class AnnotationTest1{
@MyTest1(aaa="铁扇公主",bbb=false, ccc={"Python","前端","Java"})
public void test1(){
}
}
注解的本质
想要搞清楚注解本质是什么东西,我们可以把注解的字节码进行反编译,给AnnotationTest1类加个main,运行一下,会使用到的注解都编译成class,再使用XJad工具对class进行反编译。经过对MyTest1注解字节码反编译我们会发现:
1.MyTest1注解本质上是接口,每一个注解接口都继承子Annotation接口
2.MyTest1注解中的属性本质上是抽象方法
3.@MyTest1实际上是作为MyTest接口的实现类对象
4.@MyTest1(aaa="孙悟空",bbb=false,ccc={"Python","前端","Java"})里面的属性值,可以通过调用aaa()、bbb()、ccc()方法获取到。
元注解
什么是元注解?
元注解是修饰注解的注解。
常见的元注解有:@Target注解和@Retention注解
@Target注解
作用:声明被修饰的注解只能在哪些位置使用
格式:@Target(ElementType.TYPE)
1. TYPE,类,接口
2. FIELD,成员变量
3. METHOD,成员方法
4. PARAMETER,方法参数
5. CONSTRUCTOR, 构造器
6. LOCAL VARIABLE,局部变量
@Retention注解
作用:声明注解的保留周期。
格式:@Retention(RetentionPolicy.RUNTIME)
1. SOURCE
只作用在源码阶段,字节码文件中不存在。
2. CLASS(默议值)
保留到字节码文件阶段,运行阶段不存在.
3. RUNTIME(开发常用)
一直保留到运行阶段。
注解的解析
注解解析的概述
- 什么是注解的解析?
就是判断类上、方法上、成员变量上是否存在注解,并把注解里的内容给解析出来。 - 如何解析注解?
- 指导思想:要解析谁上面的注解,就应该先拿到谁。
- 比如要解析类上面的注解,则应该先获取该类的Class对象,再通过Class对象解析其上面的注解。
- 比如要解析成员方法上的注解,则应该获取到该成员方法的Method对象,再通过Method对象解析其上面的注解。
- Class、 Method、 Field, Constructor、都实现了AnnotatedElement接口,它们都拥有解析注解的能力。
注解解析的案例
需求如下:
按照需求要求一步一步完成:
- 先定义一个MyTest4注解
//声明@MyTest4注解只能用在类上和方法上
@Target({ElementType.TYPE, ElementType.METHOD})
//控制使用了@MyTest4注解的代码中,@MyTest4保留到运行时期
@Retention(RetentionPolicy.RUNTIME)
public @interface MyTest4{
String value();
double aaa() default 100;
String[] bbb();
}
- 定义一个类Demo
@MyTest4(value="蜘蛛侠",aaa=99.9, bbb={"至尊宝","牛马"})
public class Demo{
@MyTest4(value="孙悟空",aaa=199.9, bbb={"紫霞","牛夫人"})
public void test1(){
}
}
- 写一个测试类AnnotationTest3解析Demo类上的MyTest4注解
public class AnnotationTest3{
@Test
public void parseClass(){
//1.先获取Class对象
Class c = Demo.class;
//2.解析Demo类上的注解
if(c.isAnnotationPresent(MyTest4.class)){
//获取类上的MyTest4注解
MyTest4 myTest4 = (MyTest4)c.getDeclaredAnnotation(MyTest4.class);
//获取MyTests4注解的属性值
System.out.println(myTest4.value());
System.out.println(myTest4.aaa());
System.out.println(Arrays.toString(myTest4.bbb()));
}
}
@Test
public void parseMethods() throws NoSuchMethodException {
//1.先获取Class对象
Class c = Demo.class;
//2.解析Demo类中test1方法上的注解MyTest4注解
Method m = c.getDeclaredMethod("test1");
if(m.isAnnotationPresent(MyTest4.class)){
//获取方法上的MyTest4注解
MyTest4 myTest4 = (MyTest4)m.getDeclaredAnnotation(MyTest4.class);
//获取MyTests4注解的属性值
System.out.println(myTest4.value());
System.out.println(myTest4.aaa());
System.out.println(Arrays.toString(myTest4.bbb()));
}
}
}
- 运行结果
parseClass()的运行结果如下:
parseMethods()的运行结果如下:
应用场景案例
模拟unit框架
需求
定义若干个方法,只要加了@MyTest注解,就会触发该方法执行,没有@MyTest注解的方法不能被框架执行。
分析
- 定义一个自定义注解MyTest,只能注解方法,存活范围是一直都在。
- 定义若干个方法,部分方法加上@MyTest注解修饰,部分方法不加。
- 模拟一个junit程序,可以触发加了@MyTest注解的方法执行。
实现步骤
- 第一步:先定义一个MyTest注解
@Target(ElementType.METHOD)
@Retetion(RetetionPloicy.RUNTIME)
public @interface MyTest{
}
第二步:写一个测试类AnnotationTest4,在类中定义几个被@MyTest注解标记的方法
public class AnnotationTest4{
@MyTest
public void test1(){
System.out.println("=====test1====");
}
@MyTest
public void test2(){
System.out.println("=====test2====");
}
public void test3(){
System.out.println("=====test2====");
}
public static void main(String[] args){
AnnotationTest4 a = new AnnotationTest4();
//1.先获取Class对象
Class c = AnnotationTest4.class;
//2.解析AnnotationTest4类中所有的方法对象
Method[] methods = c.getDeclaredMethods();
for(Method m: methods){
//3.判断方法上是否有MyTest注解,有就执行该方法
if(m.isAnnotationPresent(MyTest.class)){
m.invoke(a);
}
}
}
}
运行结果
=====test2====
=====test1====
原理就是反射 + 注解 实现。