在java高版本,如何使用动态语言特性。

180 阅读5分钟

在java高版本,如何使用动态语言特性。

什么是动态语言特性

动态语言特性,可以在代码执行的过程中根据输入来改变代码的执行逻辑,甚至是调用的方法。

例如在php中,有如下代码

<?php
class Pig {
    public function call() {
        echo "hengheng";
    }
}
​
class Dog {
    public function call() {
        echo "wangwang";
    }
}
​
// 检查 GET 请求中是否有 'animal' 参数
if (isset($_GET['animal'])) {
    $className = ucfirst(strtolower($_GET['animal'])); // 将首字母大写,其余小写
​
    // 使用类名字符串来创建对象
    if (class_exists($className)) {
        $animal = new $className();
        $animal->call();
    } else {
        echo "Unknown animal type";
    }
} else {
    echo "No animal type specified";
}
?>

我们可以看到,在这段代码中,我们直接利用GET获得到的字符串来改变了方法的调用。但在java中,我们无法如此自由的去动态执行。

为解决动态执行的问题,我们可以使用反射的方式。

java中的反射

java中的反射从底层原理来说还比较复杂。甚至要了解到klass与jvm堆栈问题。暂且不表。在低版本jdk中,我们可以通过反射获取任意类、接口、方法、属性等。无论他是否是私有的。同上,利用反射我们也可以动态执行一些代码。

测试类如下

package com.bilisheep;
​
public class Test {
    private static String string = "Class Name is test";
​
​
    public static void getString() {
        System.out.println(string);
    }
​
    @Override
    public String toString() {
        return "Test{" +
                "string='" + string + ''' +
                '}';
    }
}
class Test1{
    private static String string = "Class Name is test1";
​
​
    public static void getString() {
        System.out.println(string);
    }
​
    @Override
    public String toString() {
        return "Test1{" +
                "string='" + string + ''' +
                '}';
    }
}
​

利用反射动态执行

package com.bilisheep;
​
import org.springframework.cglib.core.ReflectUtils;
​
import java.lang.reflect.Method;
import java.util.Base64;
​
​
public class Main{
​
    public static void main(String[] args) throws Exception {
        String aClass = "com.bilisheep.Test";
        Class<?> aClass1 = Class.forName(aClass);
        Method getString = aClass1.getMethod("getString", null);
        getString.invoke(null,null);
    }
}
​

在很多常用的链子和漏洞利用中,我们都需要用到反射来进行更改。有些私有变量也需要利用反射来设置值。

但在高版本的jdk中,出现了以下策略,对反射进行了限制

高版本jdk限制

  1. 封闭性检查:增加了对定义类的封闭性(enclosure)的检查。这意味着当一个类(或接口)被定义时,必须确保它的封闭类(如果有的话)也是由同一个类加载器加载的。这是为了确保类加载器的封闭性不被破坏,从而增强安全性。
  2. 访问控制:加强了对类定义时访问控制的检查。例如,如果一个类试图访问某些受保护的成员或其他受限资源,这些访问请求必须通过 Java 的访问控制检查。
  3. 模块化:随着 Java 模块系统的引入,对模块边界的保护也更加严格。defineClass 必须确保类的定义不会违反模块规则,比如一个非公开的类不能被不同模块的代码访问。

这使得我们无法通过反射来修改一个不在同一模块中的私有属性

为解决这些问题,我们可以使用MethodHandles.Lookup

MethodHandles.Lookup

MethodHandles 和传统的反射 API (如 java.lang.reflect)都提供了在 Java 中执行动态代码调用的能力,但它们在设计理念、性能、使用方式以及适用场景方面有着显著的不同。下面是一些关键的区别:

  1. 设计和性能
  • 性能

    • MethodHandlesMethodHandle 对象一旦被创建,它的性能非常接近直接的方法调用。MethodHandle 能够被 JVM 的即时编译器(JIT)优化,甚至内联,从而提供非常高的执行效率。
    • 反射:传统的反射调用(使用 Method.invoke() 等)在性能上通常较慢,因为它们涉及更多的检查和间接层,虽然近年来 JVM 在这方面也有所优化。
  1. 类型检查和安全性
  • 类型检查

    • MethodHandlesMethodHandles 查找时不执行类型检查,但调用时进行。这意味着类型不匹配的错误将在运行时发生,提供了更大的灵活性和动态类型适配的能力。
    • 反射:反射在查找和调用时都会执行类型检查,错误更早被发现,但这也使得反射的调用更加严格和笨重。
  1. 语法和易用性
  • 易用性

    • MethodHandles:虽然 MethodHandle 提供了更强大的性能和灵活性,但其 API 相对复杂和抽象,新手可能会觉得难以理解和使用。
    • 反射:反射 API 相对简单直观,很多开发者对其已经相当熟悉,尽管性能较低。
  1. 访问权限和安全模型
  • 安全模型

    • MethodHandles:通过 MethodHandles.Lookup 类的设计,MethodHandle 可以绕过 Java 的访问控制检查(如果 Lookup 实例有足够的权限)。这允许在同一个类加载器或模块下执行私有或受保护的方法调用。
    • 反射:虽然可以通过设置 Accessible 标志来访问私有成员,但这通常会受到安全管理器的限制和更多的审查。
  1. 应用场景
  • MethodHandles:适用于性能敏感和需要高度动态行为的系统,如语言运行时、高级框架和动态代理实现。
  • 反射:更适合标准应用程序中的动态操作,如依赖注入、序列化框架和配置处理,其中开发的简便性可能比性能更重要。

我们最常使用的是,MethodHandles.Lookup#privateLookupIn,他可以根据我们的限制条件执行对应的私有方法。

  • 修改变量
public class ExampleClass {
    private int privateInt = 1;
​
    public int getPrivateInt() {
        return privateInt;
    }
}
​
public class Main {
    public static void main(String[] args) throws Throwable {
        ExampleClass example = new ExampleClass();
​
        // 打印修改前的值
        System.out.println("Before: " + example.getPrivateInt());
​
        // 获取 Lookup 对象
        MethodHandles.Lookup lookup = MethodHandles.privateLookupIn(ExampleClass.class, MethodHandles.lookup());
​
        // 获取 VarHandle
        VarHandle varHandle = lookup.findVarHandle(ExampleClass.class, "privateInt", int.class);
​
        // 修改私有变量的值
        varHandle.set(example, 123);
​
        // 打印修改后的值
        System.out.println("After: " + example.getPrivateInt());
    }
}
​
  • 调用方法
public class ExampleClass {
    private void privateMethod() {
        System.out.println("Private method is called!");
    }
}
​
public class Main {
    public static void main(String[] args) throws Throwable {
        ExampleClass example = new ExampleClass();
​
        // 获取 Lookup 对象
        MethodHandles.Lookup lookup = MethodHandles.privateLookupIn(ExampleClass.class, MethodHandles.lookup());
​
        // 获取 MethodHandle
        MethodHandle methodHandle = lookup.findSpecial(ExampleClass.class, "privateMethod", MethodType.methodType(void.class), ExampleClass.class);
​
        // 调用私有方法
        methodHandle.invoke(example);
    }
}
​