📌 PDF :AI人工智能 --- 大模型微调与部署实战项目
大白话说Java设计模式-43-访问者模式(源码剖析篇):JDK / Spring 中的访问者实现
📌 一句话本质:访问者在源码里不是"操作分离"的形象化,而是"元素 + 访问者 + accept/visit"三件套的工业级架构。
🏷️ 标签:访问者模式 / ElementVisitor / ClassVisitor / SpEL 🎯 适合:中高级后端 / 想读懂主流框架源码的工程师
目录
- 一、为什么读访问者源码?一张地图先看清
- [二、JDK 源码剖析:ElementVisitor 元素访问者](#二、JDK 源码剖析:ElementVisitor 元素访问者)
- [三、ASM 源码剖析:ClassVisitor 字节码访问者](#三、ASM 源码剖析:ClassVisitor 字节码访问者)
- [四、Spring 源码剖析:SpEL 表达式访问者](#四、Spring 源码剖析:SpEL 表达式访问者)
- 五、为什么框架作者这么设计?
- [六、借鉴到我们项目:3 个"抄作业"实践](#六、借鉴到我们项目:3 个"抄作业"实践)
- [七、本篇小结 + 下一模式预告](#七、本篇小结 + 下一模式预告)
一、为什么读访问者源码?一张地图先看清
访问者模式在 JDK 和 Spring 里出现得很经典 ------ElementVisitor / ClassVisitor / SpEL 都是访问者思想。
源码地图:
| 类别 | 框架 / 中间件 | 访问者接口 | 元素 |
|---|---|---|---|
| JDK | javax.lang.model |
ElementVisitor |
Element |
| JDK | javax.tools |
DiagnosticListener |
Diagnostic |
| ASM | org.objectweb.asm |
ClassVisitor |
Class 字节码 |
| ASM | org.objectweb.asm |
MethodVisitor |
Method 字节码 |
| Spring | spring-expression |
SpelNodeVisitor |
SpelNode |
| JavaParser | com.github.javaparser |
VoidVisitor |
AST |
我们按"ElementVisitor → ClassVisitor → SpEL"的顺序来剖析。
二、JDK 源码剖析:ElementVisitor 元素访问者
2.1 ElementVisitor:访问者接口
源码位置:
- OpenJDK 17
src/java.compiler/share/classes/javax/lang/model/element/ElementVisitor.java
核心源码:
java
/**
* ✅ 访问者接口:元素访问者
*/
public interface ElementVisitor<R, P> {
/**
* ✅ 默认 visit(兜底)
*/
R visit(Element e, P p);
/**
* ✅ 访问包
*/
R visitPackage(PackageElement e, P p);
/**
* ✅ 访问类型
*/
R visitType(TypeElement e, P p);
/**
* 访问变量
*/
R visitVariable(VariableElement e, P p);
/**
* 访问可执行元素(方法/构造器)
*/
R visitExecutable(ExecutableElement e, P p);
/**
* 访问类型参数
*/
R visitTypeParameter(TypeParameterElement e, P p);
/**
* 访问未知元素
*/
R visitUnknown(Element e, P p);
}
关键点:
| 关键点 | 解释 |
|---|---|
R |
visit 返回类型 |
P |
visit 参数类型 |
| 多种 visit | 每个 Element 子类对应 |
| 泛型 | 灵活 |
2.2 AbstractElementVisitor6:默认实现
核心源码:
java
/**
* ✅ 默认访问者
*/
public abstract class AbstractElementVisitor6<R, P>
implements ElementVisitor<R, P> {
@Override
public R visit(Element e, P p) {
// ✅ 默认调 visitUnknown
return e.accept(this, p);
}
@Override
public R visitPackage(PackageElement e, P p) {
return visitUnknown(e, p);
}
@Override
public R visitType(TypeElement e, P p) {
return visitUnknown(e, p);
}
// ... 其他默认实现都调 visitUnknown
}
关键点:
| 关键点 | 解释 |
|---|---|
| 默认调 visitUnknown | 兜底 |
| 业务方可覆盖 | 自定义 |
| 模板方法 | 父类定骨架 |
2.3 Element.accept():双重分派
核心源码:
java
/**
* ✅ 元素接口
*/
public interface Element {
/**
* ✅ 接受访问者(双重分派)
*/
<R, P> R accept(ElementVisitor<R, P> v, P p);
}
具体实现(TypeElement):
java
/**
* ✅ TypeElement.accept()
*/
public interface TypeElement extends Element, ... {
@Override
<R, P> R accept(ElementVisitor<R, P> v, P p) {
// ✅ 调 visitor.visitType(this, p)
return v.visitType(this, p);
}
}
关键点:
| 关键点 | 解释 |
|---|---|
| 元素.accept(visitor) | 接受访问者 |
| 元素调 visitor.visitXxx | 双重分派 |
| 编译期决定 | 类型检查 |
2.4 完整访问者调用链
Element element = ...;
ElementVisitor<R, P> visitor = new MyVisitor();
element.accept(visitor, p);
↓
element.accept() // 调 visitor.visitXxx
↓
visitor.visitType(this, p) // 编译期决定
↓
MyVisitor.visitType(typeElement, p) // 业务方实现
2.5 ElementVisitor 的"作者选择"
| 设计点 | 选择 | 原因 |
|---|---|---|
| 泛型 | R, P | 灵活 |
| 兜底 | visitUnknown | 默认 |
| 双重分派 | accept/visit | 编译期决定 |
| 默认实现 | AbstractElementVisitor6 | 简化 |
三、ASM 源码剖析:ClassVisitor 字节码访问者
3.1 ClassVisitor:字节码访问者
源码位置:
- ASM 9.x
org.objectweb.asm.ClassVisitor
核心源码:
java
/**
* ✅ 访问者:Class 字节码
*/
public abstract class ClassVisitor {
protected final int api;
protected ClassVisitor cv;
public ClassVisitor(int api) {
this(api, null);
}
public ClassVisitor(int api, ClassVisitor classVisitor) {
this.api = api;
this.cv = classVisitor;
}
/**
* ✅ 访问类
*/
public void visit(int version, int access, String name,
String signature, String superName, String[] interfaces) {
if (cv != null) {
cv.visit(version, access, name, signature, superName, interfaces);
}
}
/**
* ✅ 访问字段
*/
public FieldVisitor visitField(int access, String name, String desc,
String signature, Object value) {
if (cv != null) {
return cv.visitField(access, name, desc, signature, value);
}
return null;
}
/**
* ✅ 访问方法
*/
public MethodVisitor visitMethod(int access, String name, String desc,
String signature, String[] exceptions) {
if (cv != null) {
return cv.visitMethod(access, name, desc, signature, exceptions);
}
return null;
}
/**
* 访问结束
*/
public void visitEnd() {
if (cv != null) {
cv.visitEnd();
}
}
}
关键点:
| 关键点 | 解释 |
|---|---|
| 链式 cv | 多访问者串联 |
| 兜底 null | 子访问者可选 |
| 多种 visit | 字段/方法/类 |
3.2 ClassReader:元素
核心源码:
java
/**
* ✅ 元素:ClassReader
*/
public class ClassReader {
public void accept(ClassVisitor classVisitor, int parsingOptions) {
// 1️⃣ 读 class 文件
// 2️⃣ 调 classVisitor.visit()
// 3️⃣ 遍历字段,调 visitField()
// 4️⃣ 遍历方法,调 visitMethod()
}
}
3.3 ClassWriter:访问者 + 写
核心源码:
java
/**
* ✅ 访问者:ClassWriter(可写)
*/
public class ClassWriter extends ClassVisitor {
public byte[] toByteArray() {
// ✅ 生成字节码
}
}
3.4 业务方:自定义 ClassVisitor
java
/**
* ✅ 业务方:自定义 ClassVisitor
*/
public class LogClassVisitor extends ClassVisitor {
public LogClassVisitor(ClassVisitor cv) {
super(Opcodes.ASM9, cv);
}
@Override
public MethodVisitor visitMethod(int access, String name, String desc,
String signature, String[] exceptions) {
log.info("Method: {} {}", name, desc);
MethodVisitor mv = super.visitMethod(access, name, desc, signature, exceptions);
return new LogMethodVisitor(mv);
}
}
3.5 完整 ASM 调用链
ClassReader reader = new ClassReader(bytes);
ClassWriter writer = new ClassWriter(reader, 0);
LogClassVisitor visitor = new LogClassVisitor(writer);
reader.accept(visitor, 0);
↓
reader.accept()
↓
visitor.visit() // 访问类
visitor.visitField() // 访问字段
visitor.visitMethod() // 访问方法
visitor.visitEnd() // 访问结束
↓
writer.toByteArray() // 生成字节码
3.6 ClassVisitor 的"作者选择"
| 设计点 | 选择 | 原因 |
|---|---|---|
| 链式 cv | 多访问者 | 装饰器风格 |
| 多种 visit | 字段/方法/类 | 全覆盖 |
| 兜底 null | 简化 | 灵活 |
| 字节码 API | 低级 | 性能 |
四、Spring 源码剖析:SpEL 表达式访问者
4.1 SpelNode:元素
源码位置:
- Spring Expression 6.1.x
spring-expression/src/main/java/org/springframework/expression/spel/ast/SpelNode.java
核心源码:
java
/**
* ✅ 元素:SpelNode
*/
public abstract class SpelNode implements ... {
/**
* ✅ 接受访问者
*/
public abstract <T> T accept(SpelNodeVisitor<T> visitor);
}
4.2 SpelNodeVisitor:访问者接口
核心源码:
java
/**
* ✅ 访问者接口
*/
public interface SpelNodeVisitor<T> {
/**
* ✅ 默认 visit
*/
T visit(SpelNode node);
/**
* 复合
*/
T visitComp(Comp node);
/**
* 字面量
*/
T visitLiteral(Literal node);
/**
* 方法/构造器引用
*/
T visitMethodRef(MethodReference node);
/**
* 属性引用
*/
T visitPropertyRef(PropertyReference node);
// ... 其他
}
4.3 SpelNodeImpl:元素实现
核心源码:
java
/**
* ✅ 元素实现:Literal
*/
public class Literal extends SpelNodeImpl {
private final Object value;
@Override
public <T> T accept(SpelNodeVisitor<T> visitor) {
// ✅ 调 visitor.visitLiteral
return visitor.visitLiteral(this);
}
}
4.4 业务方:自定义 SpelNodeVisitor
java
/**
* ✅ 业务方:自定义 SpelNodeVisitor
*/
public class LoggingSpelNodeVisitor implements SpelNodeVisitor<Void> {
@Override
public Void visit(SpelNode node) {
log.info("Node: {}", node.getClass().getSimpleName());
return null;
}
@Override
public Void visitLiteral(Literal node) {
log.info("Literal: {}", node.getLiteralValue());
return null;
}
}
4.5 SpelNodeVisitor 的"作者选择"
| 设计点 | 选择 | 原因 |
|---|---|---|
| 多种 visit | AST 节点全覆盖 | 灵活 |
| 默认 visit | 兜底 | 简化 |
| 泛型 T | 返回值灵活 | 易用 |
五、为什么框架作者这么设计?
读源码看的是"实现",悟的是"设计哲学"。
5.1 框架作者选访问者的 4 个核心考虑
| 序号 | 考虑 | 举例 |
|---|---|---|
| ① | 数据结构稳定 | AST / 字节码 |
| ② | 操作多变 | 编译/字节码增强 |
| ③ | 多种操作作用于多种节点 | 类型检查/代码生成 |
| ④ | 可扩展 | 加新操作只加访问者 |
5.2 访问者 vs 策略:源码里怎么选?
| 场景 | 选哪个 | 原因 |
|---|---|---|
| 多种操作作用于多种节点 | 访问者 | 数据结构稳定 |
| 单一算法切换 | 策略 | 算法独立 |
| AST 遍历 | 访问者 | 多种分析 |
| 算法组合 | 策略 | 简单 |
5.3 框架作者不选访问者的 3 种情况
| 序号 | 场景 | 原因 |
|---|---|---|
| ① | 数据结构常变 | 加元素改 N 个访问者 |
| ② | 操作少 | 没必要 |
| ③ | 对象简单 | 用不上 |
5.4 访问者在企业级应用的 3 种典型用法
用法 1:AST 遍历
ElementVisitor / SpelNodeVisitor
→ 多种分析
用法 2:字节码增强
ClassVisitor / MethodVisitor
→ AOP / Lombok
用法 3:业务规则
营销规则 / 报表导出
→ 数据结构稳定
六、借鉴到我们项目:3 个"抄作业"实践
6.1 实践 1:AST 业务分析
场景:大白商城自定义规则表达式分析。
完整实现:
java
/**
* 借鉴 SpelNodeVisitor:自定义业务 AST 访问者
*/
public class RuleAstVisitor implements RuleNodeVisitor<Void> {
@Override
public Void visitExpression(RuleExpression node) {
log.info("Rule: {}", node.getExpression());
return null;
}
@Override
public Void visitCondition(RuleCondition node) {
log.info("Condition: field={}, op={}, value={}",
node.getField(), node.getOperator(), node.getValue());
return null;
}
@Override
public Void visitAction(RuleAction node) {
log.info("Action: type={}, params={}",
node.getActionType(), node.getParams());
return null;
}
}
6.2 实践 2:业务对象数据采集
场景:大白商城业务对象多维数据采集(统计/导出/分析)。
完整实现:
java
/**
* 借鉴 ClassVisitor:业务对象访问者
*/
public class BusinessDataCollector implements BusinessVisitor {
private final Map<String, Object> data = new HashMap<>();
@Override
public void visit(Goods goods) {
data.put("goodsCount", 1);
data.put("goodsTotal",
data.getOrDefault("goodsTotal", 0.0)
+ goods.getPrice().doubleValue());
}
@Override
public void visit(Order order) {
data.put("orderCount", 1);
data.put("orderTotal",
data.getOrDefault("orderTotal", 0.0)
+ order.getTotal().doubleValue());
}
@Override
public void visit(Coupon coupon) {
data.put("couponCount", 1);
data.put("couponTotal",
data.getOrDefault("couponTotal", 0.0)
+ coupon.getAmount().doubleValue());
}
public Map<String, Object> getData() {
return data;
}
}
6.3 实践 3:多维度分析
场景:大白商城订单多维度分析(销售/利润/用户)。
完整实现:
java
/**
* 借鉴 ElementVisitor:多维度分析
*/
public class MultiDimensionAnalyzer implements MarketingVisitor {
private double sales = 0;
private double profit = 0;
private Map<String, Integer> categoryCount = new HashMap<>();
@Override
public void visit(Goods goods) {
sales += goods.getPrice().doubleValue();
profit += goods.getPrice().doubleValue() * 0.2;
}
@Override
public void visit(Order order) {
sales += order.getTotal().doubleValue();
}
@Override
public void visit(Coupon coupon) {
// 优惠券对利润影响
profit -= coupon.getAmount().doubleValue() * 0.1;
}
public void printReport() {
log.info("【分析】销售额: {}", sales);
log.info("【分析】利润: {}", profit);
log.info("【分析】分类: {}", categoryCount);
}
}
收益:
| 优化点 | 效果 |
|---|---|
| AST 业务分析 | 规则灵活扩展 |
| 业务对象采集 | 多维数据 |
| 多维度分析 | 一遍采集多维数据 |
七、本篇小结 + 下一模式预告
7.1 本篇小结(5 个核心要点)
- JDK ElementVisitor :
Element.accept() / ElementVisitor.visitXxx双重分派。 - ASM ClassVisitor :链式 cv + visitField/visitMethod/visitEnd。
- Spring SpEL :
SpelNode.accept() / SpelNodeVisitor.visitXxx表达式访问。 - 设计哲学 :元素 + 访问者 + 双重分派。
- 借鉴价值 :AST 业务分析 / 数据采集 / 多维度分析 ,任何"操作多变"都用访问者。
7.2 一句话总结
访问者在源码里不是"操作分离"的形象化,而是"元素 + 访问者 + accept/visit"三件套的工业级架构 。ElementVisitor 遍历 AST,ClassVisitor 访问字节码,SpelNodeVisitor 遍历表达式。学访问者,先学 ElementVisitor 和 ClassVisitor。
7.3 知识脑图
访问者源码剖析
├── JDK
│ ├── ElementVisitor
│ │ ├── visit / visitPackage / visitType / visitVariable
│ │ └── visitExecutable / visitTypeParameter / visitUnknown
│ ├── AbstractElementVisitor6(默认)
│ └── Element.accept()(双重分派)
├── ASM
│ ├── ClassVisitor
│ │ ├── visit() / visitField() / visitMethod() / visitEnd()
│ │ └── 链式 cv
│ ├── ClassReader(元素)
│ ├── ClassWriter(写)
│ └── MethodVisitor
├── Spring
│ ├── SpelNode(元素)
│ ├── SpelNodeVisitor(访问者)
│ └── SpelNodeImpl
└── 借鉴价值
├── AST 业务分析
├── 业务对象采集
└── 多维度分析
7.4 下一模式预告
第 44 篇【解释器模式 - 业务实战篇】:大白商城优惠规则表达式的"自定义语言"
下一篇我们会实战解释器模式,回答 4 个问题:
- 优惠规则表达式(满 100 减 10 / 8 折 / 立减 50)怎么解析执行?
- 解释器 vs 策略 / 状态模式怎么区分?
- Spring SpEL 怎么用解释器?
- Regex / JavaCC 怎么借鉴解释器?
并附完整的优惠规则解释器实战代码(含表达式 + 终结符 + 非终结符 + 单元测试),可直接复制到 IDEA 跑。
觉得对您有帮助,麻烦 点点关注啦 ,您的关注是我创作的最大动力~ 🎯