大白话说Java设计模式-43-访问者模式(源码剖析篇)

发布时间:2026/9/13 22:18:18

大白话说Java设计模式-43-访问者模式(源码剖析篇) 大厂规范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.modelElementVisitorElementJDKjavax.toolsDiagnosticListenerDiagnosticASMorg.objectweb.asmClassVisitorClass 字节码ASMorg.objectweb.asmMethodVisitorMethod 字节码Springspring-expressionSpelNodeVisitorSpelNodeJavaParsercom.github.javaparserVoidVisitorAST我们按ElementVisitor → ClassVisitor → SpEL的顺序来剖析。二、JDK 源码剖析ElementVisitor 元素访问者2.1ElementVisitor访问者接口源码位置OpenJDK 17src/java.compiler/share/classes/javax/lang/model/element/ElementVisitor.java核心源码/** * ✅ 访问者接口元素访问者 */publicinterfaceElementVisitorR,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默认实现核心源码/** * ✅ 默认访问者 */publicabstractclassAbstractElementVisitor6R,PimplementsElementVisitorR,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,PRaccept(ElementVisitorR,Pv,Pp);}具体实现TypeElement/** * ✅ TypeElement.accept() */publicinterfaceTypeElementextendsElement,...{OverrideR,PRaccept(ElementVisitorR,Pv,Pp){// ✅ 调 visitor.visitType(this, p)returnv.visitType(this,p);}}关键点关键点解释元素.accept(visitor)接受访问者元素调 visitor.visitXxx双重分派编译期决定类型检查2.4 完整访问者调用链Element element ...; ElementVisitorR, 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.xorg.objectweb.asm.ClassVisitor核心源码/** * ✅ 访问者Class 字节码 */publicabstractclassClassVisitor{protectedfinalintapi;protectedClassVisitorcv;publicClassVisitor(intapi){this(api,null);}publicClassVisitor(intapi,ClassVisitorclassVisitor){this.apiapi;this.cvclassVisitor;}/** * ✅ 访问类 */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);MethodVisitormvsuper.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.xspring-expression/src/main/java/org/springframework/expression/spel/ast/SpelNode.java核心源码/** * ✅ 元素SpelNode */publicabstractclassSpelNodeimplements...{/** * ✅ 接受访问者 */publicabstractTTaccept(SpelNodeVisitorTvisitor);}4.2SpelNodeVisitor访问者接口核心源码/** * ✅ 访问者接口 */publicinterfaceSpelNodeVisitorT{/** * ✅ 默认 visit */Tvisit(SpelNodenode);/** * 复合 */TvisitComp(Compnode);/** * 字面量 */TvisitLiteral(Literalnode);/** * 方法/构造器引用 */TvisitMethodRef(MethodReferencenode);/** * 属性引用 */TvisitPropertyRef(PropertyReferencenode);// ... 其他}4.3SpelNodeImpl元素实现核心源码/** * ✅ 元素实现Literal */publicclassLiteralextendsSpelNodeImpl{privatefinalObjectvalue;OverridepublicTTaccept(SpelNodeVisitorTvisitor){// ✅ 调 visitor.visitLiteralreturnvisitor.visitLiteral(this);}}4.4 业务方自定义 SpelNodeVisitor/** * ✅ 业务方自定义 SpelNodeVisitor */publicclassLoggingSpelNodeVisitorimplementsSpelNodeVisitorVoid{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 种典型用法用法 1AST 遍历 ElementVisitor / SpelNodeVisitor → 多种分析 用法 2字节码增强 ClassVisitor / MethodVisitor → AOP / Lombok 用法 3业务规则 营销规则 / 报表导出 → 数据结构稳定六、借鉴到我们项目3 个抄作业实践6.1 实践 1AST 业务分析场景大白商城自定义规则表达式分析。完整实现/** * 借鉴 SpelNodeVisitor自定义业务 AST 访问者 */publicclassRuleAstVisitorimplementsRuleNodeVisitorVoid{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{privatefinalMapString,ObjectdatanewHashMap();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());}publicMapString,ObjectgetData(){returndata;}}6.3 实践 3多维度分析场景大白商城订单多维度分析销售/利润/用户。完整实现/** * 借鉴 ElementVisitor多维度分析 */publicclassMultiDimensionAnalyzerimplementsMarketingVisitor{privatedoublesales0;privatedoubleprofit0;privateMapString,IntegercategoryCountnewHashMap();Overridepublicvoidvisit(Goodsgoods){salesgoods.getPrice().doubleValue();profitgoods.getPrice().doubleValue()*0.2;}Overridepublicvoidvisit(Orderorder){salesorder.getTotal().doubleValue();}Overridepublicvoidvisit(Couponcoupon){// 优惠券对利润影响profit-coupon.getAmount().doubleValue()*0.1;}publicvoidprintReport(){log.info(【分析】销售额: {},sales);log.info(【分析】利润: {},profit);log.info(【分析】分类: {},categoryCount);}}收益优化点效果AST 业务分析规则灵活扩展业务对象采集多维数据多维度分析一遍采集多维数据七、本篇小结 下一模式预告7.1 本篇小结5 个核心要点JDK ElementVisitorElement.accept() / ElementVisitor.visitXxx双重分派。ASM ClassVisitor链式 cv visitField/visitMethod/visitEnd。Spring SpELSpelNode.accept() / SpelNodeVisitor.visitXxx表达式访问。设计哲学元素 访问者 双重分派。借鉴价值AST 业务分析 / 数据采集 / 多维度分析任何操作多变都用访问者。7.2 一句话总结访问者在源码里不是操作分离的形象化而是元素 访问者 accept/visit三件套的工业级架构。ElementVisitor 遍历 ASTClassVisitor 访问字节码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 跑。觉得对您有帮助麻烦点点关注啦您的关注是我创作的最大动力~
延伸阅读

更多相关文章

2026/9/13 22:18:18

SAP MM采购订单全流程解析:从创建、定价到收货与增强

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/9/13 22:18:18

C++多线程深度指南:线程创建、同步原语与内存模型解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/9/13 23:08:20

Java后端必懂的网络基础:分层、TCP/IP、HTTP与排查实战

1. 从一次前后端联调失败说起:为什么Java后端必须懂网络我印象很深,刚带团队那会儿,有个刚入职的同事调接口,前端说"连不上后端",后端说"我明明启动了",两边都是新手,在群里…

2026/9/13 23:08:20

其域lcc2丢失部分lod/远处显示空洞的解决方式

遇上个麻烦问题,在unity中lcc2模型某些位置离远了就不显示了。推测是只剩下了lod0,lod1-lod10丢失了。折腾了挺久,客服也不太懂,然后发现个简单的解决方式,在LCC Model Editor导出成PLY格式,然后导入再重新导出lcc2,就…

2026/9/13 23:08:20

ntfy 如何配置 Twilio 实现电话呼叫通知并设置呼叫次数上限

ntfy 如何配置 Twilio 实现电话呼叫通知并设置呼叫次数上限 【免费下载链接】ntfy Send push notifications to your phone or desktop using PUT/POST 项目地址: https://gitcode.com/GitHub_Trending/nt/ntfy 如果你的自托管 ntfy 服务器需要在发布消息时直接拨打用户…

2026/9/13 0:01:16

拯救者Y7000黑屏故障排查与维修实战指南

1. 项目概述:一台黑屏的拯救者Y7000,到底卡在哪一步? 联想拯救者Y7000系列笔记本,从2018年第一代搭载i5-8300H开始,到后来的i7-9750H、i7-10750H、i5-11400H,再到2023年款的R7-7840HS,它始终是学…

2026/9/13 0:01:16

拯救者Y7000黑屏故障排查与维修实战指南

1. 项目概述:一台黑屏的拯救者Y7000,到底卡在哪一步? 联想拯救者Y7000系列笔记本,从2018年第一代搭载i5-8300H开始,到后来的i7-9750H、i7-10750H、i5-11400H,再到2023年款的R7-7840HS,它始终是学…

2026/9/12 6:29:36

USB Type-C PCB布局分区设计:电源、高速信号与PD协议全攻略

做硬件这行,Type-C接口算是典型的“看着简单,做起来全坑”的东西。光引脚就24个,高低速信号、电源、控制线全部塞在一个小小的连接器里,如果PCB布局不做规划,打样回来基本就是“插上没反应”、“高速掉线”、“静电一打…

2026/9/12 14:32:17

系统编程学习原型如何补齐稳定性边界

系统编程学习原型如何补齐稳定性边界预算有限时&#xff0c;我先优化明显多余的复制&#xff0c;而不是猜测性地换容器。用借用传递只读数据通常就能减少分配&#xff1a; fn parse(line: &str) -> Result<Item, Error> { /* ... */ }用基准确认热点确实在分配&am…

2026/9/13 11:18:28

雨花区哪家财务公司代理记账比较好?

在雨花区&#xff0c;企业处理财税事务常常面临诸多挑战&#xff0c;选择一家靠谱的财务公司至关重要。湖南巨勤财务管理咨询有限公司就是本地正规实体财税服务机构&#xff0c;深耕本地工商财税行业多年&#xff0c;熟悉当地工商局、税务局最新政策与申报流程。主营公司注册、…

还想了解更多?直接咨询顾问

免费诊断 + 免费方案 + 透明报价。

全国咨询热线400-8866-253
免费获取方案
咨询二维码