使用 Byte Buddy 在 运行 时间动态生成单个函数(没有子函数),表示二叉表达式树
Dynamically generate a single function (without subfunctions), representing a binary expression tree, at run time with Byte Buddy
简介
我想比较一些在 运行 时生成代码的库。目前我接触到Javassist和Byte Buddy的表面。
作为概念证明,我正在尝试解决一个小问题,这是解决更复杂问题的起点。
基本上我有一个 binary expression tree,我想将其转换成一行代码并将其加载到我的 java 运行 时间。为简单起见,到目前为止我只添加了节点和常量作为叶子。
Javasist 参考资料
我已经在 Javassist 中找到了执行此操作的方法(至少适用于具有两个叶子的单个节点)。代码如下所示:
public class JavassistNodeFactory{
public DynamicNode generateDynamicNode(INode root){
DynamicNode dynamicNode = null;
try {
CtClass cc = createClass();
interceptMethod(root, cc);
compileClass(cc);
dynamicNode = instantiate(cc);
}catch (Exception e){
System.out.println("Error compiling class with javassist: "+ e.getMessage());
e.printStackTrace();
}
return dynamicNode;
}
private DynamicNode instantiate(CtClass cc) throws CannotCompileException, IllegalAccessException, InstantiationException {
Class<?> clazz = cc.toClass();
return (DynamicNode) clazz.newInstance();
}
private void compileClass(CtClass cc) throws NotFoundException, IOException, CannotCompileException {
cc.writeFile();
}
private void interceptMethod(INode root, CtClass cc) throws NotFoundException, CannotCompileException {
CtMethod calculateMethod = cc.getSuperclass().getDeclaredMethod("calculateValue",null);
calculateMethod.setBody("return "+ nodeToString(root)+ ";");
}
private CtClass createClass() throws CannotCompileException, NotFoundException {
ClassPool pool = ClassPool.getDefault();
CtClass cc = pool.makeClass(
"DN"+ UUID.randomUUID().toString().replace("-","")
);
cc.setSuperclass(pool.get("org.jamesii.mlrules.util.runtimeCompiling.DynamicNode"));
return cc;
}
private static String nodeToString(INode node){
if (node.getName().equals("")){
return ((ValueNode)node).getValue().toString();
}else{
List<? extends INode> children = node.getChildren();
assert(children.size()==2);
return ("("+nodeToString(children.get(0))+node.getName()+nodeToString(children.get(1))+")");
}
}
}
DynamicNode
class 看起来像这样:
public class DynamicNode implements INode {
@Override
public <N extends INode> N calc() {
Double value = calculateValue();
return (N) new ValueNode<Double>(value);
}
@Override
public List<? extends INode> getChildren() {
return null;
}
@Override
public String getName() {
return null;
}
private Double calculateValue() {
return null;
}
}
重要的部分是 nodeToString()
函数,我从给定的根节点生成一个由 returned 字符串表示的算术公式。 ValueNode
是具有常量值的树叶,将 return 编辑为字符串。
其他节点(只为我的情况添加节点)将为每个 child 递归调用函数并打印表达式周围的括号以及打印运算符(return 由 getName()
函数编辑)在两个children中间(简称:"(leftChild+rightChild)"
)。
calculateValue()
函数的主体将由 Javassist 在 interceptMethod()
函数中更改为 return 生成公式的结果。
字节好友尝试
我已经使用 Byte Buddy 来实现类似的解决方案。但随着我深入研究概念和文档,我越来越觉得这不是 Byte Buddy 设计的问题。大多数示例和问题似乎都集中在对其他函数的函数委托上(实际上在编译时已经存在,并且仅在 运行 时连接)。这对我来说并不是很方便,因为我无法在编译时知道我想要转换的实际树。可能可以使用底层的 ASM 库,但我想避免自己(以及我可能的继任者)处理字节码。
我有一个(显然不起作用的)基本实现,但我卡在必须为 Byte Buddy 库的 intercept()
函数提供 Implementation
的地步。我最后的状态是这样的:
public class ByteBuddyNodeFactory{
@Override
public DynamicNode generateDynamicNode(INode root) {
DynamicNode dynamicNode = null;
try {
Class<?> dynamicType = new ByteBuddy()
.subclass(DynamicNode.class)
.name("DN"+ UUID.randomUUID().toString().replace("-",""))
//this is the point where I have problems
//I don't know how to generate the Implementation for the intercept() function
//An attempt is in the nodeToImplementation() function
.method(ElementMatchers.named("calculateValue")).intercept(nodeToImplementation(root))
.make()
.load(Object.class.getClassLoader())
.getLoaded();
dynamicNode = (DynamicNode) dynamicType.newInstance();
} catch (Exception e) {
System.out.println("Error compiling testclass with bytebuddy: " + e.getMessage());
e.printStackTrace();
}
return dynamicNode;
}
private Implementation.Composable nodeToImplementation(INode node){
if (node.getName().equals("")){
return (Implementation.Composable)FixedValue.value(((ValueNode)node).getValue());
}else{
List<? extends INode> children = node.getChildren();
assert(children.size()==2);
switch (node.getName()){
case ("+"):
//This is the point where I am completely lost
//This return is just the last thing I tried and may be not even correct Java code
// But hopefully at least my intention gets clearer
return (MethodCall.invoke((Method sdjk)-> {
return (nodeToImplementation(children.get(0)).andThen(node.getName().andThen(nodeToImplementation(children.get(1)))));
}));
default:
throw new NotImplementedException();
}
}
}
}
我的想法是将子函数连接在一起,因此尝试使用 Composable
Implementation
。我尝试 return a MethodDelegation
但正如我所提到的,我觉得这不是正确的方法。在那之后我尝试了 MethodCall
但我很快意识到我也完全不知道如何使用这个 ^^
问题
是否可以在 Byte Buddy 中像在 Javassist 中一样动态地从树结构生成函数,而无需调用尽可能多的子函数?
如果可能,我该怎么做?
如果不可能:是否可以使用其他字节码操作库,如 cglib.
我更愿意保持高于字节码的抽象级别,因为底层概念的研究应该与我的问题无关。
使用 Byte Buddy 的高级 API 无法轻松完成您尝试做的事情。相反,如果您想使用 Byte Buddy,您应该 assemble 使用 StackManipulation
s 的方法。堆栈操作仍然包含 Java 字节代码,但这些位应该非常简单,因此很容易实现。
Byte Buddy 不针对这种情况的原因是,您通常可以找到比 assemble 代码片段更好的代码抽象。为什么您的节点不能实现随后从您的检测方法调用的实际实现? JIT 编译器通常会优化此代码以获得与手动内联代码相同的结果。此外,您保留了可调试性并降低了代码的复杂性。
简介
我想比较一些在 运行 时生成代码的库。目前我接触到Javassist和Byte Buddy的表面。
作为概念证明,我正在尝试解决一个小问题,这是解决更复杂问题的起点。
基本上我有一个 binary expression tree,我想将其转换成一行代码并将其加载到我的 java 运行 时间。为简单起见,到目前为止我只添加了节点和常量作为叶子。
Javasist 参考资料
我已经在 Javassist 中找到了执行此操作的方法(至少适用于具有两个叶子的单个节点)。代码如下所示:
public class JavassistNodeFactory{
public DynamicNode generateDynamicNode(INode root){
DynamicNode dynamicNode = null;
try {
CtClass cc = createClass();
interceptMethod(root, cc);
compileClass(cc);
dynamicNode = instantiate(cc);
}catch (Exception e){
System.out.println("Error compiling class with javassist: "+ e.getMessage());
e.printStackTrace();
}
return dynamicNode;
}
private DynamicNode instantiate(CtClass cc) throws CannotCompileException, IllegalAccessException, InstantiationException {
Class<?> clazz = cc.toClass();
return (DynamicNode) clazz.newInstance();
}
private void compileClass(CtClass cc) throws NotFoundException, IOException, CannotCompileException {
cc.writeFile();
}
private void interceptMethod(INode root, CtClass cc) throws NotFoundException, CannotCompileException {
CtMethod calculateMethod = cc.getSuperclass().getDeclaredMethod("calculateValue",null);
calculateMethod.setBody("return "+ nodeToString(root)+ ";");
}
private CtClass createClass() throws CannotCompileException, NotFoundException {
ClassPool pool = ClassPool.getDefault();
CtClass cc = pool.makeClass(
"DN"+ UUID.randomUUID().toString().replace("-","")
);
cc.setSuperclass(pool.get("org.jamesii.mlrules.util.runtimeCompiling.DynamicNode"));
return cc;
}
private static String nodeToString(INode node){
if (node.getName().equals("")){
return ((ValueNode)node).getValue().toString();
}else{
List<? extends INode> children = node.getChildren();
assert(children.size()==2);
return ("("+nodeToString(children.get(0))+node.getName()+nodeToString(children.get(1))+")");
}
}
}
DynamicNode
class 看起来像这样:
public class DynamicNode implements INode {
@Override
public <N extends INode> N calc() {
Double value = calculateValue();
return (N) new ValueNode<Double>(value);
}
@Override
public List<? extends INode> getChildren() {
return null;
}
@Override
public String getName() {
return null;
}
private Double calculateValue() {
return null;
}
}
重要的部分是 nodeToString()
函数,我从给定的根节点生成一个由 returned 字符串表示的算术公式。 ValueNode
是具有常量值的树叶,将 return 编辑为字符串。
其他节点(只为我的情况添加节点)将为每个 child 递归调用函数并打印表达式周围的括号以及打印运算符(return 由 getName()
函数编辑)在两个children中间(简称:"(leftChild+rightChild)"
)。
calculateValue()
函数的主体将由 Javassist 在 interceptMethod()
函数中更改为 return 生成公式的结果。
字节好友尝试
我已经使用 Byte Buddy 来实现类似的解决方案。但随着我深入研究概念和文档,我越来越觉得这不是 Byte Buddy 设计的问题。大多数示例和问题似乎都集中在对其他函数的函数委托上(实际上在编译时已经存在,并且仅在 运行 时连接)。这对我来说并不是很方便,因为我无法在编译时知道我想要转换的实际树。可能可以使用底层的 ASM 库,但我想避免自己(以及我可能的继任者)处理字节码。
我有一个(显然不起作用的)基本实现,但我卡在必须为 Byte Buddy 库的 intercept()
函数提供 Implementation
的地步。我最后的状态是这样的:
public class ByteBuddyNodeFactory{
@Override
public DynamicNode generateDynamicNode(INode root) {
DynamicNode dynamicNode = null;
try {
Class<?> dynamicType = new ByteBuddy()
.subclass(DynamicNode.class)
.name("DN"+ UUID.randomUUID().toString().replace("-",""))
//this is the point where I have problems
//I don't know how to generate the Implementation for the intercept() function
//An attempt is in the nodeToImplementation() function
.method(ElementMatchers.named("calculateValue")).intercept(nodeToImplementation(root))
.make()
.load(Object.class.getClassLoader())
.getLoaded();
dynamicNode = (DynamicNode) dynamicType.newInstance();
} catch (Exception e) {
System.out.println("Error compiling testclass with bytebuddy: " + e.getMessage());
e.printStackTrace();
}
return dynamicNode;
}
private Implementation.Composable nodeToImplementation(INode node){
if (node.getName().equals("")){
return (Implementation.Composable)FixedValue.value(((ValueNode)node).getValue());
}else{
List<? extends INode> children = node.getChildren();
assert(children.size()==2);
switch (node.getName()){
case ("+"):
//This is the point where I am completely lost
//This return is just the last thing I tried and may be not even correct Java code
// But hopefully at least my intention gets clearer
return (MethodCall.invoke((Method sdjk)-> {
return (nodeToImplementation(children.get(0)).andThen(node.getName().andThen(nodeToImplementation(children.get(1)))));
}));
default:
throw new NotImplementedException();
}
}
}
}
我的想法是将子函数连接在一起,因此尝试使用 Composable
Implementation
。我尝试 return a MethodDelegation
但正如我所提到的,我觉得这不是正确的方法。在那之后我尝试了 MethodCall
但我很快意识到我也完全不知道如何使用这个 ^^
问题
是否可以在 Byte Buddy 中像在 Javassist 中一样动态地从树结构生成函数,而无需调用尽可能多的子函数?
如果可能,我该怎么做?
如果不可能:是否可以使用其他字节码操作库,如 cglib.
我更愿意保持高于字节码的抽象级别,因为底层概念的研究应该与我的问题无关。
使用 Byte Buddy 的高级 API 无法轻松完成您尝试做的事情。相反,如果您想使用 Byte Buddy,您应该 assemble 使用 StackManipulation
s 的方法。堆栈操作仍然包含 Java 字节代码,但这些位应该非常简单,因此很容易实现。
Byte Buddy 不针对这种情况的原因是,您通常可以找到比 assemble 代码片段更好的代码抽象。为什么您的节点不能实现随后从您的检测方法调用的实际实现? JIT 编译器通常会优化此代码以获得与手动内联代码相同的结果。此外,您保留了可调试性并降低了代码的复杂性。