java中的反射原理,为什么要使用反射以及反射使用场景(面试常问)

2023-10-16 10:08:30 浏览数 (1)

java中的反射原理,为什么要使用反射以及反射使用场景

什么是反射

反射是框架的灵魂

JAVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性;这种动态获取的信息以及动态调用对象的方法的功能称为java语言的反射机制。

要想解剖一个类,必须先要获取到该类的字节码文件对象。而解剖使用的就是Class类中的方法.所以先要获取到每一个字节码文件对应的Class类型的对象.

在java中获取字节文件的方式有三种

  1. 任何数据类型(包括基本数据类型)都有一个“静态”的class属性
  2. Object(对象) ——> getClass();
  3. 通过Class类的静态方法:forName(String className)(常用)
代码语言:javascript复制
 		//方法一
        Class<CarEntity> carEntityClass0 = CarEntity.class;

        //方法二
        CarEntity carEntity =new CarEntity();
        Class carEntityClass1 =carEntity.getClass();

        //方法三
        Class carEntityClass2 = Class.forName("com.example.demo3.Entity.CarEntity");

        //判断获取到同一个类的Class对象是否是同一个
        System.out.println(carEntityClass0 == carEntityClass1);
        System.out.println(carEntityClass1 == carEntityClass2);
        System.out.println(carEntityClass0 == carEntityClass2);

上面的例子得到的结果,是三个true,由此我们得到了第一个定理: 在运行期间,一个类,只有一个Class对象产生

三种方式常用第三种,第一种需要导入类的包,依赖太强,不导包就抛编译错误。第二种对象都有了还要反射干什么。一般都第三种,一个字符串可以传入也可写在配置文件中等多种方法(框架中都是用的第三种)。

好,现在我们得到了Class对象了,又有什么用呢,Class对象是什么呢,能做什么呢?

在此之前我们先了解一下正常情况下我们new一个对象的时候,jvm底层做了什么事情。

首先要搞明白一件事情,jvm能读懂我们的java代码吗?不能!

那jvm是靠读取什么东西来运行程序的呢?.class文件! 请放大看下图。。。。

也就是说,我们现在可以不通过JVM的编译直接获取到jvm运行时需要的Class对象! 也就是说!我们是不是可以通过对Class对象进行修改而改变CarEntity这个类原本在jvm里运行的逻辑!从而达到一系列不可告人的目的呢?

没错,我们可以,这就像同桌张三把作业给我让我帮忙交给老师,然后我直接把他的作业全部撕了然后告诉老师(JVM):张三这个崽种没做作业!(这是后面要讲的代理模式)。在当前的反射篇章我们可以理解为,我可以得到张三的作业的所有答案,然后我拿着自己用!

好,例子来了,顺便我们熟悉一下Class对象的常用API,面试的时候就可以装逼了

先看看我们的实体类是什么样子的

代码语言:javascript复制
	//一个public 属性
    public String name;

	//一个private 属性
    private String price;

	//一个public 构造方法
    public CarEntity(String name, String price) {
        this.name = name;
        this.price = price;
    }
    
	//一个private 构造方法
    private CarEntity(String name){
        this.name = name;
    }


	//以下全都是public 的GET,SET方法
    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getPrice() {
        return price;
    }

    public void setPrice(String price) {
        this.price = price;
    }

好!开始测试!

代码语言:javascript复制
    public static void main(String[] args) throws Exception {
        //获取CarEntity的Class对象
        Class carEntityClass = Class.forName("com.example.demo3.Entity.CarEntity");

        System.out.println("获取所有的Public的成员变量");
        Field[] field = carEntityClass.getFields();
        for (Field field1 : field) {
            System.out.println(field1.getName());
        }
        System.out.println("获取所有的的成员变量,不管你是Public,Private,Protected还是Default ");
        Field[] field01 = carEntityClass.getDeclaredFields();
        for (Field field1 : field01) {
            System.out.println(field1.getName());
        }
    }

看看结果是什么

代码语言:javascript复制
获取所有的Public的成员变量
name
获取所有的的成员变量,不管你是Public,Private,Protected还是Default 
name
price

好,再来一个

代码语言:javascript复制
        System.out.println("获取所有的Public的构造方法");
        Constructor[] constructors = carEntityClass.getConstructors();
        for (Constructor constructor1 : constructors) {
            System.out.println(constructor1);
        }
        System.out.println("获取所有的的构造方法,不管你是Public,Private,Protected还是Default ");
        Constructor[] constructors01 = carEntityClass.getDeclaredConstructors();
        for (Constructor constructor1 : constructors01) {
            System.out.println(constructor1);
        }

结果:

代码语言:javascript复制
获取所有的Public的构造方法
public com.example.demo3.Entity.CarEntity(java.lang.String,java.lang.String)
获取所有的的构造方法,不管你是Public,Private,Protected还是Default 
public com.example.demo3.Entity.CarEntity(java.lang.String,java.lang.String)
private com.example.demo3.Entity.CarEntity(java.lang.String)

发现了没?我们现在只需要一个类的全路径,我们就可以掌握这个类的所有情况!

上面的例子我们也发现了Class对象的APi的规律,只要加了Declared的Get方法,我们就能够“非法”地获取到这个类的编写者本来不愿意公布出来的属性!

当然我们还可以获取到这个类的所有普通方法:

代码语言:javascript复制
        System.out.println("获取所有的方法");
        Method[] methods = carEntityClass.getMethods();
        for (Method method : methods) {
            System.out.println(method.getName());
        }
代码语言:javascript复制
获取所有的方法
getName
setName
getPrice
setPrice
wait
wait
wait
equals
toString
hashCode
getClass
notify
notifyAll

我们再继续深入一点点,大家耐心看。

我们先给我们的Car类补上刚刚忘掉的无参构造方法

代码语言:javascript复制
    public CarEntity() {

    }

然后开始我们的测试**(是干嘛呢?通过反射调用目标类的方法!)

代码语言:javascript复制
 //获取CarEntity的Class对象
        Class<?> carEntityClass = Class.forName("com.example.demo3.Entity.CarEntity");
        //通过Class对象获取到具体的CarEntity实例(需要无参构造方法!!!!)
        CarEntity carEntity = (CarEntity)carEntityClass.newInstance();

        System.out.println("获取SetName方法");
        //第一个参数:方法名称,第二个参数:方法形参的类型
        Method method = carEntityClass.getDeclaredMethod("setName",String.class);
        //第一个参数,对象类型carEntity,第二个参数是我这里调用方法时传的参数
        method.invoke(carEntity,"张三");


        System.out.println("获取getName方法");
        Method method2 = carEntityClass.getDeclaredMethod("getName",null);
        String name = (String) method2.invoke(carEntity,null);
        System.out.println(name);
代码语言:javascript复制
获取SetName方法
获取getName方法
张三

我们现在居然只通过一个类的路径,获取到了这个类的所有信息,并且还能调用他的所有方法。

现在是不是大概明白了,为什么一开始说反射是框架的灵魂。举个最简单的例子,Spring的注解式事务是怎么实现的?? 现在我们大概可以猜猜了(只是猜想):

  1. 通过注解,我们在项目启动的时候可以获取所有打了注解的类或方法
  2. 通过反射,我们可以获取类的所有信息或方法的所有信息
  3. 通过反射,我们可以在方法的前后加上事务回滚相关的代码,然后通过上面例子中的invoke方法调用目标方法
  4. 这个过程我不需要知道你这些类或方法是干嘛的,你的一切与我无关

框架就是这样诞生的,更多的细节请看我的其他博客,关于静态代理和动态代理。

好了 基本已经讲完,欢迎大家评论区指出不足,一起学习进步!

大家看完了点个赞,码字不容易啊。。。

0 人点赞