java在运行时动态生成一个函数(没有子函数),表示一个二进制表达式树,并使用Byte Buddy
导言
我想比较一些在运行时生成代码的库。此时,我接触了Javassist和Byte Buddy的表面
作为概念证明,我试图解决一个小问题,这是一个更复杂问题的起点
基本上,我有一个binary expression tree,我想把它转换成一行代码并加载到java运行时。为了简单起见,到目前为止,我只添加了节点和常量作为leaf
Javassist引用
我已经在Javassist中找到了实现这一点的方法(它至少适用于具有两个leaf的单个节点)。代码如下所示:
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
类如下所示:
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()
函数,其中我从给定的根节点生成一个由返回字符串表示的算术公式。ValueNode
是具有常量值的树的叶子,该常量值将作为字符串返回
其他节点(只为我的情况添加节点)将递归地为每个子函数调用函数和打印括号,同时在两个孩子的中间打印操作符(由^ {CD4}函数返回)(简称:{{CD5}})。br/>
Javassist将在interceptMethod()
函数中更改calculateValue()
函数体,以返回生成公式的结果
字节伙伴尝试
我和Byte Buddy一起玩过,以获得类似的解决方案。但随着我对概念和文档的深入了解,我越来越觉得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
{MethodDelegation
,但正如我提到的,我觉得这不是正确的方法。在那之后,我尝试了MethodCall
,但我很快意识到,我也完全不知道如何使用这个^^
问题:
在Byte Buddy中,是否可以像在Javassist中那样动态地从树结构生成函数,而不调用像我拥有的节点那样多的子函数
如果可能的话,我该怎么做
如果不可能的话:是否可以使用其他字节码操作库,比如cglib
我更愿意停留在字节码之上的抽象级别,因为对底层概念的研究应该与我的问题无关
# 1 楼答案
使用Byte Buddy的高级API,您不容易做到这一点。相反,如果您想使用Byte Buddy,应该使用
StackManipulation
来组装一个方法。堆栈操作仍然包含Java字节码,但这些字节码应该非常简单,易于实现Byte Buddy不针对这种情况的原因是,通常情况下,您可以为代码找到比汇编代码片段更好的抽象。为什么节点不能实现实际的实现,而实际的实现是从插入指令的方法调用的?JIT编译器通常会将此代码优化为与手动内联代码相同的结果。此外,还可以保持可调试性,降低代码的复杂性