📌 PDF :AI人工智能 --- RAG知识库实战项目
大白话说Java设计模式-45-解释器模式(源码剖析篇):JDK / Spring 中的解释器实现
📌 一句话本质:解释器在源码里不是"自定义语言"的形象化,而是"语法分析 + AST + 求值"三件套的工业级架构。
🏷️ 标签:解释器模式 / Pattern / SpEL / JavaCC 🎯 适合:中高级后端 / 想读懂主流框架源码的工程师
目录
- 一、为什么读解释器源码?一张地图先看清
- [二、JDK 源码剖析:Pattern / Matcher 正则解释器](#二、JDK 源码剖析:Pattern / Matcher 正则解释器)
- [三、Spring 源码剖析:SpEL 表达式解释器](#三、Spring 源码剖析:SpEL 表达式解释器)
- [四、JavaCC 源码剖析:工业级解析器生成器](#四、JavaCC 源码剖析:工业级解析器生成器)
- 五、为什么框架作者这么设计?
- [六、借鉴到我们项目:3 个"抄作业"实践](#六、借鉴到我们项目:3 个"抄作业"实践)
- [七、本篇小结 + 下一模式预告](#七、本篇小结 + 下一模式预告)
一、为什么读解释器源码?一张地图先看清
解释器模式在 JDK 和 Spring 里出现得很经典 ------Pattern / SpEL / JavaCC 都是解释器思想。
源码地图:
| 类别 | 框架 / 中间件 | 解释器 | 用途 |
|---|---|---|---|
| JDK | java.util.regex |
Pattern / Matcher |
正则表达式 |
| Spring | spring-expression |
SpelExpressionParser |
SpEL 表达式 |
| JavaCC | org.javacc |
解析器生成器 | 自定义语言 |
| Aviator | com.googlecode.aviator |
AviatorEvaluator |
高性能表达式 |
| Drools | org.drools |
规则引擎 | 业务规则 |
我们按"Pattern → SpEL → JavaCC"的顺序来剖析。
二、JDK 源码剖析:Pattern / Matcher 正则解释器
2.1 Pattern:正则解释器
源码位置:
- OpenJDK 17
src/java.base/share/classes/java/util/regex/Pattern.java
核心源码(简化版):
java
/**
* ✅ 正则解释器
*/
public final class Pattern implements java.io.Serializable {
/**
* ✅ 编译正则
*/
public static Pattern compile(String regex) {
return new Pattern(regex, 0);
}
private Pattern(String p, int f) {
// 1️⃣ 解析正则
pattern = p;
// 2️⃣ 编译为内部表示
compiled = Pattern.compile(p, f);
// 3️⃣ 创建 matcher
}
/**
* ✅ 创建匹配器
*/
public Matcher matcher(CharSequence input) {
if (!compiled) {
synchronized (this) {
if (!compiled) compile();
}
}
return new Matcher(this, input);
}
}
关键点:
| 关键点 | 解释 |
|---|---|
compile() |
编译正则 |
| 内部表示 | NFA / DFA |
matcher() |
创建匹配器 |
2.2 Matcher:解释执行
核心源码:
java
/**
* ✅ 匹配器
*/
public final class Matcher implements MatchResult {
private final Pattern parentPattern;
private CharSequence text;
/**
* ✅ 查找下一个匹配
*/
public boolean find() {
// 1️⃣ 内部 NFA 状态机
// 2️⃣ 推进字符
// 3️⃣ 检查匹配
}
/**
* ✅ 完整匹配
*/
public boolean matches() {
return false; // 简化
}
/**
* ✅ 获取匹配组
*/
public String group() {
return text.subSequence(...).toString();
}
}
关键点:
| 关键点 | 解释 |
|---|---|
find() |
部分匹配 |
matches() |
完全匹配 |
group() |
提取匹配 |
2.3 完整正则调用链
Pattern.compile("\\d+")
↓
Pattern.compile(regex, flags)
↓
1️⃣ 解析正则语法
2️⃣ 生成 NFA
3️⃣ 优化为 DFA
↓
Pattern.compile() 返回
↓
pattern.matcher(input)
↓
new Matcher(pattern, input)
↓
matcher.find()
↓
NFA 状态机推进,匹配
↓
返回匹配结果
2.4 Pattern 的"作者选择"
| 设计点 | 选择 | 原因 |
|---|---|---|
| 不可变 | Pattern final | 线程安全 |
| 编译一次 | match 多次 | 性能 |
| NFA / DFA | 内部优化 | 速度 |
| 静态方法 | compile | 易用 |
三、Spring 源码剖析:SpEL 表达式解释器
3.1 SpelExpressionParser:SpEL 解析器
源码位置:
- Spring Expression 6.1.x
spring-expression/src/main/java/org/springframework/expression/spel/standard/SpelExpressionParser.java
核心源码:
java
/**
* ✅ SpEL 解释器
*/
public class SpelExpressionParser extends ExpressionParser {
private final SpelParserConfiguration configuration;
/**
* ✅ 解析表达式
*/
public SpelExpression parseExpression(String expressionString)
throws ParseException {
return parseExpression(expressionString, ParserContext.TEMPLATE_EXPRESSION);
}
public SpelExpression parseExpression(String expressionString,
ParserContext context)
throws ParseException {
// 1️⃣ 词法分析
// 2️⃣ 语法分析
// 3️⃣ 生成 AST
// 4️⃣ 编译为内部表示
return new SpelExpression(...);
}
}
关键点:
| 关键点 | 解释 |
|---|---|
parseExpression() |
解析 |
| 词法 + 语法 | 分两阶段 |
| AST | 抽象语法树 |
3.2 SpelExpression:可执行表达式
核心源码:
java
/**
* ✅ SpEL 表达式
*/
public class SpelExpression implements Expression {
private final String expression;
private final SpelNode AST; // ✅ AST 根节点
/**
* ✅ 求值
*/
@Override
public Object getValue() throws EvaluationException {
// 1️⃣ 解释执行 AST
Object result = AST.getValue();
return result;
}
@Override
public <T> T getValue(Class<T> desiredResultType) throws EvaluationException {
// 类型转换
return (T) AST.getValue();
}
}
关键点:
| 关键点 | 解释 |
|---|---|
| AST | 解释执行 |
getValue() |
求值 |
| 类型转换 | 灵活 |
3.3 SpelNode:AST 节点
核心源码:
java
/**
* ✅ AST 节点
*/
public abstract class SpelNode implements ... {
/**
* ✅ 求值
*/
public abstract Object getValue();
/**
* 接受访问者
*/
public abstract <T> T accept(SpelNodeVisitor<T> visitor);
}
具体节点(OpPlus):
java
/**
* ✅ 加法节点
*/
public class OpPlus extends SpelNode {
@Override
public Object getValue() {
// ✅ 递归求值
Object left = getChild(0).getValue();
Object right = getChild(1).getValue();
return add(left, right);
}
}
关键点:
| 关键点 | 解释 |
|---|---|
| 递归求值 | 子节点先求值 |
| 访问者 | 多种分析 |
3.4 完整 SpEL 调用链
parser.parseExpression("amount > 100 ? amount - 10 : amount")
↓
1️⃣ 词法分析:amount, >, 100, ?, amount, -, 10, :, amount
2️⃣ 语法分析:ConditionalExpression(GT(VariableRef, 100), Minus(VariableRef, 10), VariableRef)
3️⃣ 生成 AST
↓
new SpelExpression(ast)
↓
expression.getValue()
↓
AST.getValue()
↓
递归求值
3.5 SpelExpressionParser 的"作者选择"
| 设计点 | 选择 | 原因 |
|---|---|---|
| 两阶段解析 | 词法 + 语法 | 标准 |
| AST | 树形表示 | 灵活 |
| 编译缓存 | 多次执行 | 性能 |
| 访问者模式 | SpelNodeVisitor | 扩展 |
四、JavaCC 源码剖析:工业级解析器生成器
4.1 JavaCC 是什么?
JavaCC 是工业级的解析器生成器 ,可用 .jj 文件定义语法,自动生成 Java 解释器。
4.2 简单语法定义
// 简单表达式语法
expression ::= term ( "+" term )*
term ::= factor ( "*" factor )*
factor ::= number | "(" expression ")"
number ::= [0-9]+
4.3 JavaCC 生成的代码
java
/**
* ✅ JavaCC 生成的解析器(简化)
*/
public class ExpressionParser {
public int parse() throws ParseException {
return expression();
}
int expression() throws ParseException {
int result = term();
while (true) {
if (token == PLUS) {
consume();
result += term();
} else {
break;
}
}
return result;
}
int term() throws ParseException {
int result = factor();
while (true) {
if (token == TIMES) {
consume();
result *= factor();
} else {
break;
}
}
return result;
}
int factor() throws ParseException {
if (token == NUMBER) {
int value = token.value;
consume();
return value;
} else if (token == LPAREN) {
consume();
int result = expression();
match(RPAREN);
return result;
}
throw new ParseException("...");
}
}
关键点:
| 关键点 | 解释 |
|---|---|
| 递归下降 | 标准解析 |
| Token 流 | 输入 |
| match() | 匹配 |
4.4 大白商城的"抄作业"
java
/**
* 借鉴 JavaCC:自定义优惠规则解释器
*/
public class DiscountRuleParser {
/**
* ✅ 解析优惠规则
*/
public RuleExpression parse(String rule) {
// 1️⃣ 词法分析
List<Token> tokens = tokenize(rule);
// 2️⃣ 语法分析 + AST
return parseExpression(tokens);
}
private List<Token> tokenize(String rule) {
// 简化:按空格分词
String[] parts = rule.split("\\s+");
return Arrays.stream(parts).map(Token::new).toList();
}
private RuleExpression parseExpression(List<Token> tokens) {
// 递归下降
// ...
}
}
4.5 JavaCC 的"作者选择"
| 设计点 | 选择 | 原因 |
|---|---|---|
| 自动生成 | 从语法到代码 | 提高效率 |
| 递归下降 | 标准算法 | 易理解 |
| Token 流 | 输入 | 灵活 |
| 错误恢复 | 默认 | 健壮 |
五、为什么框架作者这么设计?
读源码看的是"实现",悟的是"设计哲学"。
5.1 框架作者选解释器的 4 个核心考虑
| 序号 | 考虑 | 举例 |
|---|---|---|
| ① | 业务规则动态化 | 优惠规则 |
| ② | 自定义语言 | 贴近业务 |
| ③ | 复用解析 | 一次解析多次执行 |
| ④ | 可扩展 | 加新规则 |
5.2 解释器 vs 策略:源码里怎么选?
| 场景 | 选哪个 | 原因 |
|---|---|---|
| 业务方配置 | 解释器 | 动态化 |
| 4 种算法 | 策略 | 简单 |
| 数学表达式 | 解释器 | 复杂 |
| 业务规则多变 | 解释器 | 灵活 |
5.3 框架作者不选解释器的 3 种情况
| 序号 | 场景 | 原因 |
|---|---|---|
| ① | 性能关键 | 解释慢 |
| ② | 规则固定 | 硬编码够 |
| ③ | 业务极简 | 过度设计 |
5.4 解释器在企业级应用的 3 种典型用法
用法 1:正则匹配
Pattern / Matcher
→ 字符串模式
用法 2:业务表达式
SpEL / Aviator
→ 业务规则
用法 3:自定义语言
JavaCC / ANTLR
→ 复杂语法
六、借鉴到我们项目:3 个"抄作业"实践
6.1 实践 1:业务规则引擎
场景:大白商城运营可视化配置规则。
完整实现:
java
/**
* 借鉴 SpEL:业务规则引擎
*/
@Service
public class BusinessRuleEngine {
@Autowired
private SpelExpressionParser parser;
/**
* ✅ 业务方配置规则
*/
public boolean evaluate(String rule, Map<String, Object> vars) {
// 1️⃣ 解析
Expression expression = parser.parseExpression(rule);
// 2️⃣ 求值
Boolean result = expression.getValue(
new StandardEvaluationContext(vars), Boolean.class);
return Boolean.TRUE.equals(result);
}
public boolean example(BigDecimal amount, String userLevel) {
Map<String, Object> vars = new HashMap<>();
vars.put("amount", amount);
vars.put("userLevel", userLevel);
// ✅ SpEL 规则:VIP 满 100 减 10
String rule = "userLevel == 'VIP' && amount > 100";
return evaluate(rule, vars);
}
}
6.2 实践 2:条件路由
场景:大白商城订单按规则路由到不同处理。
完整实现:
java
/**
* 借鉴 Aviator:条件路由
*/
@Service
public class ConditionalRouter {
public String route(Map<String, Object> context) {
// 1️⃣ 规则 1:金额 > 1000 走加急
if (evaluate("amount > 1000", context)) {
return "URGENT";
}
// 2️⃣ 规则 2:VIP 走优先
if (evaluate("userLevel == 'VIP'", context)) {
return "PRIORITY";
}
// 3️⃣ 默认
return "NORMAL";
}
private boolean evaluate(String rule, Map<String, Object> ctx) {
com.googlecode.aviator.Expression expression =
AviatorEvaluator.compile(rule);
return (boolean) expression.execute(ctx);
}
}
6.3 实践 3:动态权限校验
场景:大白商城基于表达式的权限校验。
完整实现:
java
/**
* 借鉴 SpEL:动态权限
*/
@Service
public class DynamicPermissionService {
@Autowired
private SpelExpressionParser parser;
/**
* ✅ 权限校验
*/
public boolean check(String permissionRule, User user, Resource resource) {
Map<String, Object> vars = new HashMap<>();
vars.put("user", user);
vars.put("resource", resource);
Expression expression = parser.parseExpression(permissionRule);
return Boolean.TRUE.equals(
expression.getValue(new StandardEvaluationContext(vars), Boolean.class));
}
public boolean example(User user, Resource resource) {
// ✅ 动态规则:用户角色 == 资源所有者 或 资源公开
String rule = "user.role == 'ADMIN' || resource.owner == user.id || resource.public";
return check(rule, user, resource);
}
}
收益:
| 优化点 | 效果 |
|---|---|
| 业务规则引擎 | 运营可视化配置 |
| 条件路由 | 灵活路由 |
| 动态权限 | 灵活权限 |
七、本篇小结 + 下一模式预告
7.1 本篇小结(5 个核心要点)
- JDK Pattern :
compile() + matcher() + NFA/DFA正则解释器。 - Spring SpEL :
SpelExpressionParser + AST + getValue()表达式解释器。 - JavaCC :工业级解析器生成器 ,从语法到代码。
- 设计哲学 :词法 + 语法 + AST + 求值。
- 借鉴价值 :业务规则 / 条件路由 / 动态权限 ,任何"业务规则动态化"都用解释器。
7.2 一句话总结
解释器在源码里不是"自定义语言"的形象化,而是"语法分析 + AST + 求值"三件套的工业级架构 。Pattern 用 NFA/DFA 实现正则匹配,SpEL 用 AST 树形求值,JavaCC 自动生成解析器。学解释器,先学 Pattern 和 SpEL。
7.3 知识脑图
解释器源码剖析
├── JDK
│ ├── Pattern(正则)
│ │ ├── compile()(编译)
│ │ ├── matcher()(匹配器)
│ │ └── NFA/DFA(内部表示)
│ └── Matcher
│ ├── find() / matches()
│ └── group()
├── Spring
│ ├── SpelExpressionParser
│ │ ├── 词法分析
│ │ ├── 语法分析
│ │ └── AST 生成
│ ├── SpelExpression
│ │ ├── AST 根节点
│ │ └── getValue()(求值)
│ └── SpelNode(AST 节点)
│ ├── 递归求值
│ └── accept()(访问者)
├── JavaCC
│ ├── .jj 语法文件
│ ├── 递归下降
│ └── 自动生成代码
└── 借鉴价值
├── 业务规则引擎
├── 条件路由
└── 动态权限
7.4 下一模式预告
第 46 篇【责任链模式 - 业务实战篇】:大白商城优惠/风控/审批的"链式处理"
下一篇我们会实战责任链模式(本系列最后一种 GoF 设计模式),回答 4 个问题:
- 优惠/风控/审批怎么串联多个 handler?
- 责任链 vs 策略 / 状态模式怎么区分?
- Servlet Filter 怎么用责任链?
- Spring Interceptor 怎么借鉴责任链?
并附完整的风控链实战代码(含 handler + 链 + 单元测试),可直接复制到 IDEA 跑。
觉得对您有帮助,麻烦 点点关注啦 ,您的关注是我创作的最大动力~ 🎯