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

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

什么是反射

反射是框架的灵魂

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

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

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

  1. 任何数据类型(包括基本数据类型)都有一个"静态"的class属性
  2. Object(对象) ------> getClass();
  3. 通过Class类的静态方法:forName(String className)(常用)
java 复制代码
 		//方法一
        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,面试的时候就可以装逼了

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

java 复制代码
	//一个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;
    }

好!开始测试!

java 复制代码
    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());
        }
    }

看看结果是什么

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

好,再来一个

java 复制代码
        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);
        }

结果:

java 复制代码
获取所有的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方法,我们就能够"非法"地获取到这个类的编写者本来不愿意公布出来的属性!

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

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

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

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

java 复制代码
    public CarEntity() {

    }

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

java 复制代码
 //获取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);
java 复制代码
获取SetName方法
获取getName方法
张三

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

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

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

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

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

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

相关推荐
IT毕设实战小研7 分钟前
基于大数据的跨国外派人员适应满意度与留存影响因素可视化分析
android·java·大数据·python·django·课程设计
letisgo510 分钟前
JAVA 高级进阶10篇《消息队列实战:RocketMQ/Kafka选型与“不丢不重有序“三连解》
java·面试·kafka·消息队列·rocketmq
2401_8504811715 分钟前
UVA101
java
平头哥AI21 分钟前
Day 21 _ error 是个普通值_errors.New 与 fmt.Errorf 造出来,沿调用栈抛到 main 接住
后端·学习·golang·go
顶点多余22 分钟前
9.10日面试遇到问题总结
面试·职场和发展
Lyyaoo.25 分钟前
【回溯】【中等】全排列
java·数据结构·算法
xixingzhe225 分钟前
spring boot对接Dify
java·spring boot·dify
数据狐(Datafox)27 分钟前
京东商品详情API接口解析(附 JSON 样例)
java·开发语言·json
写后端的胖头鱼29 分钟前
一文讲懂JVM与调优
jvm·后端·算法·架构·jvm调优
步行cgn30 分钟前
Spring Boot 指定数据来源详解
spring boot·后端·python