16

我正在寻找一种通过重写字节码并重新加载类来动态地将字段添加到线程的方法,但不确定它是否可能。欢迎任何指点。我找到了一些关于修改和加载类的信息,并且我知道 JRebel 可以无缝地热交换您的代码,但不确定相同的方法/工具是否适用于此。

这里的动机是探索一种理论上更好的替代线程本地对象的方法。如果该方法有效,我应该能够用注释替换线程本地,并且结果应该优于当前的 JDK 实现。

PS:请救救我“万恶之源”

澄清用例:

想象一下,我有一个带有 ThreadLocal 的类:


class A {
   ThreadLocal<Counter> counter;
   ...
   counter.get().inc()
}

我想用注释替换它:


class A {
   @ThreadLocal
   Counter counter;
   ...
   counter.inc()
}

但不是生成上面的代码,我想改变 Thread 以便 Thread 现在有一个 Acounter 字段,实际代码将是:


class A {
   // Nothing here, field is now in Thread
   ...
   Thread.currentThread().Acounter.inc()
}
4

8 回答 8

11

目前不可能在运行时重新定义一个类,这样重新定义就会产生新的方法或字段。这是由于在堆中扫描所有现有实例并转换它们 + 它们的引用 + 潜在的不安全字段偏移基础更新程序(如 AtomicFieldUpdater)所涉及的复杂性。

这个限制可能会作为JEP-159的一部分被取消,但正如在并发兴趣邮件组中所讨论的那样,这是一个巨大的影响变化,因此可能永远不会发生。

使用 Javaassist/similar 将允许将类转换为具有新方法/字段的新类。此类可以由 ClassLoader 加载并在运行时使用,但它的定义不会替换现有实例。因此,不可能将此方法与代理结合使用来重新定义类,因为仪表重新定义受到限制:“重新定义可能会更改方法体、常量池和属性。重新定义不得添加、删除或重命名领域...”见这里

所以现在,不。

于 2013-06-18T07:06:48.040 回答
5

如果您想在运行时更改“类”的行为,可以尝试javassist。API 在这里

于 2013-06-07T15:02:23.813 回答
5

我见过动态重新加载 JAR 的自定义类加载解决方案 - 您为ClassLoader每个 JAR 文件定义一个并使用它从该 JAR 加载类;要重新加载整个 JAR,您只需“杀死”它的ClassLoader实例并创建另一个实例(在替换 JAR 文件之后)。

我认为不可能以Thread这种方式调整 Java 的内部类,因为您无法控制System ClassLoader. 一个可能的解决方案是让一个CustomThreadWeaver类生成一个新类,该类Thread使用您需要的变量进行扩展,并使用自定义DynamicWeavedThreadClassLoader来加载它们。

祝你好运,当你成功时向我们展示你的怪物;-)

于 2013-06-12T12:20:32.603 回答
5

可以使用检测工具,也可能 使用 javassist 等库来即时修改代码。(但是,目前无法添加和删除字段、方法或构造函数)

//Modify code using javassist and call CtClass#toBytecode() or load bytecode from file
byte[] nevcode;
Class<?> clz = Class.forName("any.class.Example");
instrumentationInstace.redefineClasses(new ClassDefinition(clz, nevcode));

不要忘记添加Can-Redefine-Classes: true到您的 java 代理的清单中。

真实示例 -string.replace(CharSequence, CharSequence)使用 javassist 优化 java < 9:

String replace_src = 
    "{String str_obj = this;\n"
    + "char[] str = this.value;\n"
    + "String find_obj = $1.toString();\n"
    + "char[] find = find_obj.value;\n"
    + "String repl_obj = $2.toString();\n"
    + "char[] repl = repl_obj.value;\n"
    + "\n"
    + "if(str.length == 0 || find.length == 0 || find.length > str.length) {\n"
    + "    return str_obj;\n"
    + "}\n"
    + "int start = 0;\n"
    + "int end = str_obj.indexOf(find_obj, start);\n"
    + "if(end == -1) {\n"
    + "    return str_obj;\n"
    + "}\n"
    + "int inc = repl.length - find.length;\n"
    + "int inc2 = str.length / find.length / 512;\ninc2 = ((inc2 < 16) ? 16 : inc);\n"
    + "int sb_len = str.length + ((inc < 0) ? 0 : (inc * inc2));\n"
    + "StringBuilder sb = (sb_len < 0) ? new StringBuilder(str.length) : new StringBuilder(sb_len);\n"
    + "while(end != -1) {\n"
    + "    sb.append(str, start, end - start);\n"
    + "    sb.append(repl);\n"
    + "    start = end + find.length;\n"
    + "    end = str_obj.indexOf(find_obj, start);\n"
    + "}\n"
    + "if(start != str.length) {\n"
    + "    sb.append(str, start, str.length - start);\n"
    + "}\n"
    + "return sb.toString();\n"
    +"}";


ClassPool cp = new ClassPool(true);
CtClass clz = cp.get("java.lang.String");
CtClass charseq = cp.get("java.lang.CharSequence");

clz.getDeclaredMethod("replace", new CtClass[] {
        charseq, charseq
}).setBody(replace_src);

instrumentationInstance.redefineClasses(new ClassDefinition(Class.forName(clz.getName(), false, null), clz.toBytecode()));
于 2015-10-14T15:45:24.177 回答
2

这似乎是一个使用正确工具来完成工作的问题。此处提出了类似的问题:Another Stack Overflow Question和 Javaassist 字节码操作库是一个可能的解决方案。

但如果没有进一步详细说明尝试这样做的原因,似乎真正的答案是使用正确的工具来完成这项工作。例如,Groovy能够动态地将方法添加到语言中

于 2013-06-17T18:48:27.540 回答
1

您可以尝试创建一个使用java.lang.instrument API的 JVM 代理,更具体地说,使用“促进已加载类的检测”的retransform 方法,然后使用 Javassist(或 ASM),如前所述处理字节码。

有关java.lang.instrument API的更多信息

于 2013-06-18T05:17:18.530 回答
0

要执行您想要的操作,更简单的替代方法是使用 Thread 的子类,运行它,然后在该线程内执行示例中的代码(连同 currentThread() 到您的子类的强制转换)。

于 2013-06-18T11:47:33.677 回答
-2

你试图做的事情是不可能的。

由于您已经了解 ThreadLocal,因此您已经知道建议的解决方案是什么。

或者,您可以对 Thread 进行子类化并添加自己的字段;但是,只有您为该类显式创建的那些线程才会具有这些字段,因此您仍然必须能够“回退”到使用本地线程。

真正的问题是“为什么?”,例如“为什么本地线程不足以满足您的要求?”

于 2013-06-13T13:34:13.200 回答