
文章目录
- 引言
- 一、从计算器到规则引擎:解释器架构的核心本质
- 二、架构设计图:从输入到执行的完整链路
- 三、Java实战:手写一个表达式解释器
-
- (一)场景:解析并计算加减乘除表达式
- (二)技术栈:
- (三)核心代码实现
-
- [1. 词法分析器(Lexer)](#1. 词法分析器(Lexer))
- [2. 语法分析器(Parser)](#2. 语法分析器(Parser))
- [3. 解释器入口](#3. 解释器入口)
- 四、适用场景与典型案例
- 五、优缺点分析:何时该用,何时慎选?
- 六、总结:给系统装上"可编程大脑"
引言
在低代码平台盛行的今天,你是否好奇过"可视化配置规则→系统自动执行"的底层实现?当我们在Excel中用公式计算数据,或在游戏中通过脚本自定义角色行为时,背后都藏着一个低调却强大的架构风格------解释器架构。作为一个亲历过多个规则引擎落地的老湿机,今天就来拆解这种赋予系统"动态灵魂"的架构设计,帮你从原理到落地全面掌握。
一、从计算器到规则引擎:解释器架构的核心本质
(一)什么是解释器架构?
简单来说,它是一种"让系统理解并执行自定义语言"的架构模式,核心在于:
- 定义领域语言 :无论是数学表达式(如
1+2*3
)、规则表达式(如age>18 && score>80
),还是配置脚本(如SQL、正则表达式),都可以视为一种"语言"。 - 解析与执行:通过"解析器"将语言转换为内部可识别的结构(如抽象语法树),再通过"解释器"逐行解释执行。
典型场景:
- 计算器APP解析用户输入的表达式并计算结果
- 规则引擎解析业务规则(如"新用户首单满100减30")并驱动流程
- 游戏引擎解析Lua脚本实现角色技能动态配置

(二)核心组件:构建"语言理解系统"的三驾马车
- 解析器(Parser)
- 职责:将输入的文本/指令转换为结构化的内部表示(如抽象语法树AST)
- 细分:
- 词法分析器(Lexer) :拆分字符流为合法token(如将
"1+2"
拆分为NUMBER(1)
、PLUS(+)
、NUMBER(2)
) - 语法分析器(Syntax Parser) :根据语法规则校验token序列,构建语法树(如
1+2*3
生成包含优先级的树结构)
- 词法分析器(Lexer) :拆分字符流为合法token(如将

- 解释器(Interpreter)
- 职责:遍历语法树,根据上下文执行具体操作
- 关键:维护运行时状态(如变量值、函数作用域),支持动态绑定(如脚本中定义的变量可在运行时修改)
- 上下文(Context)
- 职责:存储解释执行所需的外部信息
- 示例:计算器中的当前计算结果、规则引擎中的用户属性(年龄、消费记录)

二、架构设计图:从输入到执行的完整链路
用户输入
表达式/脚本/规则 词法分析器 语法分析器 抽象语法树
AST 解释器 上下文
变量/函数/状态 执行结果
- 输入处理层:接收用户输入的文本,支持多种格式(纯文本、JSON配置、DSL脚本)
- 解析层 :
- 词法分析:将字符流转换为token序列(如
"a=1+2"
拆分为IDENTIFIER(a)
、EQUALS(=)
、NUMBER(1)
、PLUS(+)
、NUMBER(2)
) - 语法分析:根据文法规则(如BNF范式)校验token合法性,生成AST(如赋值语句生成包含左值和右值的树节点)
- 词法分析:将字符流转换为token序列(如
- 执行层 :
- 解释器遍历AST节点,调用上下文获取变量值,执行具体操作(如加法节点调用数学运算模块)
- 上下文支持动态更新(如脚本中修改变量值后,后续执行使用新值)

三、Java实战:手写一个表达式解释器
(一)场景:解析并计算加减乘除表达式
(如3+5*2-4
)
(二)技术栈:
- 词法分析:使用正则表达式匹配数字和操作符
- 语法分析:递归下降解析器(适合简单文法)
- 解释执行:基于AST节点遍历

(三)核心代码实现
1. 词法分析器(Lexer)
java
import java.util.regex.Matcher;
import java.util.regex.Pattern;
enum TokenType { NUMBER, PLUS, MINUS, MULTIPLY, DIVIDE, EOF }
class Token {
TokenType type;
Object value;
Token(TokenType type, Object value) {
this.type = type;
this.value = value;
}
}
class Lexer {
private final String input;
private int position = 0;
Lexer(String input) {
this.input = input.trim();
}
Token nextToken() {
if (position >= input.length()) {
return new Token(TokenType.EOF, null);
}
char currentChar = input.charAt(position);
if (Character.isDigit(currentChar)) {
// 匹配多位数
StringBuilder number = new StringBuilder();
while (position < input.length() && Character.isDigit(input.charAt(position))) {
number.append(input.charAt(position++));
}
return new Token(TokenType.NUMBER, Integer.parseInt(number.toString()));
}
switch (currentChar) {
case '+': position++; return new Token(TokenType.PLUS, '+');
case '-': position++; return new Token(TokenType.MINUS, '-');
case '*': position++; return new Token(TokenType.MULTIPLY, '*');
case '/': position++; return new Token(TokenType.DIVIDE, '/');
// 跳过空格
case ' ': position++; return nextToken();
default: throw new IllegalArgumentException("非法字符: " + currentChar);
}
}
}
2. 语法分析器(Parser)
java
class Parser {
private Token currentToken;
private final Lexer lexer;
Parser(String input) {
lexer = new Lexer(input);
currentToken = lexer.nextToken();
}
// 解析表达式(处理加减)
int expr() {
int result = term();
while (currentToken.type == TokenType.PLUS || currentToken.type == TokenType.MINUS) {
Token opToken = currentToken;
advance();
int right = term();
result = opToken.type == TokenType.PLUS ? result + right : result - right;
}
return result;
}
// 解析项(处理乘除)
int term() {
int result = factor();
while (currentToken.type == TokenType.MULTIPLY || currentToken.type == TokenType.DIVIDE) {
Token opToken = currentToken;
advance();
int right = factor();
result = opToken.type == TokenType.MULTIPLY ? result * right : result / right;
}
return result;
}
// 解析因子(数字)
int factor() {
if (currentToken.type == TokenType.NUMBER) {
int value = (Integer) currentToken.value;
advance();
return value;
} else {
throw new IllegalArgumentException("预期数字,得到: " + currentToken.type);
}
}
private void advance() {
currentToken = lexer.nextToken();
}
}
3. 解释器入口
java
public class InterpreterDemo {
public static void main(String[] args) {
String expression = "3+5*2-4";
Parser parser = new Parser(expression);
int result = parser.expr();
System.out.println("表达式: " + expression);
System.out.println("结果: " + result); // 输出:9(3+10-4=9)
}
}

四、适用场景与典型案例
(一)这些场景请优先选择解释器架构
- 领域特定语言(DSL)
- 案例:金融风控系统中定义规则DSL(如
"loanAmount>50000 && creditScore<600 → 人工审核"
),通过解释器动态加载规则。 - 优势:业务人员可通过可视化界面配置规则,无需开发介入。
- 案例:金融风控系统中定义规则DSL(如
- 脚本化扩展
- 案例:游戏引擎允许玩家通过Lua脚本自定义角色技能(如
"当血量<30%时,自动释放治疗术"
)。 - 优势:无需重启游戏即可更新逻辑,提升用户自定义能力。
- 案例:游戏引擎允许玩家通过Lua脚本自定义角色技能(如
- 配置文件解析
- 案例:微服务网关通过解释器解析YAML配置(如路由规则、限流策略),实现动态热加载。
- 优势:避免硬编码,支持运行时配置变更。

(二)经典案例:规则引擎的核心实现
某电商促销系统使用解释器架构解析促销规则:
- 输入规则 :
"newUser && orderAmount>100 → discount=orderAmount*0.1"
- 解析过程 :
- 词法分析:拆分为
IDENTIFIER(newUser)
、LOGICAL_AND(&&)
、IDENTIFIER(orderAmount)
、GREATER_THAN(>)
、NUMBER(100)
等token - 语法分析:构建包含条件节点和操作节点的AST
- 词法分析:拆分为
- 解释执行 :
- 从上下文中获取用户是否为新用户(
newUser=true
)、订单金额(orderAmount=150
) - 计算折扣:
150*0.1=15
- 从上下文中获取用户是否为新用户(

五、优缺点分析:何时该用,何时慎选?
(一)核心优势
优势 | 具体表现 |
---|---|
动态性 | 支持运行时加载新规则/脚本,无需重新编译部署(如实时更新风控策略) |
灵活性 | 自定义领域语言,适配复杂业务逻辑(如电商促销规则的多样化组合) |
易调试 | 可逐行追踪语法树执行过程,方便定位规则配置错误 |
跨平台 | 通过统一解释器实现多语言/多环境兼容(如Python解释器可在Windows/Linux运行) |

(二)潜在挑战
- 性能瓶颈
- 解释执行效率低于编译执行(如Python解释器速度慢于C编译后的二进制文件),不适合高频计算场景。
- 优化方案:对热点代码使用JIT编译(如Java的HotSpot虚拟机),或提前将规则编译为字节码。
- 复杂性递增
- 复杂文法(如包含递归、优先级处理)会导致解析器实现难度指数级上升(如编写SQL解释器需处理子查询、事务等)。
- 解决方案:使用成熟的解析器生成工具(如ANTLR、JavaCC),自动生成词法和语法分析代码。

- 安全风险
- 执行外部输入的脚本可能引入注入攻击(如用户输入恶意表达式破坏系统)。
- 防护措施:限制脚本权限(如禁止文件操作)、使用沙箱环境隔离执行。

六、总结:给系统装上"可编程大脑"
解释器架构的本质,是赋予系统"理解人类语言"的能力:从简单的计算器到复杂的规则引擎,它让技术系统不再是固定代码的集合,而是可以通过"语言"动态定义行为的智能体。其核心价值在于:
- 业务赋能:让非技术人员通过自定义语言(如可视化规则配置)驱动系统行为
- 技术解耦:将业务逻辑从硬编码中解放出来,通过解释器实现"数据(规则)与代码分离"
当然,它并非银弹:简单场景可手写解析器(如本例中的表达式计算),复杂场景需借助专业工具(如用ANTLR构建SQL解释器)。下次当你遇到"需要动态配置规则"或"支持用户自定义逻辑"的需求时,不妨考虑引入解释器架构------让系统像人类一样,通过"阅读文字"理解并执行你的指令。
你是否在项目中遇到过需要"动态解析"的场景?欢迎在评论区分享你的经验,我们一起探讨最佳实践~
图片来源网络