Table of contents
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
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
A new class is created Script_1685399425946_58
:
Execute the execute0 method, parameter: com.googlecode.aviator.utils.Env
and then execute successfully, get the result
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)
Expression class generation process
in accordance with
String exp1 = "b-c+a";
assertEquals(8, this.instance.exec(exp1, 6, 2, 4));
The asm generation logic after parsing variables:
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 execute0
method
@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();
}
You can debug and write to the class file to view, className file name Script_1685767852489_58
:
b-c+a
generated 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 exceute0
inside a method is b-c+a
the logic of execution