有 Java 编程相关的问题?

你可以在下面搜索框中键入要查询的问题!

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) 个答案

  1. # 1 楼答案

    使用Byte Buddy的高级API,您不容易做到这一点。相反,如果您想使用Byte Buddy,应该使用StackManipulation来组装一个方法。堆栈操作仍然包含Java字节码,但这些字节码应该非常简单,易于实现

    Byte Buddy不针对这种情况的原因是,通常情况下,您可以为代码找到比汇编代码片段更好的抽象。为什么节点不能实现实际的实现,而实际的实现是从插入指令的方法调用的?JIT编译器通常会将此代码优化为与手动内联代码相同的结果。此外,还可以保持可调试性,降低代码的复杂性