Aria 可以轻松嵌入到任何 JVM 应用中。本章介绍如何在 Java 项目中集成 Aria 引擎。
Aria 发布在 ArcartX Maven 仓库,需要先添加仓库地址。
Gradle (Kotlin DSL):
repositories {
maven {
name = "arcartx-repo"
url = uri ( "https://repo.arcartx.com/repository/maven-public/" )
}
}
Gradle (Groovy DSL):
repositories {
maven {
name = 'arcartx-repo'
url = 'https://repo.arcartx.com/repository/maven-public/'
}
}
Maven:
< repositories >
< repository >
< id >arcartx-repo</ id >
< url >https://repo.arcartx.com/repository/maven-public/</ url >
</ repository >
</ repositories >
dependencies {
implementation ( "priv.seventeen.artist.aria:aria:1.0.0" )
// 可选:数据库模块
implementation ( "priv.seventeen.artist.aria:aria-db:1.0.0" )
}
dependencies {
implementation 'priv.seventeen.artist.aria:aria:1.0.0'
// 可选:数据库模块
implementation 'priv.seventeen.artist.aria:aria-db:1.0.0'
}
< dependency >
< groupId >priv.seventeen.artist.aria</ groupId >
< artifactId >aria</ artifactId >
< version >1.0.0</ version >
</ dependency >
<!-- 可选:数据库模块 -->
< dependency >
< groupId >priv.seventeen.artist.aria</ groupId >
< artifactId >aria-db</ artifactId >
< version >1.0.0</ version >
</ dependency >
要求 Java 17+。Aria 依赖 ASM 9.6 进行字节码生成(JIT 编译)。
最简单的用法 — 一行代码执行脚本:
import priv.seventeen.artist.aria.Aria;
import priv.seventeen.artist.aria.context.Context;
import priv.seventeen.artist.aria.value.IValue;
public class QuickStart {
public static void main ( String [] args ) throws Exception {
Context ctx = Aria. createContext ();
IValue< ? > result = Aria. eval ( "return 1 + 2" , ctx);
System.out. println (result. numberValue ()); // 3.0
}
}
Aria 类在首次使用时会自动初始化默认引擎(注册所有内置函数和服务)。
运行时错误以 AriaException 抛出,消息包含编译单元名、行号与出错源码行
(单元: [名字] 运行时错误, 位于第 X 行…),详见异常处理 。
public static IValue <?> eval (String code, Context context) throws AriaException
编译并立即执行代码,返回结果值。内部流程:解析 → 编译 IR → 优化 → 解释执行。
Context ctx = Aria. createContext ();
// 执行表达式
IValue< ? > result = Aria. eval ( "return math.sqrt(16)" , ctx);
System.out. println (result. numberValue ()); // 4.0
// 执行多行代码
IValue< ? > result2 = Aria. eval ( """
list = [1, 2, 3, 4, 5]
sum = list.reduce(-> { return args[0] + args[1] }, 0)
return sum
""" , ctx);
System.out. println (result2. numberValue ()); // 15.0
public static IValue <?> eval (String code, Context context, SandboxConfig sandbox) throws AriaException
在沙箱限制下执行代码:
import priv.seventeen.artist.aria.runtime.SandboxConfig;
SandboxConfig sandbox = SandboxConfig. builder ()
. maxExecutionTime ( 5000 )
. maxCallDepth ( 100 )
. allowFileSystem ( false )
. allowNetwork ( false )
. build ();
IValue< ? > result = Aria. eval ( "return 1 + 1" , ctx, sandbox);
提供两种编译重载,均可追加 boolean lenient 参数启用宽容编译(见下文)。
public static AriaCompilationUnit compile (String name, Context context, String code) throws CompileException
返回 AriaCompilationUnit,绑定了 Context,可直接执行:
Context ctx = Aria. createContext ();
AriaCompilationUnit unit = Aria. compile ( "myScript" , ctx, """
x = 10
y = 20
return x + y
""" );
IValue< ? > result = unit. execute ();
System.out. println (result. numberValue ()); // 30.0
// 可以多次执行(共享同一个 Context)
IValue< ? > result2 = unit. execute ();
AriaCompilationUnit 提供的方法:
execute() — 执行并返回结果
getName() — 编译单元名称
getProgram() — 获取 IR 程序
getContext() — 获取绑定的 Context
getTracker() — 获取源码追踪器
getWarnings() — lenient 编译产生的警告列表(严格模式恒为空)
public static AriaCompiledRoutine compile (String name, String code) throws CompileException
返回 AriaCompiledRoutine,执行时需要外部传入 Context:
// 预编译(可复用)
AriaCompiledRoutine routine = Aria. compile ( "template" , """
greeting = "Hello, " + args[0] + "!"
return greeting
""" );
// 每次执行使用不同的 Context
Context ctx1 = Aria. createContext ();
ctx1. setArgs ( new IValue< ? >[]{ new StringValue ( "Alice" ) });
IValue< ? > r1 = routine. execute (ctx1); // "Hello, Alice!"
Context ctx2 = Aria. createContext ();
ctx2. setArgs ( new IValue< ? >[]{ new StringValue ( "Bob" ) });
IValue< ? > r2 = routine. execute (ctx2); // "Hello, Bob!"
AriaCompiledRoutine 提供的方法:
execute(Context context) — 使用指定 Context 执行
getName() — 例程名称
getProgram() — 获取 IR 程序
getTracker() — 获取源码追踪器
getWarnings() — lenient 编译产生的警告列表(严格模式恒为空)
public static AriaCompilationUnit compile (String name, Context context, String code, boolean lenient)
public static AriaCompiledRoutine compile (String name, String code, boolean lenient)
默认(lenient = false)严格模式下,任何解析错误立即抛出 CompileException(fail-fast)。
lenient = true 时恢复 Shimmer 式宽容语义:解析出错的语句及其之后的所有内容被丢弃 ,
保留出错前的语句正常编译执行;每个被丢弃的错误记录为一条警告:
Context ctx = Aria. createContext ();
AriaCompilationUnit unit = Aria. compile ( "demo" , ctx,
"var.a = 1 \n var.b = {bad \n var.a = 99 \n return var.a \n " , true );
unit. execute (). numberValue (); // 1.0 —— 第 2 行起全部被截断,var.a = 99 未执行
unit. getWarnings ();
// [ "[lenient] 语句解析失败,该语句及其后内容被丢弃: Expected COLON, got NEWLINE ..." ]
lenient 只处理语法 错误;运行时错误不受影响。
AriaEngine 负责引擎初始化和 Context 工厂。
import priv.seventeen.artist.aria.api.AriaEngine;
import priv.seventeen.artist.aria.context.GlobalStorage;
// 使用默认引擎
AriaEngine engine = Aria. getEngine ();
// 创建自定义引擎(独立的 GlobalStorage)
GlobalStorage customGlobal = new GlobalStorage ();
AriaEngine customEngine = new AriaEngine (customGlobal);
customEngine. initialize (); // 注册所有内置函数
// 通过引擎创建 Context
Context ctx = customEngine. createContext ();
initialize() 方法会注册:
所有内置函数(math、console、type、string、list、map 等)
对象构造器(Range 等)
Java 互操作(Java 命名空间)
动画对象(aria-animations 在 classpath 中时自动通过反射加载)
服务层(fs、net、event、json、serial、scheduler、template、crypto、datetime、regex)
数据库模块(aria-db 在 classpath 中时自动通过反射加载,否则静默跳过)
模块加载函数(__import__)
initialize() 是幂等的,多次调用只会执行一次。
Context 是脚本执行的上下文环境,包含三层存储和作用域栈。
import priv.seventeen.artist.aria.context.Context;
import priv.seventeen.artist.aria.context.GlobalStorage;
import priv.seventeen.artist.aria.context.VariableKey;
import priv.seventeen.artist.aria.value. * ;
// 通过 Aria 创建(使用默认引擎的 GlobalStorage)
Context ctx = Aria. createContext ();
// 通过引擎创建
Context ctx2 = engine. createContext ();
// 设置全局变量
ctx. getGlobalStorage (). getGlobalVariable (VariableKey. of ( "config" ))
. setValue ( new StringValue ( "production" ));
// 设置 self 和 args
ctx. setSelf (someValue);
ctx. setArgs ( new IValue< ? >[]{ new StringValue ( "arg1" ), new NumberValue ( 42 ) });
Context 提供的变量访问层级:
getGlobalVariable(key) — 全局变量(global.xxx)
getClientVariable(key) — 客户端变量(client.xxx)
getServerVariable(key) — 服务端变量(server.xxx)
getLocalVariable(key) — 局部变量(var.xxx)
getLocalValue(key) — val 只读槽(val.xxx,宿主经 forceSetValue 写入)
getScopeVariable(key) — 作用域变量
Context 还支持创建派生上下文:
createAsyncContext() — 异步上下文(共享 globalStorage 和 localStorage,独立 scopeStack)
snapshotForClosure() — 闭包快照
createCallContext(self, args) — 函数调用上下文(全新 ScopeStack,裸名与调用方隔离)
createSharedCallContext(self, args) — 共享调用上下文(复用调用方同一 ScopeStack,仅替换 self/args)
createSharedCallContext :被调脚本
对裸名临时变量 的读写与调用方完全互通(同一 ScopeStack、同一 local/global 存储),
只有 self / args 被替换。适合宿主回调场景(如 UI 的多个 action 脚本共享一批临时变量)。
配套契约:每次执行结束时作用域栈会弹回执行前的深度(裸名不跨 eval 残留)。宿主要维持
跨多次执行 的共享层,需要自行 pushScope() 持有一层常驻作用域:
Context caller = Aria. createContext ();
caller. pushScope (); // 宿主常驻层:执行边界只弹回到该深度,此层长驻
// 预置/读取常驻层里的裸名变量
caller. getScopeStack (). get (VariableKey. of ( "flag" )). setValue ( new NumberValue ( 1 ));
// 派生共享上下文执行脚本:脚本里的裸名 flag 与调用方是同一个绑定
Context shared = caller. createSharedCallContext (selfValue, argsArray);
Aria. eval ( "flag = 9" , shared);
Aria. eval ( "return flag" , caller). numberValue (); // 9.0 —— 写回对调用方可见
val. 是宿主注入的只读槽:脚本对 val 的写入是静默 no-op ,引用上的普通
setValue 同样是 no-op。宿主注入必须 用 forceSetLocalValue(或引用上的
forceSetValue):
// Java 端注入/覆写 val 变量(脚本中经 val.config 读取)
ctx. forceSetLocalValue (VariableKey. of ( "config" ), new StringValue ( "production" ));
// 无效:普通 setValue 对 val 引用是 no-op
ctx. getLocalValue (VariableKey. of ( "config" )). setValue ( new StringValue ( "ignored" ));
AnnotationRegistry registry = Aria. getEngine (). getAnnotationRegistry ();
// 注册处理器:脚本执行时遇到 @route 注解自动回调
registry. onAnnotation ( "route" , (annotation, target) -> {
String path = annotation. getArg ( 0 ). stringValue ();
String method = target. name ();
httpServer. register (path, method);
});
// 执行脚本
Aria. eval ( """
class API {
@route('/api/users')
getUsers = -> { return 'users' }
}
""" , ctx);
// 手动查询
List< AnnotatedTarget > routes = registry. findByAnnotation ( "route" );
List< AnnotatedTarget > services = registry. findClassesByAnnotation ( "service" );
AnnotationRegistry API:
onAnnotation(name, handler) — 注册注解处理器
findByAnnotation(name) — 查找所有带指定注解的目标
findClassesByAnnotation(name) — 只查类
findFunctionsByAnnotation(name) — 只查函数/方法
getAll() — 获取所有注解目标
clear() — 清空注册表
JIT 编译器对变量系统的处理方式:
命名空间 JIT 是否处理 说明 var.xxx 是 fastDoubleVars 路径中复制为 double 局部变量,执行完毕后写回;运行时守卫在 var 值非数字时回退解释路径val.xxx 否 包含 LOAD_VAL 的代码不会被 JIT 编译,始终走解释器 global.xxx 读是 / 写否 LOAD_GLOBAL 可编译(每次经 GlobalStorage 读取);写入走解释器server.xxx 读是 / 写否 同上(读取仍会触发 listener) client.xxx 读是 / 写否 同上
var 变量的并发风险: 当 JIT 编译的代码正在执行时,var 变量被复制到 JVM 局部变量中运算,执行完毕后才写回 Context。如果 Java 端在 JIT 执行期间通过 Context 修改 var 变量,JIT 代码不会看到这个修改。这与 Java 自身的 JIT 行为一致。
函数内联的假设: JIT 会将简单的用户自定义函数(如 var.inc = -> { return args[0] + 1 })内联为纯算术运算。内联基于编译期的函数体 IR,不会在运行时重新检查 var.inc 是否被重新赋值。如果脚本在循环中重新定义了同名函数,JIT 代码仍然执行旧的内联逻辑。这是一个已知的限制——JIT 优化的代码假设函数定义在热循环中不会改变。
常量折叠: 只折叠字面量常量(LOAD_CONST),不涉及任何命名空间变量。val.PI 的读取不会被折叠——forceSetLocalValue 随时覆写 val 都是安全的。
建议:
避免在脚本执行期间从另一个线程修改同一个 Context 的 var 变量
如果需要跨线程通信,使用 global.xxx(GlobalStorage 是线程安全的,且不被 JIT 处理)
不要在热循环中重新定义已被 JIT 内联的函数
SandboxConfig 通过 Builder 模式配置脚本执行的资源限制和能力权限。
import priv.seventeen.artist.aria.runtime.SandboxConfig;
SandboxConfig sandbox = SandboxConfig. builder ()
. maxExecutionTime ( 5000 ) // 最大执行时间(毫秒),0 = 无限制
. maxCallDepth ( 256 ) // 最大调用深度,默认 512
. maxInstructions ( 1000000 ) // 最大指令数,0 = 无限制
. allowFileSystem ( false ) // 禁止文件系统访问(fs 命名空间)
. allowNetwork ( false ) // 禁止网络访问(net 命名空间)
. allowJavaInterop ( false ) // 禁止 Java 互操作(Java 命名空间)
. allowedNamespaces ( "math" , "type" , "console" , "json" ) // 白名单命名空间
. build ();
// 使用沙箱执行
Context ctx = Aria. createContext ();
IValue< ? > result = Aria. eval (code, ctx, sandbox);
配置项 类型 默认值 说明 maxExecutionTimelong0(无限制) 最大执行时间(毫秒) maxCallDepthint512 最大函数调用深度(非沙箱模式下解释器内建上限为 2048) maxInstructionslong0(无限制) 最大执行指令数 allowFileSystembooleantrue 是否允许 fs 命名空间 allowNetworkbooleantrue 是否允许 net/http 命名空间 allowJavaInteropbooleantrue 是否允许 Java 命名空间 allowedNamespacesString...null(全部允许) 命名空间白名单
预定义配置:
SandboxConfig.UNRESTRICTED — 无任何限制(默认)
要安全运行不可信代码,建议组合使用:
allowJavaInterop(false) :从源头(use / Java.type / Java.*)封死 Java 互操作,杜绝经 use('java.lang.Runtime') 等的任意代码执行。
allowedNamespaces(...) 白名单 :只放行明确需要的命名空间(能力开关 allowFileSystem/allowNetwork/allowJavaInterop 即使在无白名单时也会拦截对应命名空间)。
资源限制 :设置 maxInstructions / maxExecutionTime / maxCallDepth,防止 DoS。
沙箱激活时,import 已禁止路径穿越(..)与绝对路径,模块代码也继承沙箱;命名空间检查不会被指令缓存绕过。
注意:JavaInterop.setClassFilter(...) 是全局 类过滤器,不随单个 SandboxConfig 实例变化。若要做细粒度的 Java 类白名单(而非整体 allowJavaInterop(false)),需在进程级设置全局 ClassFilter。
import priv.seventeen.artist.aria.module.ModuleResolver;
ModuleResolver resolver = new ModuleResolver ();
// 默认搜索当前目录 "."
resolver. addSearchPath (Path. of ( "/path/to/modules" ));
resolver. addSearchPath (Path. of ( "/another/path" ));
// 解析模块路径(按 .ariac → .aria → 无扩展名 顺序查找;点号映射子目录)
Path resolved = resolver. resolve ( "myModule" );
// 搜索顺序:./myModule.ariac → ./myModule.aria → ./myModule → /path/to/modules/myModule.ariac → ...
import priv.seventeen.artist.aria.module.ModuleLoader;
import priv.seventeen.artist.aria.module.ModuleResolver;
import priv.seventeen.artist.aria.module.ModuleCache;
// 使用默认配置
ModuleLoader loader = new ModuleLoader ();
// 自定义 resolver 和 cache
ModuleResolver resolver = new ModuleResolver ();
resolver. addSearchPath (Path. of ( "src/scripts" ));
ModuleCache cache = new ModuleCache ();
ModuleLoader customLoader = new ModuleLoader (resolver, cache);
// 加载单个模块
IRProgram program = loader. load ( "utils" );
// 并行加载多个模块
Map< String , IRProgram > modules = loader. loadAll (List. of ( "utils" , "config" , "routes" ));
ModuleLoader 特性:
内存缓存:已加载的模块不会重复编译
增量编译:源码哈希未变时跳过重新编译
并行编译:loadAll 使用线程池并行加载
支持 .aria 预编译文件和源码文件
通过引擎配置模块搜索路径:
AriaEngine engine = Aria. getEngine ();
engine. getModuleLoader (). getResolver (). addSearchPath (Path. of ( "scripts" ));
AriaCompiledRoutine 适用于需要多次执行同一段代码的场景(如模板渲染、规则引擎):
// 编译一次
AriaCompiledRoutine routine = Aria. compile ( "rule" , """
score = args[0]
if (score >= 90) {
return "A"
} elif (score >= 80) {
return "B"
} elif (score >= 70) {
return "C"
} else {
return "D"
}
""" );
// 多次执行,每次传入不同参数
for ( int score : new int []{ 95 , 82 , 71 , 55 }) {
Context ctx = Aria. createContext ();
ctx. setArgs ( new IValue< ? >[]{ new NumberValue (score) });
IValue< ? > grade = routine. execute (ctx);
System.out. println (score + " → " + grade. stringValue ());
}
// 输出:
// 95 → A
// 82 → B
// 71 → C
// 55 → D
import priv.seventeen.artist.aria.Aria;
import priv.seventeen.artist.aria.api.AriaCompiledRoutine;
import priv.seventeen.artist.aria.api.AriaEngine;
import priv.seventeen.artist.aria.callable.CallableManager;
import priv.seventeen.artist.aria.context.Context;
import priv.seventeen.artist.aria.context.VariableKey;
import priv.seventeen.artist.aria.interop.JavaInterop;
import priv.seventeen.artist.aria.runtime.SandboxConfig;
import priv.seventeen.artist.aria.value. * ;
public class EmbeddingExample {
public static void main ( String [] args ) throws Exception {
JavaInterop. setClassFilter (className ->
className. startsWith ( "java.util." ) ||
className. startsWith ( "java.lang.Math" )
);
CallableManager manager = CallableManager.INSTANCE;
manager. registerStaticFunction ( "app" , "getUser" , data -> {
String id = data. get ( 0 ). stringValue ();
// 模拟数据库查询
return new StringValue ( "User-" + id);
});
AriaEngine engine = Aria. getEngine ();
engine. getModuleLoader (). getResolver ()
. addSearchPath (java.nio.file.Path. of ( "scripts" ));
Context ctx = Aria. createContext ();
IValue< ? > result = Aria. eval ( """
user = app.getUser("123")
return "Hello, " + user
""" , ctx);
System.out. println (result. stringValue ()); // "Hello, User-123"
AriaCompiledRoutine routine = Aria. compile ( "sandbox-test" , """
sq = math.pow(args[0], 2) + math.pow(args[1], 2)
return math.sqrt(sq)
""" );
SandboxConfig sandbox = SandboxConfig. builder ()
. maxExecutionTime ( 1000 )
. maxCallDepth ( 50 )
. allowFileSystem ( false )
. allowNetwork ( false )
. allowJavaInterop ( false )
. allowedNamespaces ( "math" , "type" )
. build ();
Context sandboxCtx = Aria. createContext ();
sandboxCtx. setArgs ( new IValue< ? >[]{ new NumberValue ( 3 ), new NumberValue ( 4 ) });
IValue< ? > distance = Aria. eval (
"return math.sqrt(math.pow(args[0], 2) + math.pow(args[1], 2))" ,
sandboxCtx, sandbox
);
System.out. println (distance. numberValue ()); // 5.0
Context ctx1 = Aria. createContext ();
Aria. eval ( "global.counter = 0" , ctx1);
Context ctx2 = Aria. createContext ();
Aria. eval ( "global.counter = global.counter + 1" , ctx2);
Context ctx3 = Aria. createContext ();
IValue< ? > counter = Aria. eval ( "return global.counter" , ctx3);
System.out. println (counter. numberValue ()); // 1.0
}
}