大白话说Java设计模式-45-解释器模式(源码剖析篇)

📌 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 个核心要点)

  1. JDK Patterncompile() + matcher() + NFA/DFA 正则解释器。
  2. Spring SpELSpelExpressionParser + AST + getValue() 表达式解释器。
  3. JavaCC工业级解析器生成器从语法到代码
  4. 设计哲学词法 + 语法 + AST + 求值
  5. 借鉴价值业务规则 / 条件路由 / 动态权限任何"业务规则动态化"都用解释器

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 个问题:

  1. 优惠/风控/审批怎么串联多个 handler?
  2. 责任链 vs 策略 / 状态模式怎么区分?
  3. Servlet Filter 怎么用责任链?
  4. Spring Interceptor 怎么借鉴责任链?

并附完整的风控链实战代码(含 handler + 链 + 单元测试),可直接复制到 IDEA 跑


觉得对您有帮助,麻烦 点点关注啦 ,您的关注是我创作的最大动力~ 🎯

相关推荐
吴声子夜歌25 分钟前
Java——开发中通用的方法和准则(二)
java·开发语言·php
袁震33 分钟前
HarmonyOS 应用包体积优化与上架自检实战:从 76.2MB 到 3.6MB
java·华为·性能优化·harmonyos
zcmodeltech1 小时前
反应装置模型控制系统设计与实现:多设备协同联动方案
java·网络·数据库·stm32·嵌入式硬件·能源·制造
码视野1 小时前
基于 Spring Boot + Vue3 的【城市地下燃气管网微泄漏感知与相邻地下空间燃爆预警中台】设计与实现(含PRD/三端高保真源码/大屏)
java·前端·人工智能·spring boot·后端
2601_962071571 小时前
数据库系统架构与DBMS功能探微:现代信息时代数据管理的关键
java·开发语言·数据库
梦想的旅途21 小时前
如何高效调用企业微信通讯录API管理组织架构
java·开发语言·企业微信
2601_962065252 小时前
2.启动mysql容器
java·数据库·mysql
2601_962065492 小时前
PHP For 循环
android·java·php
2601_962201722 小时前
Java进阶,集合,Colllection,常见数据结构
java·数据结构·windows