)
大厂规范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 跑。觉得对您有帮助麻烦点点关注啦您的关注是我创作的最大动力~