使用java实现eval函数,可以执行多行代码字符串。(把这篇扔到前面来的原因是在学习冰蝎的时候用到了有关的知识。)

思路

将用户的输入写入到一个临时java文件中,加载这个临时类,通过反射机制,调用这个临时类的方法来实现eval。

准备知识

类的加载

当程序主动使用某个类时,如果该类还未被加载到内存中,则系统会通过加载,连接,初始化3个步骤来对该类进行初始化。这三个步骤称为类加载或类初始化。

加载

将类的class文件读入内存,为之创建一个java.lang.Class对象。又类的加载由类加载器完成,类加载器同城由JVM提供,JVM提供的类加载器称为系统类加载器。

连接

把类的二进制数据合并到JRE中

初始化

对静态Field进行初始化

JVM初始化一个类包含的步骤
  1. 假如这个类还没有被加载和连接,则程序先加载并连接该类。

  2. 假如该类的直接父类还没有被初始化,则先初始化其直接父类

  3. 假如类中有初始化语句,则系统依次执行这些初始化语句。(static修饰的代码区)当执行第2个步骤时,系统对直接父类的初始化步骤也遵循此步骤1~3;如果该直接父类又有直接父类,则系统再次重复这 3个步骤来先初始化这个父类…

    所以JVM最先初始化的总是java.lang.Object类

    ClassLoader类的loadClass()方法来加载某个类时,该类只是被加载,使用Class的forName()静态方法才会导致强制初始化该类,

类加载机制

JVM的类加载机制主要有3种。

  1. 全盘负责。当一个类加载器负责加载某个Class时,该Class所依赖的和引用其他Class也将由该类加载器来载入。
  2. 父类委托,先让父类加载器试图加载该Class,只有在父类加载器无法加载该类时才尝试从自己的类路径中加载该类。(用户类加载器—系统类加载器—拓展类加载器—-根类加载器)
  3. 缓存机制。所有加载过的Class都会被缓存,当程序中需要使用某个Class时,类加载器先从缓存区中搜寻该Class,只有当缓存区中不存在该Class对象时,系统才会读取该类对应的二进制数据,并转化成Class对象。

类加载中的常用函数解析

protected Class<?> findClass(String name)  throws ClassNotFoundException

使用指定的二进制名称查找类。此方法应该被类加载器的实现重写,该实现按照委托模型来加载类。在通过父类加载器检查所请求的类后,此方法将被 [loadClass] 方法调用。默认实现抛出一个 ClassNotFoundException。在实现自己的类加载器时只需要重写这个方法。重写这个方法时,我们需要对java文件进行编译,并且读取编译完的class文件,将读取到的字节生成class实例。

protected final Class<?> defineClass(String name, byte[] ,int off,int len)throws ClassFormatError

name- 所需要的类的 [二进制名称],如果不知道此名称,则该参数为 null

b - 组成类数据的字节。 offoff+len-1 之间的字节应该具有《 Java Virtual Machine Specification》定义的有效类文件的格式。

off - 类数据的 b 中的起始偏移量

len - 类数据的长度

这个方法将一个 byte 数组转换为 Class 类的实例。重写findclass时需要使用这个函数将字节生成class实例。

可以理解为其实defineClass才是findClass这个方法的核心,真正的Class的来源。

最后给出实现:代码托管在github中。https://github.com/buptchk/java