# liquor **Repository Path**: ApeCoder/liquor ## Basic Information - **Project Name**: liquor - **Description**: Java 动态编译器(兼容 jar in jar)、Java 表达式引擎、Java 脚本引擎。动态编译即服务!(支持 JSR223) - **Primary Language**: Java - **License**: Apache-2.0 - **Default Branch**: main - **Homepage**: https://solon.noear.org/article/liquor - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 65 - **Created**: 2025-05-16 - **Last Updated**: 2025-05-16 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README

Liquor

Java 动态编译、表达式、脚本工具(兼容 jar in jar)

Maven Apache 2 jdk-8 jdk-11 jdk-17 jdk-21 jdk-23
gitee star github star


本工具的编译器代码主要源自 arthas。相关的知识点比较冷门,甚是珍贵。为了复用方便,特整理成一个小工具包进行长期维护。后来又增加了评估器,用于运行表达式和脚本。 | 工件 | 大小 | 功能 | 功能描述 | |--------------------|------|---------------------------------|---------------| | liquor | 24KB | DynamicCompiler | 编译类 | | liquor-eval | 23KB | LiquorEvaluator (Exprs,Scripts) | 运行表达式和脚本(支持 JSR223) | 引用依赖: ```xml org.noear liquor-eval 1.5.3 ``` 性能表现(第三方性能测试): * [https://gitee.com/xiandafu/beetl/tree/master/express-benchmark](https://gitee.com/xiandafu/beetl/tree/master/express-benchmark) ## 安全提醒 !!! Liquor 提供的是完整的 Java 能力(什么都有可能会发生)。 建议开发者,在提交给 Liquor 执行之前,做好代码的安全检测(或者过滤)。 ## 编译器演示 可以有包名;可以导入类;可相互依赖;可多次不重复编译;跟平常写 java 类一样。 ```java public class DemoApp { public static void main(String[] args) throws Exception{ //可以复用(不要,不断的新建) DynamicCompiler compiler = new DynamicCompiler(); String className = "HelloWorld"; String classCode = "import java.util.HashMap;\n\n"+ "public class HelloWorld { " + " public static void helloWorld() { " + " System.out.println(\"Hello, world!\"); " + " } " + "}"; //添加源码(可多个)并 构建 compiler.addSource(className, classCode).build(); Class clazz = compiler.getClassLoader().loadClass(className); clazz.getMethod("helloWorld").invoke(null); } } ``` 运行时调试方案说明(可参考示例模块,demo_dynamic_compiling_and_debugging_solon): * 为源代码创建一个相同类名、相同代码的 `.java` 文件(如果直接读取 `.java` 文件的,就省了) * 文件需放到工程里。比如,根目录下建个 `dynamic` 的普通目录放这个调试文件。 * 打开这个文件,设好断点。就可以和主项目一起调试了。 ## 评估器演示 Liquor 评估器工具,是基于 Java 编译实现的。在“缓存覆盖”下,性能接近原始 Java 代码。但是,当有“无限多变化”的表达式时,缓存会失效,且会产生无限多的类,然后 OOM。 以表达式评估器为例: * 使用“变量”替代常量,以减少编译 Exprs.eval("a+b+c", context)。 * 【推荐】效果,就像类与实例的关系 * 使用非缓存模式 Exprs.eval(new CodeSpec("1+2+3").cached(false)) * 【不推荐】 ### 1) 表达式评估器(只能写一行代码) * 必须有结果返回 * 表达式中没有 ";" 时,会自动添加 "return" 和 ";"。否则要自己确保语句完整 * 使用 CodeSpec::imports 导入表达式需要的类 * 不能直接用“编译器演示”里的方案调试,因为表达式生成的类名是动态的。(可以 copy 代码到具体的类里,先调试完成再复制出来) ```java public class DemoApp { public static void main(String[] args) throws Exception { //基础 System.out.println(Exprs.eval("1+1")); //进阶 CodeSpec code1 = new CodeSpec("aa + 22").parameters(new ParamSpec("aa", Integer.class)); System.out.println(Exprs.eval(code1, Maps.of("aa", 1))); //=> 23 System.out.println(Exprs.eval("bb + 22", Maps.of("bb", 3))); //=>25 System.out.println(Exprs.eval(new CodeSpec("Math.min(1,2)").imports(Math.class))); //=>1 } } ``` ### 2) 脚本评估器 * 可以导入类或静态方法;不能有包名 * 使用内部类时不要加修饰("public"、"static");内部类的字段和方法不支持 "static" * 使用 CodeSpec::imports 导入表达式需要的类或静态方法;或者在代码里添加 "import" 语句 * 不能直接用“编译器演示”里的方案调试,因为脚本生成的类名是动态的。(可以 copy 代码到具体的类里,先调试完成再复制出来) ```java public class DemoApp { public static void main(String[] args) throws Exception { //基础 Scripts.eval("System.out.println(\"hello word\");"); //进阶(如果有内部类,不要加 public) CodeSpec code1 = new CodeSpec("import java.util.HashMap;\n\n"+ " class Demo {\n" + " public String hello(String word) {\n" + " return word;\n" + " }\n" + " }\n" + "\n" + " Demo demo = new Demo();\n" + " return demo.hello(name);") //name 为外部参数 .parameters(new ParamSpec("name", String.class)) .returnType(String.class); System.out.println(Scripts.eval(code1, Maps.of("name", "noear"))); //=>noear } } ``` ## JSR223 演示 ```java @Test public void case1() { ScriptEngineManager scriptEngineManager = new ScriptEngineManager(); ScriptEngine scriptEngine = scriptEngineManager.getEngineByName("liquor"); //或 "java" scriptEngine.eval("System.out.println(\"Hello world!\");"); } ```