Java中的反射机制

一、反射简介

(一)什么是反射

​ Java的反射 (reflection) 机制是指在程序的运行状态中,可以构造任意一个类的对象,可以了解任意一个对象所属的类,可以了解任意一个类的成员变量和方法,可以调用任意一个对象的属性和方法。这种动态获取程序信息以及动态调用对象的功能称为Java语言的反射机制。反射被视为动态语言的关键。

(二)反射能做什么

​ 1、在运行时判断任意一个对象所属的类;

​ 2、在运行时构造任意一个类的对象;

​ 3、在运行时判断任意一个类所具有的成员变量和方法;

​ 4、在运行时调用任意一个对象的方法;

​ 5、生成动态代理。

(三)为什么需要反射机制

1、动态类型、静态类型语言

​ 动态类型语言:所谓动态类型语言,就是类型的检查是在运行时才做。动态类型在解释语言中极为普遍,如JavaScript、Python、Ruby等等。

​ 静态类型语言:静态类型语言的类型检查是在运行前的编译阶段,比如C#、Java 等都是静态类型语言,静态类型语言为了达到多态会采取一些类型鉴别手段,如继承、接口,而动态类型语言却不需要。Java是在编译时期确定的变量类型且在运行时期不能改变,在类型转换方面也是强制的,例如大范围整数类型转换为小范围整数类型时必须要强转,如int必须强制转换才能得到小范围类型byte;所以Java是静态、强类型语言。

​ 但是 JAVA 有着一个非常突出的动态相关机制一Reflection(反射),用在Java身上指的是可以于运行时加载、探知、使用编译期间完全知的 classes。换句话说,Java程序可以加载一个运行时才得知名称的 class 获悉其完整构造(但不包括methods定义),并生成其对象例(newlnstance) 或对其 fields 设值,或唤起 (invoke) 其methods方法。

2、开闭原则

​ 为什么在这里提开闭原则,是因为反射也能够在不修改源码的情况下对原有代码进行 ”功能增强“。

​ Software entities (modules, classes, functions, etc.) should be open for extension , but closed for modification。即:软件实体(模块、类、方法等) 应该“对扩展开放、对修改关闭”。抽象化是开闭原则的关键。 对可变性进行封装是实现抽象化的方法。

​ 即添加一个新的功能应该是,在已有代码基础上扩展代码(新增模块、类、方法等),而非修改已有代码(修改模块、类、方法 等)。

二、反射引入

​ 假设有下面的类 Person,我们在配置文件中存储了这个类的位置信息以及里面含有的方法。下面的要求就是在不知道这个 Person 类的内部情况下如何创建它的实例对象并且调用里面的方法。

classfullpath = com.purearc.bringin.Cat
method = hi
/**
 * date: 2023/6/25
 * 省略 getter、setter 和有参无参 Constructor
 * @author Arc
 */
package com.purearc.bringin;

public class Cat {
    private String name = "defaultName";
    public int age = 10;

    public void hi(){
        System.out.println("sayHi");
    }
}

​ 首先就是读取 properties 文件,通过 I/O 流读取到内存中(读取进去是以 map 的键值对形式存储)根据 key 获取 value 值之后转化成 String 字符串我们就在不打开文件的情况下获取了 Person 的全路径类名和它内部的方法名。

/**
 * date: 2023/6/25
 *
 * @author Arc
 */
package com.purearc.bringin;

public class ReflectBringIn {
    public static void main(String[] args) throws Exception {

        Properties properties = new Properties();
        properties.load(new FileInputStream("src/main/resources/re.properties"));
        String classfullpath = properties.get("classfullpath").toString();
        String methodName = properties.get("method").toString();
        //System.out.println(classfullpath);
        //System.out.println(method);
    }
}

​ 下一步就是通过 java 的反射机制来达到构造实例对象、调用成员方法的过程:

​ 1、通过 Class 类的静态方法 forName(String param); 获得一个 cls 对象,该对象的类型就是 Class,就相当于我的名字叫“名字”;

​ 2、通过 cls 对象的 newInstance(); 方法获得一个 Object 对象的实例 o;

​ 3、通过 cls 对象的 getMethond(); 方法获得 Person 的内置方法,返回的是一个 Methond 的类对象,就相当于把 “方法” 也看作是 “对象”,在反射中再一次印证了 “Java 中一切皆对象” 这句话。😙

​ 4、通过获取到的 “方法对象” 调用 Person 中的 hi(); 方法。调用的方式就是 “方法对象.invoke(实例对象);”,这与我们所学的 OGNL 正好是相反的。

		//1、加载类
        Class<?> cls = Class.forName(classfullpath);
        System.out.println("生成的 class 对象类型是:"+cls.getClass());
        //2、通过 class 类对象获得加载类的类对象(Cat)
        Object o = cls.newInstance();
        System.out.println("newInstance 对象类型是:"+o.getClass());
        //3、通过 cls 对象获得 Cat 的方法对象,即 方法也被看作对象
        Method method1 = cls.getMethod(methodName);
        //4、通过 方法对象 来调用方法
        method1.invoke(o);

​ 通过上面的这四部分操作我们就可以获得我们想要的结果,顺便还可以判断使用 getClass(); 方法来看看这几个对象的运行时类型到底是什么。

image-20230626192953963

​ 假设我们在 Cat中新增了一个 Hello(); 方法:如果使用传统方式我们就不得不在 ReflectBringIn 中修改源代码,但是通过使用反射的方式就可以只修改 re.properties 的内容即可完成对该方法的读取,这就很符合 对拓展开放对修改关闭 了。

/**
* 什么年代了还在玩传统调用
*/
Person person = new Person();
person.hi();
//源代码新增部分
person.Hello();

//新增的方法
public void Hello(){
        System.out.println("你一定要幸福啊");
    }

image-20230626193018806

三、反射原理

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

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

下面为反射执行的过程:

​ 1、源码通过 javac 编译成 .class 文件,里面包含类的成员变量和方法、构造器等。

​ 2、当需要创建对象时(new 构造器();) .class 对象通过 ClassLoader 加载进入 JVM 内存,同时在堆中生成一个 Class 的实例(假设为 cls 对象)。

​ 3、cls 对象内部包含加载进入的类的成员变量、构造器、成员方法等(相当于 cls 对象中有若干属性存储着被加载类的信息)。同时创建的对象也知道自己对应的 Class 类对象。

image-20230626192513461

​ 反射机制相关的类在 java.lang.reflection 包中,主要有Class、Method、Filed、Constructor。

(一)Class

1、Class 的特性

​ Class 对象表示某个类加载后在堆中的对象,下面是 Class 的类图,既然是一个类,那就必然继承自 Object

image-20230627164900248

​ Class 类对象不是 new 出来的,而是系统创建的。相信学过一点 JVM 的同学对这个不会陌生。下面的两种调试演示了返回 Class 类对象的过程,通过调试能够将看到系统调用 Class 类中的 loadClass() 方法返回一个 Class 对象的过程。

public class Class01 {
    public static void main(String[] args) throws Exception{
        //new 创建
        Person person = new Person();

        //反射方式
//        Class<?> person2 = Class.forName("com.purearc.pojo.Person");
    }
}

​ 下图是通过 new 的方式创建对象的过程,通过 debug 模式(强制步入)可以看到,一上来在 new 对象时会进入到 ClassLoad 中调用了一个 loadClass() 方法。

image-20230626210927494

image-20230626213121228

​ 通过反射的方式一路强制步入,最后也找到了 ClassLoader 中的 loadClass() 方法。传入的同样是全路径类名。

image-20230626212708597

image-20230626212808345

对于某个类的 Class 类对象在内存中只会存一份,因为类之加载一次。也就是说我加载进入 JVM 后即使在创建出新的关于 Person 的 Class 类对象在内存中也只有一个代表 Person 的 cls。

        Class<?> person2 = Class.forName("com.purearc.pojo.Person");
        Class<?> person3 = Class.forName("com.purearc.pojo.Person");
		//结果为 true(哈希冲突不在这里考虑)
        System.out.println(person3.hashCode() == person2.hashCode());

​ 每个类的实例都会记得自己是由哪个 Class 实例所生成。

​ 类的字节码二进制数据有的地方称为类的元数据,当类被加载之后除了在堆中生成 Class 类对象,还会将二进制的字节码文件放入方法区。

image-20230628103734433

2、Class 类的常用方法

方法名 功能说明
static Class forName(String name) 返回指定类名name的Class对象
Object newlnstance() 调用缺省构造函数,返回该Class对象的一个实例
getName() 返回此Classj对象所表示的实体(类、接口、数组类、基本类型等)名称
Class getSuperClass() 返回当前Class对象的父类的Class对象
Class [] getInterfaces() 获取当前Class对象的接口
ClassLoader getClassLoader() 返回该类的类加载器
Class getSuperclass() 返回表示此Class所表示的实体的超类的Class
Constructor[]getConstructors() 返回一个包含某些Constructor对象的数组
Field [] getDeclaredFields() 返回Field对象的一个数组
Method getMethod (String name,Class ..paramTypes) 返回一个Methodi对象,此对象的形参类型为paramType
public class Class02 {
    public static void main(String[] args) throws Exception{
        String classpath = "com.purearc.pojo.Car";
        Class<?> aClass = Class.forName(classpath);
        //1、输出aClass对象对应的实体类型
        System.out.println(aClass);
        //2、输出aClass的运行时类型
        System.out.println(aClass.getClass());
        //3、得到包名,getpackage()返回一个 Package 对象
        System.out.println(aClass.getPackage().getName());
        //4、获得类名
        System.out.println(aClass.getName());
        //5、创建对象实例
        Object car = aClass.newInstance();
        System.out.println(car);
        //6、反射获得非私有属性、给属性赋值
        Field name = aClass.getField("name");
        System.out.println("赋值前:"+name.get(car));
        name.set(car,"黑色战损轿车");
        System.out.println("赋值后:"+name.get(car));
        //7、获得所有字段
        Field[] fields = aClass.getFields();
        for (Field field : fields) {
            System.out.print("所有属性值"+field.get(car)+"   ");
        }
    }
}

image-20230628103813829

3、获得 Class 对象的方式

​ (1)已知一个类的全类名,且该类在类路径下,可通过 Class 类的静态方法 forName() 获取,可能抛出异常ClassNotFoundException。

//实例
Class<?> aClass = Class.forName("com.purearc.pojo.Car");

​ 应用场景:多用于配置文件,读取类全路径,加载类。

​ (2)若已知具体的类,通过类的class获取,该方式最为安全可靠,程序性能最高。

//实例
Constructor<?> constructor2 = cls.getConstructor(String.class);

​ 应用场景:多用于参数传递,比如通过反射得到对应构造器对象。

​ (3)已经有对象实例,通过 对象.getClass();

//实例
Car  car = new Car();
Class cls = car.getClass();

​ (4)通过类加载器获得类的 Class 对象

//1)先得到类加载器
ClassLoader classLoader = car.getClass.getClassLoader();
//2)通过类加载器得到 Class 对象
classLoader.loadClass("com.purearc.pojo.Car")

​ (5)基本数据类型(int、char、boolean、float、double、byte、long、short)获得 Class 对象,这里 class 作为基本数据类型的属性。

Class clas = 基本数据类型.class;
//实例 输出结果为 int
Class cls = int.class;
System.out.println(cls);

​ (6)基本数据类型的包装类(Integer、Character...)通过 .type 获得 Class 对象。

//实例 结果为 int (自动拆箱了)
Class cls2 = Integer.TYPE;
System.out.println(cls2);

3、含有 Class 对象的类型


 public static void main(String[] args) {
        //1、类(外部、成员内部类、静态内部类、局部内部类、匿名内部类)
        Class<String> stringClass = String.class;//class java.lang.String
        //2、接口
        Class<Serializable> serializableClass = Serializable.class;//interface java.io.Serializable
        //3、数组
        Class<Integer[]> aClass = Integer[].class;//class [Ljava.lang.Integer;
        //二维数组
        Class<float[][]> aClass1 = float[][].class;//class [[F
        //4、注解
        Class<Deprecated> deprecatedClass = Deprecated.class;//interface java.lang.Deprecated
        //5、枚举
        Class<Thread.State> stateClass = Thread.State.class;//class java.lang.Thread$State
        //6、基本数据类型
        Class<Character> characterClass = char.class;//char
        //7、void
        Class<Void> voidClass = void.class;//void
        }

4、类加载的方式

​ 反射机制是java实现动态语言的关键,也就是通过反射实现类动态加载。

(1)静态加载:编译时加载相关的类,如果没有则报错,依赖性太强。

(2)动态加载:运行时加载需要的类,如果运行时不用该类,则不报错,降低了依赖性。

public class LoadProcess {
    public static void main(String[] args) throws Exception{
        Scanner scanner = new Scanner(System.in);
        String input = scanner.next();
        switch (input) {
            case "1":
                /**
                * 静态加载
                * 可能会用不到这个类,但是在编译的时候如果没有就会报错无法通过编译
                * 如果想通过编译必须引入 Person 这个类
                */
                Person person = new Person();
                System.out.println(person);
            case "2":
                 /**
                * 动态加载
                * 当真正使用到该类时才会进行类加载,可以通过编译
                * 当 case 2 时才会报异常
                */
                Class<?> cls = Class.forName("com.purearc.pojo.Person");
                Object o = cls.newInstance();
                Method m = cls.getMethod("hi");
                m.invoke(o);
            default:
                System.out.println("donothing");

        }
    }
}

​ 静态加载:当创建对象时(new)、当子类被加载时,父类也加载、调用类中的静态成员时

​ 动态加载:通过反射

//静态加载因为编译不通过报错
ClassLoad_.java:20:错误:找不到符号
Person person = new Person();
//动态加载当输入 2 的时候需要 Person 类
Exception in thread "main" java.lang.ClassNotFoundException:Person
at java.net.URLClassLoader.findClass (Unknown Source)
at java.lang.ClassLoader.loadClass (Unknown Source)

5、类的加载过程

image-20230629105902904

​ 加载和链接由 JVM 控制,初始化阶段是我们可以操作的。

image-20230629110154798

(1)加载阶段

​ JVM在该阶段的主要目的是将字节码从不同的数据源(可能是 class 文件、也可能是 jar 包,甚至网络)转化为二进制字节流加载到内存中,并生成一个代表该类的 java.lang.Class 对象。

(2)链接阶段

验证
​ 目的是为了确保 Cass 文件的字节流中包含的信息符合当前虚拟机的要求,并且不会危害虚拟机自身的安全。image-20230629111738110

​ 包括:文件格式验证(是否以魔数 oxcafebabe 开头)、元数据验证、字节码验证和符号引用验证;

image-20230629112036243

​ 可以考虑使用 -Xverify:none 参数来关闭大部分的类验证措施,缩短虚拟机类加载的时间。

准备
​ JVM 会在该阶段对静态变量,分配内存并默认初始化(对应数据类型的默认初始值,如0、0L、nul、false 等)。这些变量所使用的内存都将在方法区中进行分配。

class A{
    //1、n1 是实例属性,不是静态变量,因此准备阶段不会分配内存;
    public int n1 = 10;
    //2、n2 是静态变量,在准备阶段会分配内存,默认初始化 0,在初始化(initialization);
    public static int n2 = 20;
    //3、常量 和静态变量不同,直接赋值为 30 ,一旦复制后就不可变了。
    public static final int n3 = 30;
}

解析

​ 虚拟机将常量池内的符号引用替换为直接引用的过程就是解析。

(3)初始化阶段

​ 到初始化阶段,才真正开始执行类中定义的Jva程序代码,此阶段是执行 <clinit>() 方法的过程。

<clinit>() 方法是由编译器按语句在源文件中出现的顺序,依次自动收集类中的所有 静态变量 的赋值动作和 静态代码块 中的语句,并进行合并。

class B {
    static {
        System.out.println("B 的 static 代码块");
        num = 300;
    }

    static int num = 100;

    public B() {
        System.out.println("B 的 constructor");
    }
}

/**
	依次自动收集类中的所有静态变量的赋值动作和静态代码块中的语句,并合并
*/
clinit(){
	System.out.println("B 的 static 代码块");
	//num = 300;
	num = 100;
}

​ 直接使用 B 类的静态属性导致了类的加载

System.out.println(B.num);
/**
结果:
	B 的 static 代码块
	100
*/

​ 虚拟机会保证一个类的<clinit>() 方法在多线程环境中被正确地加锁、同步,如果多个线程同时去初始化一个类,那么只会有一个线程去执行这个类的<clinit>()方法,其他线程都需要阻塞等待,直到活动线程执行<clinit>()方法完毕。

image-20230629172254768

(二)Method

​ Method 对象代表某个类的方法

​ 下面会具体说,不截图了😠

(三)Filed

​ Field 对象表示某个类的成员变量。

​ 通过 Filed 对象的 Field getField(String name) 方法即可获得成员变量的信息,不过不能获得其私有(private)的属性。否则会有异常 Exception in thread "main" java.lang.NoSuchFieldException: name

        Field fieldAge = cls.getField("age");
//        Field fieldName = cls.getField("name");
//        System.out.println(fieldName.get(o));
        System.out.println(fieldAge.get(o));

image-20230626195335276

(四)Constructor

​ Constructor 对象代表类的构造方法。通过 Class 类对象的 Constructor<T> getConstructor(Class<?>... parameterTypes) 方法即可获得构造器对象,get() 的参数是类中构造器的形参 clss。

        Constructor<?> constructor = cls.getConstructor();
        System.out.println(constructor);
        Constructor<?> constructor2 = cls.getConstructor(String.class);
        System.out.println(constructor2);

image-20230626195731413

四、反射优化

​ 反射可以动态的创建和使用对象(也是框架底层核心),使用灵活,没有反射机制,框架技术就失去底层支撑。但是使用反射基本是解释执行,对执行速度有影响。

​ 下面是通过反射方式和构造生成分别生成对象后调用 hi(); 方法的过程,hi(); 方法是一个空方法。

    /**
     * 反射调用
     * @throws Exception
     */
    public static void m1() throws Exception {

        Properties properties = new Properties();
        properties.load(new FileInputStream("src/main/resources/re.properties"));
        String classfullpath = properties.get("personclasspath").toString();
        String methodName = properties.get("personmethod").toString();
        Class<?> cls = Class.forName(classfullpath);
        Object person = cls.newInstance();
        Method method1 = cls.getMethod(methodName);
        //method1.setAccessible(true);
        Long start = System.currentTimeMillis();
        for (int i = 0; i < 100000000; i++) {
            method1.invoke(person);
        }
        Long end = System.currentTimeMillis();
        System.out.println("反射用时"+(end - start));
    }

    /**
     * 传统方式调用
     */
    public static void m2(){
        Long start = System.currentTimeMillis();
        Person person2 = new Person();
        for (int i = 0; i < 100000000; i++) {
            person2.hi();
        }
        Long end = System.currentTimeMillis();
        System.out.println("传统方法"+(end - start));
    }
}

image-20230626202944960

​ Method 和 Field、Constructor 对象都有 setAccessible() 方法。setAccessible() 作用是启动和禁用访问安全检查的开关,参数值为true 表示反射的对象在使用时取消访问检查,提高反射的效率。参数值为 false 则表示反射的对象执行访问检查。

​ 密码吗的,可以看到还是有点作用的。

image-20230626203449233

五、获取类的结构信息

(一)Class 类中的 API

​ 必要的测试代码

package com.purearc.pojo;
public class Parent {
    public String gender;
    public void hi(){}
    public Parent() {}
}

package com.purearc.interface_;
public interface A {
}
public interface B {
}

​ 测试 Api

/**
 * date: 2023/6/29
 * 获取类的结构信息
 *
 * @author Arc
 */
package com.purearc.question;
@Test
public void api01() throws ClassNotFoundException {
        Class cls = Class.forName("com.purearc.pojo.Person2");
        //1、getName:获得全类名
        System.out.println(cls.getName());
        //2、getSimpleName:获得简单类名
        System.out.println(cls.getSimpleName());
        //3、getFileds:获得所有 public 修饰的属性,包含本类和父类中的
        Field[] fields = cls.getFields();
        System.out.println("子类和父类的public属性");
        for (Field field : fields) {
            System.out.println("  "+field);
        }
        //4、getDeclaredFileds:获取本类中的所有属性
        Field[] declaredFields = cls.getDeclaredFields();
        System.out.println("该类中的所有属性");
        for (Field declaredField : declaredFields) {
            System.out.println("  "+declaredField);
        }
        //5、getMethods:获取所有public修饰的方法,包含本类以及父类的
        Method[] methods = cls.getMethods();
        System.out.println("子类父类中的所有public方法");
        for (Method method : methods) {
            System.out.println("  "+method);
        }
        //6、getDeclaredMethods:获取本类中所有方法
        Method[] declaredMethods = cls.getDeclaredMethods();
        System.out.println("本类中的所有方法");
        for (Method declaredMethod : declaredMethods) {
            System.out.println("  "+declaredMethod);
        }
        //7、getConstructors:获取本类 public 修饰的构造器
        Constructor[] constructors = cls.getConstructors();
        System.out.println("子类父类中的所有public构造器");
        for (Constructor constructor : constructors) {
            System.out.println("  "+constructor);
        }
        //8、getDeclaredConstructors:获取本类中所有构造器
        Constructor[] declaredConstructors = cls.getDeclaredConstructors();
        System.out.println("本类中的所有构造器");
        for (Constructor declaredConstructor : declaredConstructors) {
            System.out.println("  "+declaredConstructor);
        }
        //9、getPackage:以 Package 形式返回包信息
        Package aPackage = cls.getPackage();
        System.out.println(aPackage);
        //10、getSuperClass: 以Class形式返回父类信息
        Class superclass = cls.getSuperclass();
        System.out.println("父类的class:"+superclass);
        //11、getInterfaces: 以Class[] 形式返回接口信息
        Class[] interfaces = cls.getInterfaces();
        for (Class anInterface : interfaces) {
            System.out.println("接口信息"+anInterface);
        }
        //12、getAnnotations:Annotation[] 形式返回注解信息
        Annotation[] annotations = cls.getAnnotations();
        for (Annotation annotation : annotations) {
            System.out.println("注解信息"+annotation);
        }
    }

}

​ 结果

com.purearc.pojo.Person2
————————————————————————————————————————————————————————————————
Person2
————————————————————————————————————————————————————————————————
子类和父类的public属性
  public java.lang.String com.purearc.pojo.Person2.name
  public java.lang.String com.purearc.pojo.Parent.gender
————————————————————————————————————————————————————————————————
该类中的所有属性
  public java.lang.String com.purearc.pojo.Person2.name
  protected int com.purearc.pojo.Person2.age
  java.lang.String com.purearc.pojo.Person2.job
  private double com.purearc.pojo.Person2.sal 
  ————————————————————————————————————————————————————————————————
子类父类中的所有public方法
#这里有从 Object 继承下来关于对象锁的一些方法,不仅是直接父类,也包含父类的父类
  public void com.purearc.pojo.Person2.m1()
  public void com.purearc.pojo.Parent.hi()
  public final void java.lang.Object.wait(long,int) throws java.lang.InterruptedException
  public final native void java.lang.Object.wait(long) throws java.lang.InterruptedException
  public final void java.lang.Object.wait() throws java.lang.InterruptedException
  public boolean java.lang.Object.equals(java.lang.Object)
  public java.lang.String java.lang.Object.toString()
  public native int java.lang.Object.hashCode()
  public final native java.lang.Class java.lang.Object.getClass()
  public final native void java.lang.Object.notify()
  public final native void java.lang.Object.notifyAll()
  ————————————————————————————————————————————————————————————————
本类中的所有方法
  protected void com.purearc.pojo.Person2.m2()
  private void com.purearc.pojo.Person2.m4()
  void com.purearc.pojo.Person2.m3()
  public void com.purearc.pojo.Person2.m1()
   ————————————————————————————————————————————————————————————————
子类父类中的所有public构造器
  public com.purearc.pojo.Person2()
   ————————————————————————————————————————————————————————————————
本类中的所有构造器
  public com.purearc.pojo.Person2()
  private com.purearc.pojo.Person2(double)
   ————————————————————————————————————————————————————————————————
package com.purearc.pojo
 ————————————————————————————————————————————————————————————————
父类的class:class com.purearc.pojo.Parent
 ————————————————————————————————————————————————————————————————
接口信息interface com.purearc.interface_.A
接口信息interface com.purearc.interface_.B
 ————————————————————————————————————————————————————————————————
注解信息@java.lang.Deprecated()

(二)Field 中的 API

​ getModifiers():以int形式返回修饰符。

说明:默认修饰符是0,public是1,private是2,protected是4,static是8,final是16 ,如果是多个的组合会相加。

​ getType():以 Class 形式返回类型

​ getName():返回属性名

    @Test
    public void api02() throws ClassNotFoundException {
        Class cls = Class.forName("com.purearc.pojo.Person2");
        //getDeclaredFileds:获取本类中的所有属性
        Field[] declaredFields = cls.getDeclaredFields();
        for (Field declaredField : declaredFields) {
            System.out.println("该类中的所有属性 " +declaredField+"    属性修饰符的值:"+declaredField.getModifiers() + "    该属性的类型"+declaredField.getType());
        }
    }

结果如下:

image-20230629210926619

(三)Method 中的 API

​ getModifiers():以int形式返回修饰符

说明:默认修饰符是0,public是1,private是2,protected是4,static是8,final是16。

​ getReturnType():以Classi形式获取返回类型。

​ getName():返回方法名。

​ getParameterTypes():以 Class[] 返回参数类型数组。

    @Test
    public void api02() throws ClassNotFoundException {
        Class cls = Class.forName("com.purearc.pojo.Person2");
        //getMethods:获取所有public修饰的方法,包含本类以及父类的
        Method[] methods = cls.getMethods();
        for (Method method : methods) {
            System.out.println("所有public方法  " + method +
                    "    对应修饰符的映射值:"+method.getModifiers()+"    返回值类型:"+method.getReturnType());
        }
    }

​ 结果如下:

image-20230629211652205

//修改一个方法测试
public void m1(String name,int age,double weight){}

    @Test
    public void api03() throws ClassNotFoundException, NoSuchMethodException {
        Class cls = Class.forName("com.purearc.pojo.Person2");
//        Method[] methods = cls.getMethods();
//        Class<?>[] parameterTypes = methods[0].getParameterTypes();
//        for (Class<?> parameterType : parameterTypes) {
//            System.out.println(parameterType);
//        }]
        Method m1 = cls.getMethod("m1", String.class, int.class, double.class);
        for (Class<?> parameterType : m1.getParameterTypes()) {
            System.out.println(parameterType);
        }
    }}

​ 一度感觉自己是个毮比,明明上面在 getMethod 的时候已经传参了......

image-20230629214422054

(四)Constructor 中的 API

​ getModifiers():以 int 形式返回修饰符

​ getName():返回构造器名(全类名)

​ getParameterTypes():以 Class [ ] 返回参数类型数组

    @Test
    public void api04() throws ClassNotFoundException, NoSuchMethodException {
        Class cls = Class.forName("com.purearc.pojo.Person2");
        Constructor[] declaredConstructors = cls.getDeclaredConstructors();
        for (Constructor declaredConstructor : declaredConstructors) {
            for (Class parameterType : declaredConstructor.getParameterTypes()) {
                System.out.println(parameterType);
            }
        }
    }

方式一:调用类中的oublic修饰的无参构造器
方式二:调用类中的指定构造器
Class类相关方法
newlnstance():调用类中的无参构造器,获取对应类的对象
getConstructor(Class...clazz):根据参数列表,获取对应的构造器对象
getDecalaredConstructor(Class...clazz):根据参数列表,获取对应的构造器对象
Constructor类相关方法
setAccessible():暴破
newlnstance(Object...obj):调用构造器

六、反射绕过安全性检查访问

安全性检查

访问检查,就是查看成员属性、成员方法的使用是否符合访问权限(public、protected、default、private)。简单来说,如果一个类的成员(属性或者方法)的访问权限是private,那么该成员只能在当前类中使用;如果一个类的成员的访问权限是public,那么该成员可以在任意类中使用;如果一个类的成员的访问权限是default,那么该成员只能在同一个包下面的类中使用;如果一个类的成员的访问权限是protected,那么该成员可以在同一个包下面的类中和其他包下面的该类的子类中使用。

​ 例如类的成员的访问权限是default,你却在另一个包中使用了该成员,当编译时,编译器会进行访问检查,发现成员的使用与给定的访问权限不一致,因此会报错。

image-20230630111004365

​ 反射对象的访问检查是怎么的呢😕?一个类的成员属性、成员方法、构造函数,在反射中分别被抽象为 Field、Method、Counstructor 类。我们可以使用 Field 访问对象的成员属性,成员属性的访问权限,编译器是不知道的,只有运行时才知道。因此对于反射对象(例如Field)访问权限的检查只能交给虚拟机。

setAccessible(boolean flag)方法是 AccessibleObject 类中的一个方法,它是 Field、 Method、Constructor 的公共父类。当Field、Method 或 Constructor (三者都是反射对象)分别用于设置字段(set(Object obj, Object value))或获取字段(get(Object obj))、调用方法(invoke(Object obj, Object... args))或创建和初始化类的新实例(newInstance(Object... initargs))时,将执行运行时访问检查

image-20230630110539837

​ 方法名 setAccessible 很容易让人产生误解,给人的感觉是设置了成员的可访问性,例如,觉得public修饰的成员是任意类都可以访问的,所以可访问标志是true;觉得private修饰的成员只有本类可以访问,所以可访问标志是false。其实不然,不管是什么访问权限,其可访问标志的值都为false。也就是说对于可能导致非法访问的所有都会进行访问安全性检查。

    @Test
    public void AccessTest() throws ClassNotFoundException {
        Class cls = Class.forName("com.purearc.pojo.Person2");
        Field[] fields = cls.getDeclaredFields();
        for (Field field : fields) {
            System.out.println(field.isAccessible());
        }
    }

image-20230630111756943

(一)实例创建

public class User {
    private String name = "defaultName";
    private int age = 10;
    public User(String name) {
        this.name = name;
    }
    public User() {
    }
    private User(String name, int age) {
        this.name = name;
        this.age = age;
    }
}

1、通过 public 的无参构造器创建实例

获得构造器 -> 构造器.newInstance();

    /**
     * 通过 public 的无参构造器创建实例
     */
    @Test
    public void m1() throws ClassNotFoundException, InstantiationException, IllegalAccessException {
        Class<?> cls = Class.forName("com.purearc.pojo.User");
        Object user = cls.newInstance();
        System.out.println(user);
    }

image-20230630105718424

2、通过 public 的无参构造器创建实例

获得有参构造器 -> 构造器.newInstance( 参数<Class> ..);

 /**
     * public 的有参构造器
     * 先得到构造器,在创建实例
     */
    @Test
    public void m2() throws Exception {
        Class<?> cls = Class.forName("com.purearc.pojo.User");
        Constructor<?> constructor = cls.getConstructor(String.class);
        Object newObj = constructor.newInstance("newName");
        System.out.println(newObj);
    }

image-20230630105704731

3、通过 public 的无参构造器创建实例(暴破)

通过获得 getDeclaredConstructor 强行获得构造器 -> 设置反射的对象在使用时取消 Java 语言访问检查 ->私有构造器.newInstance( 参数<Class> ..);

​ 可以看到如果开启安全性检查即使获得了私有构造器也无法使用这个构造器创建一个新的对象,强行调用会抛出 非法访问 的异常。

image-20230630104602630

/**
     * 通过私有构造器创建实例
     * @throws Exception
     */
    @Test
    public void m3() throws ClassNotFoundException, InstantiationException, IllegalAccessException, NoSuchMethodException, InvocationTargetException {
        Class<?> cls = Class.forName("com.purearc.pojo.User");
        Constructor<?> declaredConstructor = cls.getDeclaredConstructor(String.class,int.class);
        //获得的 declaredConstructor 是私有的
        declaredConstructor.setAccessible(true);
        Object newnewObj = declaredConstructor.newInstance("newnewName", 20);
        System.out.println(newnewObj);
    }

image-20230630105644558

(二)属性获得和操作

public class Student {
    public int age;
    private static String name;
    public Student(){}
}

1、根据属性名获取Field对象

Field property = clazz对象.getDeclaredField(属性名);
    @Test
    public void m1() throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException, NoSuchFieldException {
        Class<?> cls = Class.forName("com.purearc.pojo.Student");
        Object o = cls.getConstructor().newInstance();
        Field age = cls.getField("age");
        age.set(o,88);
        System.out.println(o);
        System.out.println(age.get(o));
    }

image-20230630152205784

2、绕过安全性检查访问 private

property.setAccessible(true);

​ 如果是静态属性,则 set 和 get 中的参数 o,可以写成 null。

    @Test
    public void m2() throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException, NoSuchFieldException {
        Class<?> cls = Class.forName("com.purearc.pojo.Student");
        Object o = cls.getConstructor().newInstance();
        Field name = cls.getDeclaredField("name");
        name.setAccessible(true);
        //name.set(o,"newName");
        name.set(null,"newName");
        System.out.println(name.get(null));
    }

​ 不关闭检查还是有异常抛出。

java.lang.IllegalAccessException: Class com.purearc.question.OperMember can not access a member of class com.purearc.pojo.Student with modifiers "private static"

image-20230630153959149

(三)方法操作

1、根据方法名和参数列表获取Method方法对象

public class OperMethod {
    @Test
    public void m1() throws ClassNotFoundException, InstantiationException, IllegalAccessException, NoSuchMethodException, InvocationTargetException {
        Class<?> cls = Class.forName("com.purearc.pojo.Boss");
        Object bos = cls.newInstance();
        Method hi = cls.getDeclaredMethod("hi",String.class);
        hi.invoke(bos,"  purearc");
    }

image-20230630170843866

2、绕过安全性检查使用方法

​ 如果是静态方法,则 invoke 的参数 instance ,可以写成 null

​ 在反射中如果方法有返回值,统一返回 Obeject 类型,运行类型和方法定义的返回值类型相同,可以认为在接受的时候不知道是什么类型所以用顶级父类接受。

 @Test
    public void m2() throws ClassNotFoundException, InstantiationException, IllegalAccessException, NoSuchMethodException, InvocationTargetException {
        Class<?> cls = Class.forName("com.purearc.pojo.Boss");
        Object bos = cls.newInstance();
        Method say = cls.getDeclaredMethod("say",int.class,String.class,char.class);
        say.setAccessible(true);
        //因为 say 方法是静态的,所以才可以传入 null
        System.out.println(say.invoke(null, 100, "hi", 'p').getClass());
        String res = (String)say.invoke(null, 100, "hi", 'p');
        System.out.println(res);
    }

image-20230630170723303

posted @ 2023-06-30 17:30  Purearc  阅读(118)  评论(1编辑  收藏  举报