反射和注解及二者综合案例

一、反射

1、反射概述

java.lang.reflect包是这么描述的:

提供类和接口,以获得关于类和对象的反射信息。
在安全限制内,反射允许编程访问关于加载类的字段、
方法和构造方法的信息,并允许使用反射字段、
方法和构造方法对其底层对等项进行操作。 

反射指的是允许以编程方式访问已加载类的成分(成员变量、方法、构造器等)。

认识了反射是什么之后,接下来了解一下反射具体学什么?

因为反射获取的是类的信息,那么反射的第一步首先获取到类才行。由于Java的设计原则是万物皆对象,获取到的类其实也是以对象的形式体现的,叫字节码对象,用Class类来表示。获取到字节码对象之后,再通过字节码对象就可以获取到类的组成成分了,这些组成成分其实也是对象,其中每一个成员变量用Field类的对象来表示每一个成员方法用Method类的对象来表示每一个构造器用Constructor类的对象来表示。即:

  1. 加载类获取类的字节码:Class对象
  2. 获取类的构造器:Constructor对象
  3. 获取类的成员变量:Field对象
  4. 获取类的成员方法:Method对象

下面就开始了挨个的学习!!!

2、获取类的字节码(重要)

反射的第一步:是将字节码加载到内存,我们需要获取到的字节码对象。

在这里插入图片描述

比如有一个Student类,获取Student类的字节码代码有三种写法。不管用哪一种方式,获取到的字节码对象其实是同一个。

public class Test1Class{
    public static void main(String[] args){
        //(方式一)通过Class类的静态方法forName获取  参数为全限定类名
        Class c2 = Class.forName("com.itheima.d2_reflect.Student");
        System.out.println(c1 == c2); //true
        
        //(方式二) 通过类名.class  获取Class对象
        Class c1 = Student.class;
        System.out.println(c1.getName()); //获取全类名
        System.out.println(c1.getSimpleName()); //获取简单类名
        
        // (方式三) 通过Object中的getClass方法获取
        Student s = new Student();
        Class c3 = s.getClass();
        System.out.println(c2 == c3); //true
    }
}

通过比较三种方式得到的Class对象的地址我们可以看到,他们三个是同一个地址,所以通过这三种方式来获取Class对象是等效的。其实这是在类的三个不同的阶段来获取的对象分别是编译,加载和运行阶段。

在知道了如何获取字节码对象之后,我们再来看我们之前总是常用的equals方法就很明确了;

第一步;首先判断两个对象是否为同一个地址。如果是同一个地址的话,就没有必要比较,直接返回。(比较地址)

第二步; 先判断是否为null,为null的话,也没有要比较直接返回false。然后在判断两个对象的字节码对象是否相同。因为同一个类的字节码文件在内存中只有一份,如果不同直接返回(比较字节码),如果相同,就说明肯定是当前类的实现类。强制转换即可

最后:在比较其中的内容。基本数据类型直接比较,引用数据类型通过方法比较。返回比较的结果

    @Override
    public boolean equals(Object o) {
        // this  o
        if (this == o) return true;
        if (o == null || this.getClass() != o.getClass()) return false;

        Student student = (Student) o;

        if (age != student.age) return false;
        return name != null ? name.equals(student.name) : student.name == null;
    }

3、获取类的构造器

反射的第一步是先得到类对象,然后从类对象中获取类的成分对象。

Class类中用于获取构造器的方法

方法说明
Constructor<?>[] getConstructors()返回所有构造器对象的数组(只能拿public的)
Constructor<?>[] getDeclaredConstructors()返回所有构造器对象的数组,存在就能拿到
Constructor getConstructor(Class<?>… parameterTypes)返回单个构造器对象(只能拿public的)
Constructor getDeclaredConstructor(Class<?>… parameterTypes)返回单个构造器对象,存在就能拿到

在记忆方法的时候有个小技巧,首先构造器的单词我们肯定知道Constructor,如果想要获取多带构造器,那么就是用那个名字长的。即 越长越多方法越多。短的就是获取只被public修饰的构造器

示例代码:

public class Cat{
    private String name;
    private int age;
    // public无参构造
    public Cat(){   
    }
    // 私有全参构造
    private Cat(String name, int age){       
    }
}

获得全部的构造器

public class Test2Constructor(){
    @Test
    public void testGetConstructors(){
        //1、反射第一步:必须先得到这个类的Class对象
        Class c = Cat.class;
        
        //2、获取类的全部构造器
        Constructor[] constructors = c.getDeclaredConstructors();
        //3、遍历数组中的每一个构造器对象。
        for(Constructor constructor: constructors){
            System.out.println(constructor.getName()+"---> 参数个数:"+constructor.getParameterCount());
        }
    }
}

控制台结果:

com.yfs1024.test.Cat---> 参数个数:0
com.yfs1024.test.Cat---> 参数个数:2

获取单个构造器

参数需要传入对应参数的class 如果是包装类型使用的是 包装类.TYPE

public class Test2Constructor(){
    @Test
    public void testGetConstructor(){
        //1、反射第一步:必须先得到这个类的Class对象
        Class c = Cat.class;
        
        //2、获取类public修饰的空参数构造器
        Constructor constructor1 = c.getConstructor();
        System.out.println(constructor1.getName()+"---> 参数个数:"+constructor1.getParameterCount());
        
        //3、获取private修饰的有两个参数的构造器,第一个参数String类型,第二个参数int类型
        Constructor constructor2 = 
            c.getDeclaredConstructor(String.class,int.class);
        
        System.out.println(constructor2.getName()+"---> 参数个数:"+constructor1.getParameterCount());

    }
}

控制台结果:

com.yfs1024.test.Cat---> 参数个数:0
com.yfs1024.test.Cat---> 参数个数:2

4、反射获取构造器的作用

获取到构造器后,有什么作用呢?很好想,其实获取构造器的作用:初始化对象并返回

这里我们需要用到如下的两个方法,注意:这两个方法时属于Constructor的,需要用Constructor对象来调用。

符号说明
T newInstance(Object… initargs)根据指定的构造器创建对象
public void setAccessible(boolean flag)设置为true,表示取消访问检查,进行暴力反射

如下图所示,constructor1和constructor2分别表示Cat类中的两个构造器。现在我要把这两个构造器执行起来

在这里插入图片描述

由于构造器是private修饰的,需要调用setAccessible(true) 表示禁止检查访问控制,然后再调用newInstance(实参列表) 就可以执行构造器,完成对象的初始化了。

    @Test
    public void getConstructor() throws Exception {
        Class<Cat> catClass = Cat.class;
        Constructor<Cat> constructor = catClass.getDeclaredConstructor(String.class,int.class);
        constructor.setAccessible(true);  // 关闭检查访问  ---> 暴力反射
        Cat cat = constructor.newInstance("zs",12);
        System.out.println(cat);
    }

学了上面的方法,其实我们在开发中一般只会用一个构造器来获取对象的,从来没有说,来,遍历个构造器,创建一堆对象,所以只需要我们掌握获取一个对象就可以。而且我们一般使用的也是无参构造,我们常用的获取无参构造的方法通常是使用当前字节码Class对象来创建。如下: 前提是构造器不是私有的,如果是那么就是能使用上面的方法了

    @Test
    public void getConstructor() throws Exception {
        Class<Cat> catClass = Cat.class;
        Cat cat = catClass.newInstance();
        System.out.println(cat);
    }

5、反射获取的成员变量和使用

其实套路是一样的,在Class类中提供了获取成员变量的方法,如下图所示。

方法说明
Field[] getFields()返回所有成员变量对象的数组(只能拿public的)
Field[] getDeclaredFields()返回所有成员变量对象的数组,存在就能拿到
Field getField(String name)返回单个成员变量对象(只能拿public的)
Field getDeclaredField(String name)返回单个成员变量对象,存在就能拿到

假设有一个Cat类它有若干个成员变量,用Class类提供 的方法将成员变量的对象获取出来。

在这里插入图片描述

执行完上面的代码之后,我们可以看到控制台上打印输出了,每一个成员变量的名称和它的类型。

在这里插入图片描述

获取到成员变量的对象之后该如何使用呢?

在Filed类中提供给给成员变量赋值和获取值的方法,如下图所示。

符号说明
void set(Object obj, Object value):赋值
Object get(Object obj)获取值。
    @Test
    public void getFiles() throws Exception {   // 成员变量私有,构造器都是public
        Class<Cat> catClass = Cat.class;
        Cat cat = new Cat("小花",23);
        Field name = catClass.getDeclaredField("name");
        name.setAccessible(true);
        name.set(cat,"小花的爸爸");
        System.out.println(name.get(cat)); // 小花的爸爸

    }

强调一下设置值、获取值的方法时Filed类的需要用Filed类的对象来调用,而且不管是设置值、还是获取值,都需要依赖于该变量所属的对象。

6、反射获取成员方法(重要)

在Java中反射包中,每一个成员方法用Method对象来表示,通过Class类提供的方法可以获取类中的成员方法对象。如下:

方法说明
Method[] getMethods()返回所有成员方法对象的数组(只能拿public的)这个连父类的public方法也可以拿到
Method[] getDeclaredMethods()返回所有成员方法对象的数组,存在就能拿到
Method getMethod(String name, Class<?>… parameterTypes)返回单个成员方法对象(只能拿public的)
Method getDeclaredMethod(String name, Class<?>… parameterTypes)返回单个成员方法对象,存在就能拿到

接下来我们还是用代码演示一下:假设有一个Cat类,在Cat类中红有若干个成员方法

public class Cat{
    private String name;
    private int age;
    
    public Cat(){
        System.out.println("空参数构造方法执行了");
    }
    
    private Cat(String name, int age){
        System.out.println("有参数构造方法执行了");
        this.name=name;
        this.age=age;
    }
    
    private void run(){
        System.out.println("(>^ω^<)喵跑得贼快~~");
    }
    
    public void eat(){
        System.out.println("(>^ω^<)喵爱吃猫粮~");
    }
    
    private String eat(String name){
        return "(>^ω^<)喵爱吃:"+name;
    }
    
    public void setName(String name){
        this.name=name;
    }
    public String getName(){
        return name;
    }
    public void setAge(int age){
        this.age=age;
    }
     public int getAge(){
        return age;
    }
}

接下来,通过反射获取Cat类中所有的成员方法,每一个成员方法都是一个Method对象

public class Test3Method{
    public static void main(String[] args){
        //1、反射第一步:先获取到Class对象
        Class c = Cat.class;
        
        //2、获取类中的全部成员方法
        Method[] methods = c.getDecalaredMethods();
        
        //3、遍历这个数组中的每一个方法对象
        for(Method method : methods){
            System.out.println(method.getName()+"-->"+method.getParameterCount()+"-->"+method.getReturnType());
        }
    }
}

方法名 参数个数 返回值类型

getName-->0-->class java.lang.String
run-->0-->void
setName-->1-->void
eat-->1-->class java.lang.String
eat-->0-->void
setAge-->1-->void
getAge-->0-->int

获取单个指定的成员方法:

    public static void main(String[] args) throws Exception {
        //1、反射第一步:先获取到Class对象
        Class c = Cat.class;

        //2、获取类中的eat 方法
        Method eat = c.getDeclaredMethod("eat");
        System.out.println("参数个数:"+eat.getParameterCount());

//        获取 eat(String name)
        Method eat1 = c.getDeclaredMethod("eat", String.class);
        System.out.println("参数个数:"+eat1.getParameterCount());

    }

控制台:

参数个数:0
参数个数:1

获取到成员方法之后,有什么作用呢?

在Method类中提供了方法,可以将方法自己执行起来。

符号说明
Object invoke(Object obj, Object… args)运行方法 参数一:用obj对象调用该方法 参数二:调用方法的传递的参数(如果没有就不写) 返回值:方法的返回值(如果没有就不写)

run()方法和eat(String name)方法执行起来。看分割线之下的代码

        //1、反射第一步:先获取到Class对象
        Class c = Cat.class;
        //4、获取private修饰的run方法,得到Method对象
        Method run = c.getDeclaredMethod("run");
        //执行run方法,在执行前需要取消权限检查
        Cat cat = new Cat();
        run.setAccessible(true);
        Object rs1 = run.invoke(cat); // 无返回值返回null
        System.out.println(rs1);

        //5、获取private 修饰的eat(String name)方法,得到Method对象
        Method eat = c.getDeclaredMethod("eat",String.class);
        eat.setAccessible(true);
        Object rs2 = eat.invoke(cat,"鱼儿");
        System.out.println(rs2);
空参数构造方法执行了
(>^ω^<)喵跑得贼快~~
null
(>^ω^<)喵爱吃:鱼儿

7、反射的应用案例

案例1

下面我们会通过一个使用和不使用反射的两种方式来做个一个对比,来欣赏反射的优美。

需求:有一个水果接口,有一些苹果,橙子等实现类,父类拥有一个获取果汁的方法。还有一个过滤的类,用于对榨取的水果汁进行处理。

不是用反射,我们需要用下面的方法实现:Fruit接口

// 水果接口,有一个获取果汁的方法
public interface Fruit {
    public void getJuice();
}

苹果类

public class Apple implements Fruit{
    @Override
    public void getJuice() {
        System.out.println("榨取苹果汁");
    }
}

橙汁类:

public class Orange implements Fruit{
    @Override
    public void getJuice() {
        System.out.println("得到一杯橙汁");
    }
}

过滤类:

public class MainGuoLv {
    public static void main(String[] args) throws Exception {
       // guoLv(new Apple());
        guoLv(new Orange());
    }
    // 过滤方法
    public static void guoLv(Fruit fruit){
        // 获取果汁
        fruit.getJuice();
        System.out.println("过滤完成");
    }
}

当我们学了多态的好处是我们只需要用父类来接收子类对象,就可以通过一个方法来对不同的水的方法进行处理。

但是有一个问题,如果我现在过滤橙汁了,我就需要创建一个橙汁对象。那么我又想过滤苹果汁我就需要传入一个苹果汁对象。看起来很简单,对吧。但是有一个问题,就是如果这个功能上线,上线之后的代码都是编译之后的class文件,即字节码文件。那如果我们想再改需求,一会儿过滤苹果汁一会过滤橙汁了,那完了,我们需要对java文件进行修改,然后在编译成class文件。这是一个地方修改。那如果过个配置的地方,每次都要重新编译,是不是太费事了。

那我们看看用反射应该怎么解决呢?我们可以操作字节码文件呀

Fruit接口,苹果类,橙子类我们不动,我们只需要对过滤类用反射的方法进行修改:通常的做法是这样的,我们配置一个文件,因为像txt文件是不会编译的(properties文件也可以),我们学过通过类名获取类的字节码对象。有了字节码对象,我们就可以操作这个类中的方法了。实现如下:

准备一个Fruit.txt,内容如下

fruitName=com.yfs1024.reflectTest02.Apple

过滤类修改为反射后代码如下:

public class MainGuoLv {
    public static void main(String[] args) throws Exception {
//        (1)获取文件的全限定类名
        Properties properties = new Properties();
//        (2)加载文件,
        properties.load(new FileInputStream("javaEE-day14\\fruit.txt"));
//        (3)通过键获取值
        String fruitName = properties.getProperty("fruitName");
//        (4)得到了全限定类名,就可以通过Class中的静态方法获取字节码对象
        Class<?> aClass = Class.forName(fruitName);
//        (5)通过字节码文件创建对象
        Fruit fruit = (Fruit)aClass.newInstance();   // 这是常用创建对象的 当然也可以通过获取构造器在创建
//        (6)调用方法
        guoLv(fruit);
    }
//       过滤方法
    public static void guoLv(Fruit fruit) {
        fruit.getJuice();
        System.out.println("过滤完成");
    }
}

可能看起来很多代码,但是已经解除了被java文件的封印。这个时候我们如果想要过滤橙汁就可以直接在Fruit.txt文件中直接修改即可。就不用再去改动源码

fruitName=com.yfs1024.reflectTest02.Orange
案例2:

我们在刚才学习方法时候,也看到了通过字节码对象(Class对象)我们可以获取所有的方法。那么我们可以在方法作一些文章嘛?当然。

有这样的一个工具类:我想通过控制台输入具体的数据,和方法实现调用具体的功能,那么下面还是通过不使用反射和使用反射来看反射的好处

public class MyUtils {

    public static int getSum(int a,int b){
        return a + b;
    }

    public static int minus(int a,int b){
        return a - b;
    }

    public static int multiply(int a,int b){
        return a * b;
    }

    public static int by(int a,int b){
        return a / b;
    }
}

不使用反射的代码:

        Scanner scanner = new Scanner(System.in);
        System.out.println("请输入第一个数");
        int a = scanner.nextInt();
        System.out.println("请输入第二个数");
        int b = scanner.nextInt();
        System.out.println("请输入要执行的方法名");
        String methodName = scanner.next();
// 判断,传入参数执行   这里会发现如果一两个方法还要,如果十个,二十个,那是不是都要写
		if(methodName.equals("getSum")){
            int sum = Utils.getSum(a, b);
            System.out.println(sum);
        }else if(methodName.equals("minus")){
            int sum = Utils.minus(a, b);
            System.out.println(sum);
        }else if(methodName.equals("multiply")){
            int sum = Utils.multiply(a, b);
            System.out.println(sum);
        }else if(methodName.equals("by")){
            int sum = Utils.by(a, b);
            System.out.println(sum);
        }

使用反射来完成需求:

        Scanner scanner = new Scanner(System.in);
        System.out.println("请输入第一个数");
        int a = scanner.nextInt();
        System.out.println("请输入第二个数");
        int b = scanner.nextInt();
        System.out.println("请输入要执行的方法名");
        String methodName = scanner.next();
        Class<Utils> utilsClass = MyUtils.class;
        Method method = utilsClass.getMethod(methodName, int.class, int.class);//getSum
// 以为是静态方法,所以不需要传入对象,传null即可
        Object result = method.invoke(null,a,b);
        System.out.println(result);

而且,不管有多少个类似的方法都可以,当然,这里因为是举例,所以就把参数都一样,如果不一样,我们可以通过通过获取假如一些判断,在getMethod时,以及invoke时传入一个数组,因为第二个参数都是可变参数嘛.

二、注解

1、认识注解&定义注解

注解和反射一样,都是用来做框架的,我们这里学习注解的目的其实是为了以后学习框架或者做框架做铺垫的。

那注解该怎么学呢?和反射的学习套路一样,我们先充分的认识注解,掌握注解的定义和使用格式,然后再学习它的应用场景。

先来认识一下什么是注解?

Java注解是代码中的特殊标记,比如@Override、@Test等,作用是:让其他程序根据注解信息决定怎么执行该程序。

比如:Junit框架的@Test注解可以用在方法上,用来标记这个方法是测试方法,被@Test标记的方法能够被Junit框架执行。

再比如:@Override注解可以用在方法上,用来标记这个方法是重写方法,被@Override注解标记的方法能够被IDEA识别进行语法检查。

在这里插入图片描述

  • 注解不光可以用在方法上,还可以用在类上、变量上、构造器上等位置。

上面我们说的@Test注解、@Overide注解是别人定义好给我们用的,将来如果需要自己去开发框架,就需要我们自己定义注解。所以下面能会就会,不会要会怎样去使用

接着我们学习自定义注解

自定义注解的格式如下图所示
在这里插入图片描述

下面的注释和注意事项很重要

在注解中:限制了抽象方法的返回值类型只能是以下
       基本类型
       字符串类型
       字节码类型
       枚举类型
       注解类型
       以上类型的一维数组
           
   抽象方法不能有参数
           
注意事项:1)当方法名为value的时候, 使用注解, 如果仅仅只是对value赋值的时候, value可以省略不写
       (2)当方法的返回值为数组, 但是你的值只有一个的时候,{}可以省略不写!!!!3)我们的抽象方法可以通过default给定默认返回值!!!,如果给了默认返回值使用者可以不用重写!!!!!!!!!!!!

注解可以定义的方式如下:

public @interface A {
    //定义常量
    int age = 10;
    //定义抽象方法
    int value() default 18;
    String method1() default "jack";
    String[] method2() default {};
    Class method3();
    Week method4();    // 枚举
    Override method5();
    String[] method6();
}

比如:现在我们自定义一个MyTest注解

public @interface MyTest{
    String aaa();
    boolean bbb() default true;	//default true 表示默认值为true,使用时可以不赋值。
    String[] ccc();
}

定义好MyTest注解之后,我们可以使用MyTest注解在类上、方法上等位置做标记。注意使用注解时需要加@符号,如下

@MyTest1(aaa="牛魔王",ccc={"HTML","Java"})
public class AnnotationTest1{
    @MyTest(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{
    @MyTest(aaa="铁扇公主",bbb=false, ccc={"Python","前端","Java"})
    public void test1(){
        
    }
}
注解本质是什么呢?

想要搞清楚注解本质是什么东西,我们可以把注解的字节码进行反编译,使用XJad工具进行反编译。经过对MyTest1注解字节码反编译我们会发现:

1.MyTest1注解本质上是接口,每一个注解接口都继承子Annotation接口
2.MyTest1注解中的属性本质上是抽象方法
3.@MyTest1实际上是作为MyTest接口的实现类对象
4.@MyTest1(aaa="孙悟空",bbb=false,ccc={"Python","前端","Java"})里面的属性值,可以通过调用aaa()bbb()ccc()方法获取到。 【别着急,继续往下看,在解析注解时我们会用到】

在这里插入图片描述

2,元注解

接下来我们还需要学习几种特殊的注解,叫做元注解。

什么是元注解?

即:修饰注解的注解 哈哈,有一点绕,但是很好理解

是这样,我们通过前面的反射我们大致知道从java源码到真正的运行,这个中间有很多的阶段,所以我们就需要通过一个注解来告诉jvm这个类或者方法,在什么时候才是我们需要的。其次我们可以通过一些注解来约束这个注解使用的位置,是在类呀,还是方法呀,还是成员变量呀。

这两个注解分别是@Target、@Retetion

@Target是用来声明注解只能用在那些位置,比如:类上、方法上、成员变量上等
@Retetion是用来声明注解保留周期,比如:源代码时期、字节码时期、运行时期

在这里插入图片描述

注意:我们常用的让注解保留的周期是 RUNTIME

示例:

@Target元注解的使用:比如定义一个MyTest3注解,并添加@Target注解用来声明MyTest3的使用位置

接下来,我们把@MyTest3用来类上观察是否有错,再把@MyTest3用在方法上、变量上再观察是否有错

在这里插入图片描述

如果我们定义MyTest3注解时,使用@Target注解属性值写成下面样子

//声明@MyTest3注解只能用在类上和方法上
@Target({ElementType.TYPE,ElementType.METHOD})	
public @interface MyTest3{
    
}

到这里@Target元注解的使用可能就已经游刃有余了,下面在学习@Retetion元注解

@Retetion元注解的使用:定义MyTest3注解时,给MyTest3注解添加@Retetion注解来声明MyTest3注解保留的时期

@Retetion是用来声明注解保留周期,比如:源代码时期、字节码时期、运行时期
	@Retetion(RetetionPloicy.SOURCE): 注解保留到源代码时期、字节码中就没有了
	@Retetion(RetetionPloicy.CLASS): 注解保留到字节码中、运行时注解就没有了
	@Retetion(RetetionPloicy.RUNTIME):注解保留到运行时期

【自己写代码时,比较常用的是保留到运行时期】

//声明@MyTest3注解只能用在类上和方法上
@Target({ElementType.TYPE,ElementType.METHOD})	
//控制使用了@MyTest3注解的代码中,@MyTest3保留到运行时期
@Retetion(RetetionPloicy.RUNTIME)
public @interface MyTest3{
    
}

3,解析注解(注解和反射综合案例)

通过前面的学习我们能够自己定义注解,也能够把自己定义的注解标记在类上或者方法上等位置,但是总感觉有点别扭,给类、方法、变量等加上注解后,我们也没有干什么呀!!!

接下来,我们就要做点什么。我们可以通过反射技术把类上、方法上、变量上的注解对象获取出来,然后通过调用方法就可以获取注解上的属性值了。我们把获取类上、方法上、变量上等位置注解及注解属性值的过程称为解析注解。

解析注解套路如下

1.如果注解在类上,先获取类的字节码对象,再获取类上的注解
2.如果注解在方法上,先获取方法对象,再获取方法上的注解
3.如果注解在成员变量上,先获取成员变量对象,再获取变量上的注解
总之:注解在谁身上,就先获取谁,再用谁获取谁身上的注解

注解的操作中经常需要进行解析,注解的解析就是判断是否存在注解,存在注解就解析出内容。

与注解解析相关的接口

Annotation: 注解的顶级接口,注解都是Annotation类型的对象
AnnotatedElement:该接口定义了与注解解析相关的解析方法

方法说明
Annotation[] getDeclaredAnnotations()获得当前对象上使用的所有注解,返回注解数组。
T getDeclaredAnnotation(Class annotationClass)根据注解类型获得对应注解对象
boolean isAnnotationPresent(Class annotationClass)判断当前对象是否使用了指定的注解,如果使用了则返回true,否则false

所有的类成分Class, Method , Field , Constructor,都实现了AnnotatedElement接口他们都拥有解析注解的能力:

解析来看一个案例,来演示解析注解的代码编写

(1)定义个MyTest4的注解

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE,ElementType.METHOD})
public @interface MyTest4 {
    String value();

    double aaa() default 100;

    String[] bbb();
}

(2)定义一个类Demo

@MyTest4(value = "类上value的薛定谔的猫",aaa = 100,bbb = {"类上的bbb的拉普拉斯兽","类上的bbb的拉普拉斯兽2"})
public class Demo {
    public static void main(String[] args) {

    }
    @MyTest4(value = "方法上的芝诺的乌龟",aaa = 800,bbb = {"方法上的麦克斯韦妖1","方法上的麦克斯韦妖2"})
    private void test1() {

    }
}

③ 写一个测试类AnnotationTest3解析Demo类上的MyTest4注解

public class AnnotationTest3 {
    public static void main(String[] args) throws NoSuchMethodException {
//        获得使用注解的类的字节码对象
        Class<Demo> demoClass = Demo.class;
//        获取类中的注解
        MyTest4 annotation = demoClass.getAnnotation(MyTest4.class);
//        获取类中MyTest4注解的value属性
        String value = annotation.value();
        System.out.println("类中value的值:" + value);
//        获取类中MyTest4注解的value属性
        double aaa = annotation.aaa();
        System.out.println("类中的aaa的值:" + aaa);
//        获取类中的MyTest4注解中的bbb属性
        String[] bbb = annotation.bbb();
//        遍历输出
        for (String s : bbb) {
            System.out.println("类中bbb[]的值:" + s);
        }

        //(2)获取方法中的
        Method test1 = demoClass.getDeclaredMethod("test1");
        MyTest4 annotation1 = test1.getAnnotation(MyTest4.class);
//        获取方法中MyTest4注解的value属性
        String value1 = annotation1.value();
        System.out.println("方法中value的值:" + value1);
//        获取方法中MyTest4注解的value属性
        double aaa1 = annotation1.aaa();
        System.out.println("方法中的aaa的值:" + aaa1);
//        获取方法中的MyTest4注解中的bbb属性
        String[] bbb1 = annotation1.bbb();
//        遍历输出
        for (String s : bbb1) {
            System.out.println("方法中bbb[]的值:" + s);
        }

    }
}
类中value的值:类上value的薛定谔的猫
类中的aaa的值:100.0
类中bbb[]的值:类上的bbb的拉普拉斯兽
类中bbb[]的值:类上的bbb的拉普拉斯兽2
方法中value的值:方法上的芝诺的乌龟
方法中的aaa的值:800.0
方法中bbb[]的值:方法上的麦克斯韦妖1
方法中bbb[]的值:方法上的麦克斯韦妖2

4,注解的应用场景

接下来,我们再学习一下注解的应用场景,注解是用来写框架的,比如现在我们要模拟Junit写一个测试框架,要求有@MyTest注解的方法可以被框架执行,没有@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);
        	}
        }
    }
}
  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

yfs1024

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值