Domain 是 Fluxon 的”控制流扩展点”。
它提供了比 :: 上下文调用更强大的机制,允许执行器完全控制代码块的执行时机、方式和环境。
Domain 的核心特性是闭包语义:执行器接收的是未求值的 AST,可以决定是否执行、何时执行、执行多少次。
适用场景
- 异步执行:
async { fetch("http://api.example.com") }
- 事务管理:
transaction { updateDatabase(); commit() }
- 重试逻辑:
retry { unstableOperation() }
- 结构化并发:
scope { async { task1() }; async { task2() } }
- 条件执行:
lazy { expensiveComputation() }
与其他机制的区别
| 特性 | :: 上下文调用 | Domain |
|---|
| 语法 | value :: block | domain block |
| 块执行 | 立即执行,无法延迟 | 执行器控制执行时机 |
| 闭包语义 | 不支持(块立即求值) | 支持(接收未求值的 AST) |
| 编译支持 | ✅ | ❌(仅解释执行) |
| 适用场景 | 简单上下文传递 | 异步、事务、重试、结构化并发 |
Domain 的字节码生成会抛 UnsupportedOperationException,因此含 domain 的脚本不要走
编译执行引擎。
组成与执行流程
| 组件 | 作用 |
|---|
DomainRegistry | 注册表:domainName -> DomainExecutor |
DomainExecutor | 执行器:接收 Environment 和 Supplier<Object>(域体闭包) |
DomainSyntaxMacro | 内置语法宏:命中已注册标识符 |
DomainExpression | AST 节点:在解析时捕获 executor 和域体 AST |
执行大致流程:
- Parser 读到
IDENTIFIER,DomainSyntaxMacro 发现已注册同名 domain。
- 先消费 domain 名称 token,再解析后续的块或表达式作为域体。
- 域体 AST 与
DomainExecutor 一起被封装进 DomainExpression。
- Interpreter 执行到该节点时,将域体包装为
Supplier<Object> 并调用 executor。
注册与隔离
使用全局主注册表(最常见)
在应用启动时注册即可(建议注册阶段单线程):
DomainRegistry.primary().register("async", (env, body) -> {
return CompletableFuture.supplyAsync(body::get);
});
为沙箱/多租户使用独立注册表
如果你需要”每份脚本一套 domain 集合”,可以创建独立实例并放到 CompilationContext:
DomainRegistry registry = new DomainRegistry();
registry.register("async", (env, body) -> CompletableFuture.supplyAsync(body::get));
CompilationContext ctx = new CompilationContext(source);
ctx.setDomainRegistry(registry);
Environment env = FluxonRuntime.getInstance().newEnvironment();
List<ParseResult> ast = Fluxon.parse(env, ctx);
Object result = Fluxon.eval(ast, env);
如果你也自定义了 SyntaxMacroRegistry,记得把 DomainSyntaxMacro 放进去;
否则即使 registry 有 domain,解析器也不会命中。
最小示例:实现 retry
这个 domain 失败时最多重试 3 次:
import org.tabooproject.fluxon.Fluxon;
import org.tabooproject.fluxon.compiler.CompilationContext;
import org.tabooproject.fluxon.parser.DomainRegistry;
import org.tabooproject.fluxon.parser.ParseResult;
import org.tabooproject.fluxon.runtime.Environment;
import org.tabooproject.fluxon.runtime.FluxonRuntime;
import java.util.List;
public final class DomainDemo {
public static void main(String[] args) {
DomainRegistry registry = new DomainRegistry();
registry.register("retry", (env, body) -> {
int maxAttempts = 3;
Exception lastException = null;
for (int i = 0; i < maxAttempts; i++) {
try {
return body.get(); // 执行域体
} catch (Exception e) {
lastException = e;
System.out.println("Attempt " + (i + 1) + " failed: " + e.getMessage());
}
}
throw new RuntimeException("All " + maxAttempts + " attempts failed", lastException);
});
String source = "retry { unstableOperation() }";
CompilationContext ctx = new CompilationContext(source);
ctx.setDomainRegistry(registry);
Environment env = FluxonRuntime.getInstance().newEnvironment();
// 注册一个会失败 2 次的函数
env.defineRootFunction("unstableOperation", new NativeFunction<>(null, funcCtx -> {
// 实际实现...
return "success";
}));
List<ParseResult> ast = Fluxon.parse(env, ctx);
System.out.println(Fluxon.eval(ast, env));
}
}
高级用法:通过 target 实现结构化并发
Domain 可以通过 Environment#setTarget(Object) 设置上下文对象,使内部代码能够访问域的上下文。
这是实现结构化并发、事务等协作模式的基础。
示例:Scope + Async
// 注册 scope 域:创建任务作用域
registry.register("scope", (env, body) -> {
TaskScope scope = new TaskScope();
Object previousTarget = env.getTarget();
try {
env.setTarget(scope); // 设置 target,使内部代码能访问 scope
Object result = body.get();
scope.join(); // 等待所有子任务完成
return result;
} finally {
env.setTarget(previousTarget); // 恢复之前的 target
}
});
// 注册 async 域:检查是否在 scope 内
registry.register("async", (env, body) -> {
Object target = env.getTarget();
// 如果在 scope 内,将任务注册到 scope
if (target instanceof TaskScope) {
TaskScope scope = (TaskScope) target;
return scope.submit(() -> body.get());
}
// 否则独立执行
return CompletableFuture.supplyAsync(body::get);
});
在脚本中使用:
scope {
task1 = async { longOperation1() } // 注册到 scope
task2 = async { longOperation2() } // 注册到 scope
// scope.join() 自动等待所有任务完成
}
Target 类型检查约定
推荐使用 instanceof 进行类型检查:
Object target = env.getTarget();
if (target instanceof TaskScope) {
TaskScope scope = (TaskScope) target;
// 使用 scope
} else if (target instanceof TransactionContext) {
TransactionContext tx = (TransactionContext) target;
// 使用事务上下文
}
常见模式
1. 异步执行
registry.register("async", (env, body) -> {
return CompletableFuture.supplyAsync(body::get);
});
脚本:
future = async { fetch("http://api.example.com") }
2. 结果转换
registry.register("double", (env, body) -> {
Object result = body.get();
if (result instanceof Number) {
return ((Number) result).intValue() * 2;
}
return result;
});
脚本:
value = double { 21 } // 返回 42
3. 条件执行(惰性求值)
registry.register("lazy", (env, body) -> {
// 不执行 body,返回一个惰性求值的包装器
return new LazyValue(body);
});
脚本:
lazyValue = lazy { expensiveComputation() }
// 只有在需要时才执行
4. 环境修改
registry.register("withContext", (env, body) -> {
env.assign("contextValue", "special-context", -1);
return body.get();
});
脚本:
withContext {
print(&contextValue) // 输出: special-context
}
注意事项
- 命名规则:domain 名称是标识符(
IDENTIFIER),允许包含 -(例如 retry-3-times)。
- 命名冲突:domain 的匹配发生在”标识符兜底”之前,
如果你注册了与函数/变量同名的 domain,会改变解析结果。
- 闭包捕获:域体是闭包,可以访问外部作用域的变量。
- 异常处理:
- 执行器内抛出的异常会传播到调用方。
- 建议抛出
FluxonRuntimeError 子类以获得 SourceTrace。
- 线程安全:
DomainExecutor 可能在多线程环境下被并发调用,应保证无状态或正确处理并发访问。
Environment 不是线程安全的,异步执行时需要创建新的环境或使用同步机制。
相关链接