0


【Java高级】框架底层基础:Java的反射机制剖析

💁 个人主页:黄小黄的博客主页
❤️ 支持我:👍 点赞 🌷 收藏 🤘关注
🎏 格言:All miracles start from sometime somewhere, make it right now.
本文来自专栏:JavaSE从入门到精通
在这里插入图片描述


文章目录


1 为什么需要反射?

不知道大家有没有思考过一个问题,为什么需要反射?
通过我们以前对Java的学习,我们知道,可以通过 new 的方式,来创建一个对象,并调用对象中的方法。

但是在实际开发中,可以说很多框架都有一个需求:通过配置文件,实现在不修改源码的情况下,实现需求。

那么问题就出现了,如果没有反射机制,我们就没办法通过配置文件来创建对象… …
在这里插入图片描述

例子需求如下:

创建一个Cat类,Cat类里包含两个方法,一个为hi,调用会打印hello,另一个方法为say,调用会打印喵喵喵。
请你在main函数中创建Cat类对象,分别调用这两个方法。

Cat类代码如下:

classCat{publicvoidhi(){
        System.out.println("hello");}publicvoidsay(){
        System.out.println("喵喵喵");}}

常规调用方法如下:

        Cat cat =newCat();
        cat.say();

显然这样是可行的。
但是,当我们需要调用的方法不是say,而是hi时,就需要修改源码了。

有没有一种方式,能够让我们,仅仅通过外部文件的配置,在不修改源码的情况下,来控制程序呢?

我们尝试 通过Java的反射机制来解决, 相应的源码已经有了注释:

import java.io.FileInputStream;import java.io.IOException;import java.lang.reflect.InvocationTargetException;import java.lang.reflect.Method;import java.util.Properties;/**
 * @author 兴趣使然黄小黄
 * @version 1.0
 * 走进反射
 */publicclassTest{publicstaticvoidmain(String[] args)throws ClassNotFoundException, InstantiationException,
            IllegalAccessException, NoSuchMethodException, InvocationTargetException, IOException {//读取配置文件
        Properties properties =newProperties();
        properties.load(newFileInputStream("D:\\Ideaproject2021\\JavaSE\\src\\reflection\\re.properties"));
        String classFullPath = properties.getProperty("classFullPath");
        String method = properties.getProperty("method");//1.加载类,返回Class类型的对象
        Class aClass= Class.forName(classFullPath);//2.通过 aClass 得到加载的类 Cat 实例
        Object o = aClass.newInstance();
        System.out.println("o 的运行类型="+ o.getClass());//3.通过 aClass 得到加载的类 Cat 的 hi 方法对象//在反射中,万物皆是对象,可以把方法也看成对象
        Method method1 = aClass.getMethod(method);//4.通过method1调用方法//传统:对象.方法() , 反射机制:方法.invoke(对象)
        method1.invoke(o);}}

配置文件信息如下:
在这里插入图片描述
运行结果:
在这里插入图片描述

如果修改配置文件中method的信息,则可以实现调用不同的方法,我们无需再更改源码。小伙伴可以自己尝试,这里不再赘述!


2 走进Java反射机制

在这里插入图片描述

2.1 Java Reflection 说明与原理刨析

反射机制 允许程序在执行期借助于Reflection API取得任何类的内部信息, 比如:类的成员变量、构造器、成员方法等。并且,能够操作对象的属性以及方法。反射在设计模式和框架的底层都经常用到。

加载完类之后,在堆中会产生一个Class类型的对象 (一个类只有一个Class对象),这个对象包含了类的完整结构信息。通过这个对象得到类的结构。这个对象就像一面镜子,透过镜子看到类的结构,所以称之为反射。

反射可以做什么?

  • 在运行时判断任意一个对象所处的类;
  • 在运行时构造任意一个类的对象;
  • 在运行时得到任意一个类所具有的成员变量和方法;
  • 在运行时调用任意一个对象的成员变量和方法;
  • 生成动态代理。

反射机制原理刨析:
在这里插入图片描述
Java程序在计算机有三个阶段:编译阶段、加载阶段和运行阶段。

  1. Student类中包含构造器、成员变量和方法等,其源码通过 Javac 编译,可以得到 Student.class字节码文件;
  2. Student.class字节码文件中包含了该类的属性、构造器、成员变量、泛型、注解等信息;
  3. 当我们 new 一个Student对象的时候,会导致类的加载,Student.class字节码文件会被加载,加载到内存的堆区,形成Class类对象;
  4. 加载通过类加载器实现,形成的Class类对象中包含成员变量、构造器、注解等,体现了反射;
  5. 在底层,会把成员变量等当作对象来看待,映射成各种数据结构,例如成员变量映射成 Field[] 数组,构造器映射成 Constructor[] 数组;
  6. 最后生成的对象生成在堆中,该对象知道自身属于哪个Class对象,存在一个映射关系,这就是反射能够操作的原因;
  7. 当得到Class对象后,可以创建对象、调用对象方法、操作属性等。

2.2 反射的相关类

在这里插入图片描述

在反射中,我们需要掌握的主要类如下:

  • java.lang.Class:代表一个类,Class对象表示某个类加载后在堆中的对象;
  • java.lang.reflect.Method:代表类的方法,Method对象表示某个类的方法;
  • java.lang.reflect.Field:代表类的成员变量,Field对象表示某个类的成员变量;
  • java.lang.reflect.Constructor:代表类的构造方法,Constructor对象标识构造器;

在下面的例子中展示反射的使用:

//获取成员变量//getField不能得到私有属性
        Field field1 = aClass.getField(field);
        System.out.println(field1.get(o));//获取无参构造方法
        Constructor c1 = aClass.getConstructor();//获取有参构造方法
        Constructor c2 = aClass.getConstructor(String.class);//传入参赛的class对象

2.3 反射调用优化

反射可以动态的创建和使用对象,但是,使用反射基本是解释执行,对执行速度有一定的影响。

下面通过一个例子来让大家体验一下使用反射和不使用反射,效率上的区别,然后我们再来谈谈如何优化(虽然作用微乎其微)。

import java.lang.reflect.InvocationTargetException;import java.lang.reflect.Method;/**
 * @author 兴趣使然黄小黄
 * @version 1.0
 * 反射机制使用与否的效率比对
 */publicclassReflectionTest{publicstaticvoidmain(String[] args)throws ClassNotFoundException, InvocationTargetException,
            InstantiationException, IllegalAccessException, NoSuchMethodException {m1();m2();}//使用传统方法publicstaticvoidm1(){
        Cat cat =newCat();long start = System.currentTimeMillis();for(int i =0; i <90000000; i++){
            cat.hi();}long end = System.currentTimeMillis();
        System.out.println("传统方式调用 m1 耗时: "+(end-start)+"ms");}//使用反射方法publicstaticvoidm2()throws ClassNotFoundException, InstantiationException, IllegalAccessException,
            InvocationTargetException, NoSuchMethodException {
        Class aClass= Class.forName("reflection.Cat");
        Object o = aClass.newInstance();
        Method hi = aClass.getMethod("hi");long start = System.currentTimeMillis();for(int i =0; i <90000000; i++){
            hi.invoke(o);}long end = System.currentTimeMillis();
        System.out.println("使用反射调用 m2 耗时: "+(end-start)+"ms");}}

在这里插入图片描述
可以看到,使用反射与否,对执行效率是有一定影响的。

如何对反射调用进行优化呢?

可以通过 关闭访问检查 的方式来优化:

  1. Method、Field和Constructor对象都有setAccessible()方法;
  2. setAccessible()作用是启动和关闭访问检查的开关;
  3. 参数值为true表示,反射的对象在使用时取消访问检查,提高反射效率。而参数值为false,则表示反射的对象执行访问检查。
import java.lang.reflect.InvocationTargetException;import java.lang.reflect.Method;/**
 * @author 兴趣使然黄小黄
 * @version 1.0
 * 反射机制使用与否的效率比对
 */publicclassReflectionTest{publicstaticvoidmain(String[] args)throws ClassNotFoundException, InvocationTargetException,
            InstantiationException, IllegalAccessException, NoSuchMethodException {m1();m2();m3();}//使用传统方法publicstaticvoidm1(){
        Cat cat =newCat();long start = System.currentTimeMillis();for(int i =0; i <90000000; i++){
            cat.hi();}long end = System.currentTimeMillis();
        System.out.println("传统方式调用 m1 耗时: "+(end-start)+"ms");}//使用反射方法publicstaticvoidm2()throws ClassNotFoundException, InstantiationException, IllegalAccessException,
            InvocationTargetException, NoSuchMethodException {
        Class aClass= Class.forName("reflection.Cat");
        Object o = aClass.newInstance();
        Method hi = aClass.getMethod("hi");long start = System.currentTimeMillis();for(int i =0; i <90000000; i++){
            hi.invoke(o);}long end = System.currentTimeMillis();
        System.out.println("使用反射调用 m2 耗时: "+(end-start)+"ms");}//使用反射方法,进行过优化publicstaticvoidm3()throws ClassNotFoundException, InstantiationException, IllegalAccessException,
            InvocationTargetException, NoSuchMethodException {
        Class aClass= Class.forName("reflection.Cat");
        Object o = aClass.newInstance();
        Method hi = aClass.getMethod("hi");//关闭反射调用方法时的访问检查
        hi.setAccessible(true);long start = System.currentTimeMillis();for(int i =0; i <90000000; i++){
            hi.invoke(o);}long end = System.currentTimeMillis();
        System.out.println("反射优化调用 m3 耗时: "+(end-start)+"ms");}}

虽然作用是很小的,但是优化后的m3相较m2执行效率已经有了一些改善。

在这里插入图片描述


3 Class类

3.1 Class类说明与演示

在这里插入图片描述

Class类的基本说明:

  • Class也是一个类,是Object类的子类;
  • Class类对象不是new出来的,是由系统创建的;
  • 对于某个类的Class对象,在内存中只有一份,因为类只加载一次;
  • 每个类的实例都会记得自己是哪个Class实例所生成的;
  • 通过Class可以完整地得到一个类的完整结构,通过一系列API;
  • Class对象存放在堆中;
  • 类的字节码二进制数据,是放在方法区的,也有时候叫做类的元数据(包括方法代码、变量名、方法名、访问权限等)。

3.2 Class常用方法

方法摘要:

在这里插入图片描述
演示准备:Car类

/**
 * @author 兴趣使然黄小黄
 * @version 1.0
 */publicclassCar{public String brand ="奥迪";publicint price =999999;public String color ="金属黑";@Overridepublic String toString(){return"Car{"+"brand='"+ brand +'\''+", price="+ price +", color='"+ color +'\''+'}';}}

下面对Class常用方法的使用进行演示:

import java.lang.reflect.Field;/**
 * @author 兴趣使然黄小黄
 * @version 1.0
 * 演示Class类的常用方法
 */publicclassClassTest{publicstaticvoidmain(String[] args)throws ClassNotFoundException, InstantiationException, IllegalAccessException, NoSuchFieldException {

        String classAllPath ="reflection.Car";//1.获取Car类对应的Class对象//<?>表示不确定的Java类型
        Class<?> cls = Class.forName(classAllPath);
        System.out.println("cls = "+ cls);//显示cls对象,是哪个类的Class对象
        System.out.println("cls 的运行类型 = "+ cls.getClass());//输出运行类型//2.获取包名
        System.out.println("包名 = "+ cls.getPackage().getName());//3.得到全类名
        System.out.println("全类名 = "+ cls.getName());//4.生成对象实例
        Car car =(Car) cls.newInstance();
        System.out.println(car);//会调用car.toString()//5.通过反射获取属性,但是无法获得私有属性
        Field brand = cls.getField("brand");
        System.out.println("brand的值 = "+ brand.get(car));//6.通过反射给属性赋值
        brand.set(car,"奔驰");
        System.out.println("修改后brand的值 = "+ brand.get(car));//7.通过遍历,得到Car类中的所有属性
        System.out.println("===================字段名==================");
        Field[] fields = cls.getFields();for(Field f :
                fields){
            System.out.println(f.getName());}}}

在这里插入图片描述

3.3 获得Class对象的方式

回顾:Java程序在计算机中有三个阶段,分别是 编译阶段、加载阶段和运行阶段。

四种核心拿到Class对象的方式如下:

  • 编译阶段:Class.forName();
  • 加载阶段:class对象已经存在了,可以通过 类.class() 拿到;
  • 运行阶段:对象已经存在,可以通过 对象.getClass() 拿到;
  • 类加载器:通过 类加载器 拿到Class对象。

下面 根据不同的应用场景,来演示获取Class对象的方式:

1️⃣ 多用于配置文件,读取全路径,加载类。
前提: 已经知道一个类的全类名,且该类在类路径下,可以通过Class类的静态方法forName()获取,可能抛出ClassNotFoundException
实例:

Class cls= Class.forName("xxx.xxx.xxx");

2️⃣ 多用于参数传递,比如通过反射得到对应的构造器对象。
前提: 若已知具体的类,通过Class获取,该方式最为安全可靠,程序性能高。
实例:

Class cls2= XXX.class;

3️⃣ 通过创建好的对象,获取Class对象
前提: 已知某个类的实例,调用该实例的getClass()方法获取Class对象。
实例:

Car car =newCar();
Class<?extendsCar> cls3 = car.getClass();

4️⃣ 通过类加载器。
实例:

//(1)先得到类加载器 car
ClassLoader classLoader = car.getClass().getClassLoader();//(2)通过类加载器得到Class对象
Class<?> cls4 = classLoader.loadClass(classAllPath);

比较四种方式对象是否为同一对象

/**
 * @author 兴趣使然黄小黄
 * @version 1.0
 * 获取Class的不同方式,演示
 */publicclassGetClassTest{publicstaticvoidmain(String[] args)throws ClassNotFoundException {//1. Class.forName
        String classAllPath ="reflection.Car";
        Class<?> cls1 = Class.forName(classAllPath);
        System.out.println("cls1 = "+ cls1);//2.类名.class,多用于参数传递
        Class<Car> cls2 = Car.class;
        System.out.println("cls2 = "+ cls2);//3.对象.getClass(),已经知道有对象实例
        Car car =newCar();
        Class<?extendsCar> cls3 = car.getClass();
        System.out.println("cls3 = "+ cls3);//4.通过类加载器来获取Class对象//(1)先得到类加载器 car
        ClassLoader classLoader = car.getClass().getClassLoader();//(2)通过类加载器得到Class对象
        Class<?> cls4 = classLoader.loadClass(classAllPath);
        System.out.println("cls4 = "+ cls4);//比较
        System.out.println("是否为同一对象:"+((cls1.hashCode()==cls2.hashCode())&&(cls3.hashCode()==cls4.hashCode())&&(cls2.hashCode()== cls3.hashCode())));}}

在这里插入图片描述

当然除了以上四种方式,还有一些琐碎的其他方式没有叙述

5️⃣ 基本数据类型获取Class类对象的方式

Class cls= 基本数据类型.class;

6️⃣ 基本数据类型对应的包装类,可以通过.type得到Class类对象

Class cls= 包装类.TYPE;

3.4 哪些类型有Class对象

  1. 外部类,内部成员类,静态内部类,局部内部类,匿名内部类;
  2. interface:接口;
  3. 数组;
  4. enum:枚举;
  5. annotation:注解;
  6. 基本数据类型;
  7. void。

写在最后

🌟以上便是本文的全部内容啦,后续内容将会持续免费更新,如果文章对你有所帮助,麻烦动动小手点个赞 + 关注,非常感谢 ❤️ ❤️ ❤️ !
如果有问题,欢迎私信或者评论区!
下一节,将会与大家聊聊 类加载与反射爆破,欢迎关注!
在这里插入图片描述

共勉:“你间歇性的努力和蒙混过日子,都是对之前努力的清零。”
在这里插入图片描述

标签: java jvm servlet

本文转载自: https://blog.csdn.net/m0_60353039/article/details/126996013
版权归原作者 兴趣使然黄小黄 所有, 如有侵权,请联系我们删除。

“【Java高级】框架底层基础:Java的反射机制剖析”的评论:

还没有评论