首页 > 编程语言 >java反射机制

java反射机制

时间:2024-06-02 23:11:42浏览次数:24  
标签:反射 调用 java String Class public 机制 class

java反射机制

基础内容

反射调用就是指通过反射机制进行的方法调用。反射机制是Java编程语言的一个重要特性,它允许程序在运行时检查、操作和实例化类,方法,字段等,并在运行时获取类的信息以及动态调用类的方法。反射机制使得Java程序可以在运行时动态地加载、探测和使用类,而不需要在编译时就知道这些类的具体信息。

通过反射机制,可以实现以下功能:

  1. 获取类的信息:可以在运行时获取类的信息,如类的名称、父类、接口、成员变量、方法等。
  2. 实例化类:可以通过类的名称动态实例化类的对象。
  3. 调用方法:可以通过方法名动态调用类的方法。
  4. 获取字段信息:可以通过字段名获取类的字段信息。
  5. 动态代理:可以在运行时动态生成代理类,实现代理相关的功能。

java基本反射调用

反射调用一般分为4个步骤:

  • 类的实例化
  • 得到要调用类的class
  • 得到要调用的类中的方法(Method)
  • 方法调用(invoke)

obj.getClass()获得类:

demo:

import java.lang.reflect.Method;
public class Main{
    public static void main(String[] args) throws Exception{
        Reflect r1= new Reflect(); //对类进行是实例化
        r1.print(1,3); //直接调用
        Class c = r1.getClass(); //获取实列化对象所属类的类的对象
        Method m1 = c.getMethod("print",int.class,int.class); //获得方法
        Object i = m1.invoke(r1, 1, 2); //反射机制调用方法
    }
}
class Reflect{
    public void print(int a,int b){
        System.out.println(a+b);
    }
}

上面分别是两种调用类的方法的方法

第一种是通过类的实例化直接调用,

第二种是通过类的对象用getMethod获得方法,然后利用反射机制调用方法;

至于一些函数的具体使用如getClassgetMethod,invoke可以直接问gpt。

加上throws Exception是因为getMethod()invoke()方法会抛出NoSuchMethodExceptionIllegalAccessExceptionIllegalArgumentException等异常,可以去掉看看报错。

那么可以利用这种反射调用来调用恶意类实现rce:

import java.lang.reflect.Method;
public class Main{
    public static void main(String[] args)throws Exception {
        Runtime runtime=Runtime.getRuntime(); //获得Runtime的实例化
        runtime.exec("calc.exe"); //实列化直接调用
        Class c=runtime.getClass();
        Method m1=c.getMethod("exec",String.class);
        Object i=m1.invoke(runtime,"calc.exe");  //过程同上
    }
}

为什么这里不用Runtime runtime=new Runtime();来进行实例化,这个后面再说。

剩下的步骤的就不用多解释了,通过getClass()来获取类对象,从而用getMethod获取方法,在用反射进行调用来执行命令。

Class.forName()获得类

还可以直接通过Class.forName()反射获得类(根据给定的类名加载并返回对应的 Class 对象),通过反射调用方法getRuntime得到类的实例化,最后调用exec方法。

import java.lang.reflect.Method; //引入Method类
public class Main{
    public static void main(String[] args) throws Exception{ //抛出异常就不说了
        Class c=Class.forName("java.lang.Runtime");  //获得类得class对象
        Method m1= c.getMethod("getRuntime",new Class[]{}); //利用getMethod获得getRuntime方法
        Object runtime=m1.invoke(null); //利用反射来调用方法getRuntime
        Method m2=c.getMethod("exec",String.class); //利用getMethod获得getRuntime方法
        Object o=m2.invoke(runtime,"calc.exe"); //反射调用exec方法
    }
}

null是因为getRuntime是静态方法,静态方法不用实例化的对象就能调用。

QQ截图20240428222037

QQ截图20240602194554

这样可以获得Runtime得实列化对象后就能调用exec方法了。

简写一下:

public class Main{
    public static void main(String[] args) throws Exception{
        Object runtime=Class.forName("java.lang.Runtime").getMethod("getRuntime",new Class[]{}).invoke(null);
        Class.forName("java.lang.Runtime").getMethod("exec",String.class).invoke(runtime,"calc.exe");
    }
}

这样就不用引入Method类了(当然这里虽然获得实列化对象的效果一样,还是略有区别的)这里的实列化不能直接调用方法机进行命令执行runtime.exec("calc.exe");

想要直接调用方法还需要强制类型转换:

public class Main{
    public static void main(String[] args) throws Exception{
        Object runtime=Class.forName("java.lang.Runtime").getMethod("getRuntime",new Class[]{}).invoke(null);
        Runtime run=(Runtime) runtime;
        run.exec("calc.exe");
    }
}

上面写了两种获得Class类的方法,一般是三种方法:

  • obj.getClass():获得实例化对象所属类的的类。
  • Class.forName:知道某个类的名字,想获取到这个类,直接用forName来获取。
  • Test.class:如果已经加载了某个类,只是想获取到它的java.lang.Class对象,可以直接拿到它的Class属性。这个方法其实不属于反射了。

综上所述,反射三要素:类的对象(类),类的实列化对象,方法。

获得类的实列化对象

一、Class.newInstance获取

获得类的实例化对象一般使用Class.newInstance(虽然上面的例子是使用的是getRuntime方法),但很多情况下会失败,因为这个方法只能调用无参构造函数,对于一些类里面没有无参构造函数或者是私有属性的构造方法就会失败。

二、静态方法调用

上面的Runtime类就是一个例子,它的无参构造函数就是私有属性,但它是单列模式,可以通过静态方法getRuntime()调用其构造函数进行实例化(搞成私有属性利用方法进行调用的目的是为了防止建立多次数据库连接)。

QQ截图20240602220638

但是对于一些于没有静态方法的又该怎么办呢。

三、getConstructor()函数

对于没有静态方法或者构造函数有参时可以用一个新的反射方法进行获取,getConstructor()(无参有参都可以获取)

getMethod类似,getConstructor的接收参数是构造函数列表类型(因为构造函数也支持重载),获取到构造函数再使用newInstance来执行就可以获得类的实列化对象。

例如常用的另一种命令执行方式ProcessBuilder,使用反射来获取其构造函数,然后调用start来进行命令执行:

import java.lang.reflect.Constructor;
import java.util.Arrays;
import java.util.List;

public class Main{
    public static void main(String[] args)throws Exception{
        Class c=Class.forName("java.lang.ProcessBuilder"); //获取类
        Constructor con=c.getConstructor(List.class);  //获得有参构造函数,如果要获得无参就什么都不用添
        Object obj=con.newInstance(Arrays.asList("calc.exe")); //进行实列化
        ProcessBuilder pro = (ProcessBuilder) obj; //强制类型转换
        pro.start(); //调用方法
    }
}

全反射调用:

import java.lang.reflect.Constructor;
import java.util.Arrays;
import java.util.List;

public class Main{
    public static void main(String[] args)throws Exception{
        Class c=Class.forName("java.lang.ProcessBuilder");
        Constructor con=c.getConstructor(List.class);
        Object obj=con.newInstance(Arrays.asList("calc.exe"));
        c.getMethod("start").invoke(obj); //利用反射进行方法调用
    }
}

进入ProcessBuilder类看见还有个有参构造方法,参数类型是ProcessBuilder(String... command),即可变长参数

QQ截图20240602222959

对于可变长参数,java在编译时会编译为一个数组,也就是说,下面写法底层上是等效的(不能重载)

public void m1(String[] names){}
public void m1(String... names){}

因此对m1函数传参就直接传数组

String[] names={"hello","world","!!!!"};
m1(names);

所以还可以这样构造:

import java.lang.reflect.Constructor;

public class Main{
    public static void main(String[] args)throws Exception{
        Class c=Class.forName("java.lang.ProcessBuilder");
        Constructor con=c.getConstructor(String[].class);
        Object obj=con.newInstance(new String[][]{{"calc.exe"}}); //因为newInstance接受的参数是Object... args,所以这里传入的是二维数组,二维数组在处理是会当作一个整体对象处理
        c.getMethod("start").invoke(obj);
    }
}

或者把参数强制类型转换为object:

import java.lang.reflect.Constructor;

public class Main{
    public static void main(String[] args)throws Exception{
        Class c=Class.forName("java.lang.ProcessBuilder");
        Constructor con=c.getConstructor(String[].class);
        Object obj=con.newInstance((Object) new String[]{"calc.exe"}); //强制类型转换
        c.getMethod("start").invoke(obj);
    }
}

有参的构造方法大概就是这样了

当然这个类是可以直接调用的:

public class Main{
    public static void main(String[] args)throws Exception{
        ProcessBuilder pro=new ProcessBuilder("calc.exe");
        pro.start();
    }
}

四、getDeclared系列

构造函数为私有方法的解决办法:

这就涉及到getDeclared系列的反射了,与普通的 getMethodgetConstructor 区别是:

getMethod 系列方法获取的是当前类中所有公共方法,包括从父类继承的方法

getDeclaredMethod 系列方法获取的是当前类中“声明”的方法,是实在写在这个类里的,包括私有的方法,但从父类里继承来的就不包含了

getDeclaredMethod 的具体用法和 getMethod 类似, getDeclaredConstructor 的具体用法和 getConstructor 类似,不再赘述。

举个例子,上面说了Runtime的构造函数是私有方法,试一试:

import java.lang.reflect.Constructor;

public class Main {
    public static void main(String[] args) throws Exception {
        Class clazz = Class.forName("java.lang.Runtime");
        Constructor constructor = clazz.getDeclaredConstructor();  //获取私有的无参构造函数
        constructor.setAccessible(true); //修改其作用域,必须的
        Object runtime = constructor.newInstance();  //进行实例化
        clazz.getMethod("exec", String.class).invoke(runtime, "calc.exe"); //反射调用方法
    }
}

jdk11虽然有很多报错但是还是能执行:

QQ截图20240506191157

jdk1.8(java8)就能完美执行:

QQ截图20240506191429

总结一下:

  • 获取Class对象一般有三种方法Class.forName,obj.getClass,test.class
  • 获取实列化类一般直接newInstance,失败就两种情况要么是有参构造函数,要么就是私有的。一般有静态调用方法就直接用方法,如:getRuntime,没有就Constructor配合newInstance,私有的就用getDeclared系列的反射
  • 最后就是getMethod获取方法然后invoke进行调用方法。

利用反射一个重要目的就是绕沙盒机制(forName)

例如上下文只有intger类型的数字,怎么获得Runtime类

123.getClass().forNmae("java.lang.Runtime");

forName有两个函数重载:

  • Class<?> forName(String name)

  • Class<?> forName(String name, **boolean** initialize, ClassLoader loader)

第一个就是我们最常见的获取class的方式,其实可以理解为第二种方式的一个封装:

Class.forName(String name)
//等于
Class.forName(Stirng name,true,currentLoder)

默认情况下, forName 的第一个参数是类名;第二个参数表示是否初始化;第三个参数就 是 ClassLoader 。

ClassLoader 是什么呢?它就是一个“加载器”,告诉Java虚拟机如何加载这个类。 Java默认的 ClassLoader 就是根据类名来加载类, 这个类名是类完整路径,如 java.lang.Runtime

现在来说说第二个参数可能造成的漏洞:

第二个参数的意思是告诉java虚拟机是否执行“类初始化”

例子:

public class Main {
    {
        System.out.println("1block initial" + this.getClass());
    }

    static {
        System.out.println("2initail" + Main.class);
    }

    public Main() {
        System.out.println("3initial:" + this.getClass());
    }

    public static void main(String[] args) {
        Main main = new Main();
    }
}

结果:

QQ截图20240506152357

可以看到首先调用的是static{},然后是{},最后是构造函数{}

static{}是在“类初始化”调用,而{}中的代码会在构造函数的super()后面,在当前构造函数前面

所以当有如下代码(name可控):

public void ref(String[] name)throws Exception{
		Class.forName(name);
}

就可以构造个static{}的执行代码,例如:

import java.lang.Runtime; 
import java.lang.Process; 

public class TouchFile { 
	static { 
		try { 
            Runtime rt = Runtime.getRuntime(); 
		   String[] commands = {"touch", "/tmp/success"}; 
		   Process pc = rt.exec(commands); pc.waitFor(); 
		} 
	catch (Exception e) { 
		// do nothing 
		} 
	} 
}

那么就会在初始化类时执行代码。

标签:反射,调用,java,String,Class,public,机制,class
From: https://www.cnblogs.com/gaorenyusi/p/18227793

相关文章

  • 基于Java的敬老院管理系统设计与实现
    摘要新世纪以来,互联网与计算机技术的快速发展,我国也迈进网络化、集成化的信息大数据时代。对于大众而言,单机应用早已成为过去,传统模式早已满足不了当下办公生活等多种领域的需求,在一台电脑上不联网的软件少之又少,取而代之的是相互连通的软件系统,构成信息大数据社会。快......
  • 为师妹写的《Java并发编程之线程池十八问》被表扬啦!
    写在开头  之前给一个大四正在找工作的学妹发了自己总结的关于Java并发中线程池的面试题集,总共18题,将之取名为《Java并发编程之线程池十八问》,今天聊天时受了学妹的夸赞,心里很开心,毕竟自己整理的东西对别人起到了一点帮助,记录一下!Java并发编程之线程池十八问  经......
  • 堆排序-java
    这次主要讲了堆排序和堆的基本构造,下一期会详细讲述堆的各种基本操作。文章目录前言一、堆排序1.题目描述2.堆二、算法思路1.堆的存储2.结点下移down3.结点上移up4.堆的基本操作5.堆的初始化三、代码如下1.代码如下:2.读入数据:3.代码运行结果总结前言......
  • java选择题
    1.以下哪项不是java基础类型()A.intB.booleanC.StringD.float正确答案:CJava的基础数据类型包括:byte、short、int、long、float、double、char和boolean。String不是一个基础数据类型,而是一个对象类型,它在Java中表示字符串。单选题2.假定AB为一个类,则执行“ABa......
  • Java题目集4~6的总结
    前言面向对象编程课程的“答题判题程序-4”作业是一个综合性的练习,旨在加深学生对面向对象编程思想的理解,并实际应用于解决复杂问题。本作业要求学生设计并实现一个答题程序,模拟小型测试的全过程,包括题目信息、试卷信息、答题信息、学生信息的输入,以及答题结果的判断和输出。家......
  • JVM(Java虚拟机)、JMM(Java内存模型)笔记
    面试常见:请你谈谈你对JVM的理解?java8虚拟机和之前的变化更新?什么是OOM,什么是栈溢出StackOverFlowError?怎么分析?JVM的常用调优参数有哪些?内存快照如何抓取?怎么分析Dump文件?谈谈JVM中,类加载器你的认识?请你谈谈你对JVM的理解?JVM(Java虚拟机)是Java程序的运行环境,它允......
  • JAVA IO流(File类,字节流,字符流)
    File类分隔符:a.路径名称分隔符:windows:linux:/b.路径分隔符:一个路径和其他路径之间的分隔符;1.概述:文件和目录(文件夹)路径名的抽象表示2.File的静态成员staticStringpathSeparator:与系统有关的路径分隔符,为了方便,它被表示为一个字符串。staticStrings......
  • Java面试题:解释一下Java中的synchronized关键字,它是如何保证线程安全的?
    在Java中,synchronized关键字是一种同步锁机制,用于确保多个线程在访问共享资源时能够保持线程安全。线程安全是指在多线程环境下,当多个线程尝试同时访问共享资源时,任何时刻最多只有一个线程能够执行特定的代码段。synchronized关键字可以用于以下几个方面:方法同步:当synch......
  • 南昌航空大学大一下学期java题目集4-6总结性Blog-苏礼顺23201608
    一、前言——总结三次题目集的知识点、题量、难度等情况 关于知识点  这次的三次题目集更加进一步体现了面向对象程序设计的思想方法。主要是之前的三次题目集就只是利用了面向对象三大基础特性中的封装特性,而这三次的题目集增加了继承与多态,这正是面向对象设计的精髓所......
  • JAVA使用ForkJoinPool实现子任务拆分进行数值累加代码示例
      SumTask.javaimportjava.util.concurrent.RecursiveTask;/***定义任务和拆分逻辑*RecursiveTask<Long>这个是有返回值的*如果不需要返回值可以用RecursiveAction*/publicclassSumTaskextendsRecursiveTask<Long>{/***累加的开始值......