大白话说Java设计模式-43-访问者模式(源码剖析篇)
2026/9/13 22:18:07 网站建设 项目流程

📌大厂规范:Java项目工具类 — 01_字符串类型转换工具类(Java企业级代码)

大白话说Java设计模式-43-访问者模式(源码剖析篇):JDK / Spring 中的访问者实现

📌一句话本质:访问者在源码里不是"操作分离"的形象化,而是"元素 + 访问者 + accept/visit"三件套的工业级架构。

🏷️ 标签:访问者模式 / ElementVisitor / ClassVisitor / SpEL 🎯 适合:中高级后端 / 想读懂主流框架源码的工程师


目录

  • 一、为什么读访问者源码?一张地图先看清
  • 二、JDK 源码剖析:ElementVisitor 元素访问者
  • 三、ASM 源码剖析:ClassVisitor 字节码访问者
  • 四、Spring 源码剖析:SpEL 表达式访问者
  • 五、为什么框架作者这么设计?
  • 六、借鉴到我们项目:3 个"抄作业"实践
  • 七、本篇小结 + 下一模式预告

一、为什么读访问者源码?一张地图先看清

访问者模式在 JDK 和 Spring 里出现得很经典——ElementVisitor/ClassVisitor/ SpEL 都是访问者思想。

源码地图

类别框架 / 中间件访问者接口元素
JDKjavax.lang.modelElementVisitorElement
JDKjavax.toolsDiagnosticListenerDiagnostic
ASMorg.objectweb.asmClassVisitorClass 字节码
ASMorg.objectweb.asmMethodVisitorMethod 字节码
Springspring-expressionSpelNodeVisitorSpelNode
JavaParsercom.github.javaparserVoidVisitorAST

我们按"ElementVisitor → ClassVisitor → SpEL"的顺序来剖析。


二、JDK 源码剖析:ElementVisitor 元素访问者

2.1ElementVisitor:访问者接口

源码位置

  • OpenJDK 17
  • src/java.compiler/share/classes/javax/lang/model/element/ElementVisitor.java

核心源码

/** * ✅ 访问者接口:元素访问者 */publicinterfaceElementVisitor<R,P>{/** * ✅ 默认 visit(兜底) */Rvisit(Elemente,Pp);/** * ✅ 访问包 */RvisitPackage(PackageElemente,Pp);/** * ✅ 访问类型 */RvisitType(TypeElemente,Pp);/** * 访问变量 */RvisitVariable(VariableElemente,Pp);/** * 访问可执行元素(方法/构造器) */RvisitExecutable(ExecutableElemente,Pp);/** * 访问类型参数 */RvisitTypeParameter(TypeParameterElemente,Pp);/** * 访问未知元素 */RvisitUnknown(Elemente,Pp);}

关键点

关键点解释
Rvisit 返回类型
Pvisit 参数类型
多种 visit每个 Element 子类对应
泛型灵活

2.2AbstractElementVisitor6:默认实现

核心源码

/** * ✅ 默认访问者 */publicabstractclassAbstractElementVisitor6<R,P>implementsElementVisitor<R,P>{@OverridepublicRvisit(Elemente,Pp){// ✅ 默认调 visitUnknownreturne.accept(this,p);}@OverridepublicRvisitPackage(PackageElemente,Pp){returnvisitUnknown(e,p);}@OverridepublicRvisitType(TypeElemente,Pp){returnvisitUnknown(e,p);}// ... 其他默认实现都调 visitUnknown}

关键点

关键点解释
默认调 visitUnknown兜底
业务方可覆盖自定义
模板方法父类定骨架

2.3Element.accept():双重分派

核心源码

/** * ✅ 元素接口 */publicinterfaceElement{/** * ✅ 接受访问者(双重分派) */<R,P>Raccept(ElementVisitor<R,P>v,Pp);}

具体实现(TypeElement):

/** * ✅ TypeElement.accept() */publicinterfaceTypeElementextendsElement,...{@Override<R,P>Raccept(ElementVisitor<R,P>v,Pp){// ✅ 调 visitor.visitType(this, p)returnv.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.5ElementVisitor的"作者选择"

设计点选择原因
泛型R, P灵活
兜底visitUnknown默认
双重分派accept/visit编译期决定
默认实现AbstractElementVisitor6简化

三、ASM 源码剖析:ClassVisitor 字节码访问者

3.1ClassVisitor:字节码访问者

源码位置

  • ASM 9.x
  • org.objectweb.asm.ClassVisitor

核心源码

/** * ✅ 访问者:Class 字节码 */publicabstractclassClassVisitor{protectedfinalintapi;protectedClassVisitorcv;publicClassVisitor(intapi){this(api,null);}publicClassVisitor(intapi,ClassVisitorclassVisitor){this.api=api;this.cv=classVisitor;}/** * ✅ 访问类 */publicvoidvisit(intversion,intaccess,Stringname,Stringsignature,StringsuperName,String[]interfaces){if(cv!=null){cv.visit(version,access,name,signature,superName,interfaces);}}/** * ✅ 访问字段 */publicFieldVisitorvisitField(intaccess,Stringname,Stringdesc,Stringsignature,Objectvalue){if(cv!=null){returncv.visitField(access,name,desc,signature,value);}returnnull;}/** * ✅ 访问方法 */publicMethodVisitorvisitMethod(intaccess,Stringname,Stringdesc,Stringsignature,String[]exceptions){if(cv!=null){returncv.visitMethod(access,name,desc,signature,exceptions);}returnnull;}/** * 访问结束 */publicvoidvisitEnd(){if(cv!=null){cv.visitEnd();}}}

关键点

关键点解释
链式 cv多访问者串联
兜底 null子访问者可选
多种 visit字段/方法/类

3.2ClassReader:元素

核心源码

/** * ✅ 元素:ClassReader */publicclassClassReader{publicvoidaccept(ClassVisitorclassVisitor,intparsingOptions){// 1️⃣ 读 class 文件// 2️⃣ 调 classVisitor.visit()// 3️⃣ 遍历字段,调 visitField()// 4️⃣ 遍历方法,调 visitMethod()}}

3.3ClassWriter:访问者 + 写

核心源码

/** * ✅ 访问者:ClassWriter(可写) */publicclassClassWriterextendsClassVisitor{publicbyte[]toByteArray(){// ✅ 生成字节码}}

3.4 业务方:自定义 ClassVisitor

/** * ✅ 业务方:自定义 ClassVisitor */publicclassLogClassVisitorextendsClassVisitor{publicLogClassVisitor(ClassVisitorcv){super(Opcodes.ASM9,cv);}@OverridepublicMethodVisitorvisitMethod(intaccess,Stringname,Stringdesc,Stringsignature,String[]exceptions){log.info("Method: {} {}",name,desc);MethodVisitormv=super.visitMethod(access,name,desc,signature,exceptions);returnnewLogMethodVisitor(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.6ClassVisitor的"作者选择"

设计点选择原因
链式 cv多访问者装饰器风格
多种 visit字段/方法/类全覆盖
兜底 null简化灵活
字节码 API低级性能

四、Spring 源码剖析:SpEL 表达式访问者

4.1SpelNode:元素

源码位置

  • Spring Expression 6.1.x
  • spring-expression/src/main/java/org/springframework/expression/spel/ast/SpelNode.java

核心源码

/** * ✅ 元素:SpelNode */publicabstractclassSpelNodeimplements...{/** * ✅ 接受访问者 */publicabstract<T>Taccept(SpelNodeVisitor<T>visitor);}

4.2SpelNodeVisitor:访问者接口

核心源码

/** * ✅ 访问者接口 */publicinterfaceSpelNodeVisitor<T>{/** * ✅ 默认 visit */Tvisit(SpelNodenode);/** * 复合 */TvisitComp(Compnode);/** * 字面量 */TvisitLiteral(Literalnode);/** * 方法/构造器引用 */TvisitMethodRef(MethodReferencenode);/** * 属性引用 */TvisitPropertyRef(PropertyReferencenode);// ... 其他}

4.3SpelNodeImpl:元素实现

核心源码

/** * ✅ 元素实现:Literal */publicclassLiteralextendsSpelNodeImpl{privatefinalObjectvalue;@Overridepublic<T>Taccept(SpelNodeVisitor<T>visitor){// ✅ 调 visitor.visitLiteralreturnvisitor.visitLiteral(this);}}

4.4 业务方:自定义 SpelNodeVisitor

/** * ✅ 业务方:自定义 SpelNodeVisitor */publicclassLoggingSpelNodeVisitorimplementsSpelNodeVisitor<Void>{@OverridepublicVoidvisit(SpelNodenode){log.info("Node: {}",node.getClass().getSimpleName());returnnull;}@OverridepublicVoidvisitLiteral(Literalnode){log.info("Literal: {}",node.getLiteralValue());returnnull;}}

4.5SpelNodeVisitor的"作者选择"

设计点选择原因
多种 visitAST 节点全覆盖灵活
默认 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 业务分析

场景:大白商城自定义规则表达式分析。

完整实现

/** * 借鉴 SpelNodeVisitor:自定义业务 AST 访问者 */publicclassRuleAstVisitorimplementsRuleNodeVisitor<Void>{@OverridepublicVoidvisitExpression(RuleExpressionnode){log.info("Rule: {}",node.getExpression());returnnull;}@OverridepublicVoidvisitCondition(RuleConditionnode){log.info("Condition: field={}, op={}, value={}",node.getField(),node.getOperator(),node.getValue());returnnull;}@OverridepublicVoidvisitAction(RuleActionnode){log.info("Action: type={}, params={}",node.getActionType(),node.getParams());returnnull;}}

6.2 实践 2:业务对象数据采集

场景:大白商城业务对象多维数据采集(统计/导出/分析)。

完整实现

/** * 借鉴 ClassVisitor:业务对象访问者 */publicclassBusinessDataCollectorimplementsBusinessVisitor{privatefinalMap<String,Object>data=newHashMap<>();@Overridepublicvoidvisit(Goodsgoods){data.put("goodsCount",1);data.put("goodsTotal",data.getOrDefault("goodsTotal",0.0)+goods.getPrice().doubleValue());}@Overridepublicvoidvisit(Orderorder){data.put("orderCount",1);data.put("orderTotal",data.getOrDefault("orderTotal",0.0)+order.getTotal().doubleValue());}@Overridepublicvoidvisit(Couponcoupon){data.put("couponCount",1);data.put("couponTotal",data.getOrDefault("couponTotal",0.0)+coupon.getAmount().doubleValue());}publicMap<String,Object>getData(){returndata;}}

6.3 实践 3:多维度分析

场景:大白商城订单多维度分析(销售/利润/用户)。

完整实现

/** * 借鉴 ElementVisitor:多维度分析 */publicclassMultiDimensionAnalyzerimplementsMarketingVisitor{privatedoublesales=0;privatedoubleprofit=0;privateMap<String,Integer>categoryCount=newHashMap<>();@Overridepublicvoidvisit(Goodsgoods){sales+=goods.getPrice().doubleValue();profit+=goods.getPrice().doubleValue()*0.2;}@Overridepublicvoidvisit(Orderorder){sales+=order.getTotal().doubleValue();}@Overridepublicvoidvisit(Couponcoupon){// 优惠券对利润影响profit-=coupon.getAmount().doubleValue()*0.1;}publicvoidprintReport(){log.info("【分析】销售额: {}",sales);log.info("【分析】利润: {}",profit);log.info("【分析】分类: {}",categoryCount);}}

收益

优化点效果
AST 业务分析规则灵活扩展
业务对象采集多维数据
多维度分析一遍采集多维数据

七、本篇小结 + 下一模式预告

7.1 本篇小结(5 个核心要点)

  1. JDK ElementVisitorElement.accept() / ElementVisitor.visitXxx双重分派。
  2. ASM ClassVisitor链式 cv + visitField/visitMethod/visitEnd
  3. Spring SpELSpelNode.accept() / SpelNodeVisitor.visitXxx表达式访问。
  4. 设计哲学元素 + 访问者 + 双重分派
  5. 借鉴价值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 个问题:

  1. 优惠规则表达式(满 100 减 10 / 8 折 / 立减 50)怎么解析执行?
  2. 解释器 vs 策略 / 状态模式怎么区分?
  3. Spring SpEL 怎么用解释器?
  4. Regex / JavaCC 怎么借鉴解释器?

并附完整的优惠规则解释器实战代码(含表达式 + 终结符 + 非终结符 + 单元测试),可直接复制到 IDEA 跑


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

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询