Liquor (动态编译后)就是 Java 原生运行,比一般的 JVM 脚本(或表达式)性能高 “20 倍” 左右
Liquor 是一个开源的轻量级 Java 动态编译器
Liquor Java 动态编译器。支持完整的 Java 语法及各版本特性编译特点:
- 可以指定父类加载器(默认,为当前线程内容类加载器)
- 可以单个类编译
- 可以多个类同时编译
- 可以增量编译
- 非线程安全。多线程时,要注意锁控制。
- 编译的性能,可以按“次”计算。尽量多类编译一次。
- 可以与主项目一起调试
Liquor的核心价值在于业务敏捷性,即用户在界面上的配置(流程、规则、公式、代码)必须实时生效,而传统的 Java 开发模式是强静态编译的,新代码需要重新编译、打包和重启。
Liquor充当了运行时 JIT 编译器和规则引擎。它弥合了 “配置” 与 “高性能 Java 运行时” 之间的鸿沟。它解决了 Java 生态中热更新、高性能、强类型三者难以兼得的根本矛盾。
1. 动态编译即服务 (Dynamic Compilation-as-a-Service)
Liquor 的 DynamicCompiler 模块将编译能力从开发工具链中解放出来,变为一个运行时的服务能力。
- 实现机制: 低代码平台的前端配置(如拖拽流程、设置数据源映射)经过后端解析器,被转化为标准的 Java 代码片段。Liquor 在运行时调用 Java Compiler API 将这些代码编译成字节码。
- 技术价值: 平台无需依赖外部的编译环境,也不需要进行耗时的重启。新业务逻辑直接在内存中生成、加载并执行,实现了业务逻辑的零停机热更新。
2. 代码示例:流程节点的动态实现
// 低代码平台解析用户配置,生成一个 Java 类或方法 String customLogicCode = """ import com.platform.utils.DataConverter; import java.util.Map; // 增加 Map 导入,使代码更完整 public class CustomProcessor { public Map<String, Object> process(Map<String, Object> inputData) { if ((double)inputData.get("amount") > 1000) { inputData.put("status", "HighValue"); } // 调用平台内置工具类 inputData.put("convertedKey", DataConverter.hashKey(inputData.get("originalKey"))); return inputData; } } """; // Liquor 运行时编译和加载 DynamicCompiler compiler = new DynamicCompiler(); compiler.addSource("CustomProcessor", customLogicCode).build(); // 实例化并执行 (模拟流程引擎调用) Class<?> clazz = compiler.getClassLoader().loadClass("CustomProcessor"); Object processor = clazz.getDeclaredConstructor().newInstance(); // ... 调用 processor.process(data) ...2. 代码示例:高性能表达式求值 (Exprs 应用)
在数据校验和计算字段场景中,保证计算速度至关重要:
// 1. 定义一个用于计算的表达式 CodeSpec calculationSpec = new CodeSpec("basePrice * (1 + taxRate) + shippingFee") .parameters( new ParamSpec("basePrice", Double.class), new ParamSpec("taxRate", Double.class), new ParamSpec("shippingFee", Double.class) ); // 2. 首次执行(触发编译并缓存) Map<String, Object> data1 = Map.of("basePrice", 100.0, "taxRate", 0.1, "shippingFee", 5.0); Double total1 = Exprs.eval(calculationSpec, data1); // 首次编译 // 3. 第二次执行(直接命中缓存,极快) Map<String, Object> data2 = Map.of("basePrice", 200.0, "taxRate", 0.1, "shippingFee", 5.0); Double total2 = Exprs.eval(calculationSpec, data2); // 直接执行字节码三、构建动态扩展点的基石:动态 Bean 与 IoC 集成
1. 动态类加载与 IoC 容器集成 (Dynamic Class Loading & IoC)
Liquor 编译生成的类由其内部的 DynamicClassLoader 负责加载。
- 实现机制:
平台利用 Liquor 的集成特性(如对 Spring 或 Solon 的支持),将动态编译的类(例如一个带有 @Component 注解的 Service 类)注册到 IoC (Inversion of Control) 容器中。
- 技术价值:
动态依赖注入: 动态生成的代码可以直接 @Autowired 或 @Inject 平台中已有的服务 Bean。
动态 API 暴露: 动态编译的 Controller 类可以即时映射到 URL 路径,实现 动态 API 接口的发布,为低代码平台提供了灵活的微服务扩展能力。
编译后,从 ClassLoader 获取类。
public class DemoApp { public static void main(String[] args) throws Exception{ //可以复用(可以,不断的增量编译) DynamicCompiler compiler = new DynamicCompiler(); String className = "HelloWorld"; String classCode = "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); } }3、多类编译示例
可以把需要编译的代码收集后,多类编译一次。这样,时间更少。
public class DemoApp { @Test public void test() throws Exception{ DynamicCompiler compiler = new DynamicCompiler(); compiler.addSource("com.demo.UserDo", "package com.demo;\n" + "import java.util.HashMap;\n\n"+ "public class UserDo{\n" + " private String name;\n" + "\n" + " public String getName() {\n" + " return name;\n" + " }\n" + " \n" + " public UserDo(String name) {\n" + " this.name = name;\n" + " }\n" + "}"); compiler.addSource("com.demo.IUserService", "package com.demo;\n" + "public interface IUserService {\n" + " UserDo getUser(String name);\n" + "}"); compiler.addSource("com.demo.UserService", "package com.demo;\n" + "public class UserService implements IUserService {\n" + " @Override\n" + " public UserDo getUser(String name) {\n" + " return new UserDo(name);\n" + " }\n" + "}"); compiler.build(); Class<?> clz = compiler.getClassLoader().loadClass("com.demo.UserService"); Object obj = clz.newInstance(); System.out.println(obj); System.out.println(obj.getClass()); Object objUser = clz.getMethods()[0].invoke(obj, "noear"); System.out.println(objUser); System.out.println(objUser.getClass()); } }4、类加载器的切换
类加载器 ClassLoader 内部是基于 hash 管理类的,所以相同的类名只能有一个。。。如果我们需要对相同的类名进行编译。可以采用两种方式:
- 重新实例化动态编译器(DynamicCompiler)
- 通过切换类加载器(也可以新建类加载器)。应用时,可建立识别体系,识别是重要换新的类加载器?
ClassLoader cl_old = compiler.getClassLoader(); ClassLoader cl_new = compiler.newClassLoader(); compiler.setClassLoader(cl_new); compiler.addSource("..."); compiler.build(); //...用完,可以换回来 //只是示例 compiler.setClassLoader(cl_old);参考:
源码地址:https://gitee.com/noear/liquor
https://solon.noear.org/article/liquor
具体可参考示例模块:demo_dynamic_compiling_and_debugging_solon
Bilibili 视频演示:《Liquor Java 动态编译神器 - 随心所欲的动态编译与调试》