Rule engine architecture - based on aviator

aviator usage scenarios

github address: aviator

scenes to be used:

  • Rule judgment and rule engine
  • formula calculation
  • Dynamic Script Control
  • Aggregate data ELT etc...

ASM bytecode manipulation framework

asm implementation: directly modify or generate .class

insert image description here

insert image description here

sample code

package com.googlecode.aviator;

import com.googlecode.aviator.asm.ClassWriter;
import com.googlecode.aviator.asm.MethodVisitor;
import com.googlecode.aviator.asm.Opcodes;

/**
 * @author dingqi on 2023/5/29
 * @since 1.0.0
 */
public class TestAsm extends ClassLoader{
    
    
    /**
     * 生成一个Class类
     * public class User {
     *
     *     public static void main(String[] args) {
     *         System.out.println("Hello World");
     *     }
     *
     *     public int add(int a, int b){
     *         return a + b;
     *     }
     * }
     */
    public static byte[] generateClazz() {
    
    
        ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS | ClassWriter.COMPUTE_FRAMES);

        cw.visit(
                Opcodes.V1_7,
                Opcodes.ACC_PUBLIC,
                "com/googlecode/aviator/User",
                null,
                "java/lang/Object",
                null
        );

        MethodVisitor mv = cw.visitMethod(
                Opcodes.ACC_PUBLIC,
                "<init>",
                "()V",
                null,
                null
        );
        // 开始访问方法code
        mv.visitCode();
        // 局部变量进栈
        mv.visitVarInsn(Opcodes.ALOAD, 0);
        // 执行特殊实例方法(构造方法)
        mv.visitMethodInsn(Opcodes.INVOKESPECIAL, "java/lang/Object", "<init>", "()V");
        // 方法返回
        mv.visitInsn(Opcodes.RETURN);
        // 最大栈大小值、最大方法本地参数值
        mv.visitMaxs(1, 1);
        // 方法结束
        mv.visitEnd();

        mv = cw.visitMethod(
                Opcodes.ACC_PUBLIC + Opcodes.ACC_STATIC,
                "main",
                "(Ljava/lang/String;)V",
                null,
                null
        );
        // 开始访问方法code
        mv.visitCode();
        // 访问static类字段out,参数类型Ljava/io/PrintStream;
        mv.visitFieldInsn(
                Opcodes.GETSTATIC,
                "java/lang/System",
                "out",
                "Ljava/io/PrintStream;"
        );
        // 常量加载进栈
        mv.visitLdcInsn("Hello World");
        // 调用对象的实例方法println,方法参数String数组(Ljava/lang/String;)V
        mv.visitMethodInsn(
                Opcodes.INVOKEVIRTUAL,
                "java/io/PrintStream",
                "println",
                "(Ljava/lang/String;)V"
        );
        // 方法返回
        mv.visitInsn(Opcodes.RETURN);
        // 最大栈大小值、最大方法内本地参数值
        mv.visitMaxs(2, 1);
        // 方法结束
        mv.visitEnd();

        // 再添加方法
        mv = cw.visitMethod(
                Opcodes.ACC_PUBLIC,
                "add",
                "(II)I",
                null,
                null
        );
        // 入参
        mv.visitVarInsn(Opcodes.ILOAD,1);
        mv.visitVarInsn(Opcodes.ILOAD,2);
        mv.visitInsn(Opcodes.IADD); //2个int类型相加
        //返回int 类型
        mv.visitInsn(Opcodes.IRETURN);
        // 设置操作数栈的深度和局部变量的大小:2个数计算,加上this 总共3个变量
        mv.visitMaxs(2, 3);
        mv.visitEnd();

        cw.visitEnd();
        return cw.toByteArray();
    }

    public static void main(String[] args) throws Exception {
    
    
        TestAsm testAsm = new TestAsm();
        byte[] code = TestAsm.generateClazz();
        String className = "com.googlecode.aviator.User";
        Class<?> clazz = testAsm.defineClass(className, code, 0, code.length);
        clazz.getMethods()[0].invoke(null, new Object[]{
    
    null});

        Object o = clazz.newInstance();
        Integer ans = (Integer)clazz.getMethods()[1].invoke(o, 1, 2);
        System.out.println("add ans:" + ans);
        ans = (Integer)clazz.getMethods()[1].invoke(o, 1, 3);
        System.out.println("add ans:" + ans);
    }

}
/** 输出
 Hello World
 add ans:3
 add ans:4
 */

aviator expression example

public class AviatorEvaluatorInstanceUnitTest {
    
    

  protected AviatorEvaluatorInstance instance;

  @Before
  public void setup() {
    
    
    this.instance = AviatorEvaluator.newInstance();
  }

  @Test
  public void testExec() {
    
    
    String exp1 = "b-c+a";
    assertEquals(8, this.instance.exec(exp1, 6, 2, 4));
  }

}

debug

insert image description here
A new class is created Script_1685399425946_58:
insert image description here

Execute the execute0 method, parameter: com.googlecode.aviator.utils.Env
insert image description here
and then execute successfully, get the result
insert image description here
debug and you can see that the generated class does have methods:public final java.lang.Object Script_1685400413476_58.execute0(com.googlecode.aviator.utils.Env)
insert image description here

Expression class generation process

in accordance with

String exp1 = "b-c+a";
assertEquals(8, this.instance.exec(exp1, 6, 2, 4));

insert image description here
The asm generation logic after parsing variables:
insert image description here
insert image description here

 private void callASM(final Map<String, VariableMeta/* metadata */> variables,
      final Map<String, Integer/* counter */> methods, final Set<Token<?>> constants) {
    
    
    this.codeGen.initConstants(constants);
    this.codeGen.initVariables(variables);
    this.codeGen.initMethods(methods);
    this.codeGen.setLambdaBootstraps(this.lambdaBootstraps);
    this.codeGen.start();


generation execute0method

 @Override
  public void start() {
    
    
    makeConstructor();
    startVisitMethodCode();
  }
private void startVisitMethodCode() {
    
    
    this.mv = this.classWriter.visitMethod(ACC_PUBLIC + +ACC_FINAL, "execute0",
        "(Lcom/googlecode/aviator/utils/Env;)Ljava/lang/Object;",
        "(Lcom/googlecode/aviator/utils/Env;)Ljava/lang/Object;", null);
    this.mv.visitCode();
  }

insert image description here

You can debug and write to the class file to view, className file name Script_1685767852489_58:
insert image description here

b-c+agenerated class file

public class Script_1685767852489_58 extends ClassExpression {
    
    
    private final AviatorJavaType f0;
    private final AviatorJavaType f1;
    private final AviatorJavaType f2;

    public Script_1685767852489_58(AviatorEvaluatorInstance var1, List var2, SymbolTable var3) {
    
    
        super(var1, var2, var3);
        this.f2 = new AviatorJavaType("a", var3);
        this.f0 = new AviatorJavaType("b", var3);
        this.f1 = new AviatorJavaType("c", var3);
    }

    public final Object execute0(Env var1) {
    
    
        return this.f0.sub(this.f1, var1).add(this.f2, var1).getValue(var1);
    }
}

The parameters are set through the constructor, and then exceute0inside a method is b-c+athe logic of execution

Guess you like

Origin blog.csdn.net/qq_26437925/article/details/130937869