在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限制
- 封闭性检查:增加了对定义类的封闭性(enclosure)的检查。这意味着当一个类(或接口)被定义时,必须确保它的封闭类(如果有的话)也是由同一个类加载器加载的。这是为了确保类加载器的封闭性不被破坏,从而增强安全性。
- 访问控制:加强了对类定义时访问控制的检查。例如,如果一个类试图访问某些受保护的成员或其他受限资源,这些访问请求必须通过 Java 的访问控制检查。
- 模块化:随着 Java 模块系统的引入,对模块边界的保护也更加严格。
defineClass必须确保类的定义不会违反模块规则,比如一个非公开的类不能被不同模块的代码访问。
这使得我们无法通过反射来修改一个不在同一模块中的私有属性
为解决这些问题,我们可以使用MethodHandles.Lookup
MethodHandles.Lookup
MethodHandles 和传统的反射 API (如 java.lang.reflect)都提供了在 Java 中执行动态代码调用的能力,但它们在设计理念、性能、使用方式以及适用场景方面有着显著的不同。下面是一些关键的区别:
- 设计和性能
-
性能:
MethodHandles:MethodHandle对象一旦被创建,它的性能非常接近直接的方法调用。MethodHandle能够被 JVM 的即时编译器(JIT)优化,甚至内联,从而提供非常高的执行效率。- 反射:传统的反射调用(使用
Method.invoke()等)在性能上通常较慢,因为它们涉及更多的检查和间接层,虽然近年来 JVM 在这方面也有所优化。
- 类型检查和安全性
-
类型检查:
MethodHandles:MethodHandles查找时不执行类型检查,但调用时进行。这意味着类型不匹配的错误将在运行时发生,提供了更大的灵活性和动态类型适配的能力。- 反射:反射在查找和调用时都会执行类型检查,错误更早被发现,但这也使得反射的调用更加严格和笨重。
- 语法和易用性
-
易用性:
MethodHandles:虽然MethodHandle提供了更强大的性能和灵活性,但其 API 相对复杂和抽象,新手可能会觉得难以理解和使用。- 反射:反射 API 相对简单直观,很多开发者对其已经相当熟悉,尽管性能较低。
- 访问权限和安全模型
-
安全模型:
MethodHandles:通过MethodHandles.Lookup类的设计,MethodHandle可以绕过 Java 的访问控制检查(如果Lookup实例有足够的权限)。这允许在同一个类加载器或模块下执行私有或受保护的方法调用。- 反射:虽然可以通过设置
Accessible标志来访问私有成员,但这通常会受到安全管理器的限制和更多的审查。
- 应用场景
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);
}
}