Java 在运行时使用Byte Buddy动态生成表示二进制表达式树的单个函数(无子函数) 介绍
我想比较一些在运行时生成代码的库。此时,我接触了Javassist和Byte Buddy的表面 作为概念证明,我试图解决一个小问题,这是一个更复杂问题的起点。Java 在运行时使用Byte Buddy动态生成表示二进制表达式树的单个函数(无子函数) 介绍,java,binary-tree,bytecode-manipulation,byte-buddy,Java,Binary Tree,Bytecode Manipulation,Byte Buddy,我想比较一些在运行时生成代码的库。此时,我接触了Javassist和Byte Buddy的表面 作为概念证明,我试图解决一个小问题,这是一个更复杂问题的起点。 基本上,我有一个我想转换成一行代码并加载到java运行时的。为了简单起见,到目前为止,我只添加了节点和常量作为leaf Javassist引用 我已经在Javassist中找到了实现这一点的方法(它至少适用于具有两个leaf的单个节点)。代码如下所示: public class JavassistNodeFactory{ publ
基本上,我有一个我想转换成一行代码并加载到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))+")");
}
}
}
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;
}
}
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();
}
}
}
}
重要的部分是nodeToString()
函数,在该函数中,我从给定的根节点生成一个由返回字符串表示的算术公式。ValueNode
是具有常量值的树的叶子,该值将作为字符串返回。其他节点(只为我的情况添加节点)将递归地为每个子函数调用函数和打印括号,同时在两个孩子的中间打印操作符(由<代码> GETNAME()/代码>函数)(简称:“代码>”(LeftCudi+ RealCHIAD)<< /> >)。 Javassist将在
interceptMethod()
函数中更改calculateValue()
函数的主体,以返回生成公式的结果
字节伙伴尝试
我和Byte Buddy一起玩过,以获得类似的解决方案。但随着我对概念和文档的深入了解,我越来越觉得Buddy的设计目的不是为了解决这个问题。大多数示例和问题似乎都集中在对其他函数的函数委托上(这些函数实际上已经在编译时存在,并且只在运行时连接到)。在我的例子中,这并不方便,因为我无法知道在编译时要转换的实际树。可能可以使用底层ASM库,但我希望避免自己(以及可能的继任者)处理字节码
我有一个基本的实现(显然不工作),但我必须为Byte Buddy库的intercept()
函数提供实现。我的上一个状态如下所示:
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))+")");
}
}
}
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;
}
}
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();
}
}
}
}
公共类ByteBuddyNodeFactory{
@凌驾
公共动态节点GeneratedDynamicNode(索引节点根){
DynamicNode DynamicNode=null;
试一试{
类dynamicType=newbytebuddy()
.subclass(DynamicNode.class)
.name(“DN”+UUID.randomUUID().toString().replace(“-”,”))
//这就是我的问题所在
//我不知道如何生成intercept()函数的实现
//在nodeToImplementation()函数中进行了尝试
.method(ElementMatchers.named(“calculateValue”).intercept(nodeToImplementation(root))
.make()
.load(Object.class.getClassLoader())
.getLoaded();
dynamicNode=(dynamicNode)dynamicType.newInstance();
}捕获(例外e){
System.out.println(“使用bytebuddy编译testclass时出错:+e.getMessage());
e、 printStackTrace();
}
返回动态节点;
}
私有实现。可组合节点实现(INode节点){
if(node.getName().equals(“”){
return(Implementation.Composable)FixedValue.value(((ValueNode)node.getValue());
}否则{
列表使用Byte Buddy的高级API不容易实现您要做的事情。相反,如果您想使用Byte Buddy,您应该使用StackManipulation
s组装一个方法。堆栈操作仍然包含Java字节码,但这些字节码应该非常简单,易于实现
Byte Buddy不针对此场景的原因是,通常情况下,您可以为代码找到比汇编代码片段更好的抽象。为什么节点不能实现实际实现,然后从插入指令的方法调用该实现?JIT编译器通常会优化此代码,以获得与ma相同的结果每年一次的内联代码。此外,您可以保留可调试性并降低代码的复杂性。事实上,节点已经完成了计算。它们是模拟框架的一部分。但是我们发现遍历树并计算值(这是经常做的)速度相当慢。因此,这基本上只是一个性能问题,我们尝试用这种方法解决。实际上,节点的类型甚至比算术类型更多,允许递归和其他混乱的东西。我们希望重要节点的自定义公式将提供显著的加速。