[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"site-settings":3,"\u002Farticles\u002Fdesign-pattern-27":26,"\u002Farticles\u002Fdesign-pattern-27\u002Frelated":43,"\u002Farticles\u002Fdesign-pattern-27\u002Fadjacent":72,"\u002Fcategories":83,"render-design-pattern-27":109},{"code":4,"message":5,"data":6},0,"ok",{"author_city":7,"author_years":8,"site_tagline":9,"site_description":10,"skill_one_desc":11,"author_status":12,"skill_two_title":13,"about_hero_image":9,"skill_one_title":14,"icp":15,"author_role":16,"og_default_image":9,"closing_quote_date":17,"toolkit":18,"skill_two_desc":19,"author_bio":9,"site_footer":9,"site_url":20,"author_tagline":21,"weekly_thought":9,"tagline_line_one":22,"author_bio_two":9,"closing_quote":23,"site_name":24,"tagline_line_two":25,"home_hero_image":9},"西安","10","","花开空白 - 个人博客","熟悉Java、Python、Go等后端语言。","努力工作中","前端开发","后端开发","陕ICP备2021009682号-1","开发者","2022年·秋","Java,JavaScript,TypeScript,Python,Go","熟悉Js、Ts语言以及Vue、uniapp等框架。","https:\u002F\u002Firule.cn","很高兴遇见你。","把复杂的事，","把每次一发布，都当成一次和陌生人的握手。","花开空白","写成简单的故事。",{"code":4,"message":5,"data":27},{"id":28,"slug":29,"title":30,"summary":31,"coverUrl":9,"categoryId":32,"status":33,"topFlag":4,"viewCount":34,"likeCount":4,"wordCount":35,"readingMinutes":36,"publishedAt":37,"contentMd":38,"contentHtml":39,"tags":40,"metaTitle":9,"metaDescription":9,"keywords":9,"canonicalUrl":9,"robots":41,"ogImage":9,"faq":42,"aiSummary":42},"2113934922031828994","design-pattern-27","设计模式修炼之路 ㉗ 业务代表模式——替表示层挡住远程的脏活","替表示层挡住远程脏活、查找+重试+异常翻译一肩挑、重试风暴、外观\u002F代理\u002F业务代表三兄弟对账、Feign 转世","2113897628365230081",1,1811,6147,16,"2025-03-25T21:44:13","> J2EE 模式第二站。\n\n㉖ 的 Controller 直接持有 Catalog、直接调用，丝滑顺畅——因为它们在同一个进程里，一行代码就是一次方法调用的全部成本。可 EJB 时代的真实画风是：**业务层住在另一台服务器上**。表示层每次调用业务，都要先 JNDI 查找服务、再建网络连接、调完还得翻译远程异常、失败了还得琢磨要不要重试……Controller 迟早被这些\"远程破事\"淹没，沦为一个兼职网管。\n\n**在表示层和业务层之间修一条缓冲带：查找、重试、异常翻译这些与业务无关的机械细节，全部由它一个人消化**——业务代表模式，J2EE 八式里的清道夫。\n\n## 一、业务代表模式是什么\n\nJ2EE 蓝皮书里的定义大意：业务代表（Business Delegate）是表示层的一个对象，**它替表示层去查找、访问业务服务，并处理服务定位、远程调用、异常翻译等底层细节**，让表示层只跟业务语义打交道。\n\n三个角色：\n\n\u003Ctable>\u003Cthead>\u003Ctr>\u003Cth>角色\u003C\u002Fth>\u003Cth>干什么\u003C\u002Fth>\u003Cth>本文对应\u003C\u002Fth>\u003C\u002Ftr>\u003C\u002Fthead>\u003Ctbody>\u003Ctr>\u003Ctd>BusinessDelegate（业务代表）\u003C\u002Ftd>\u003Ctd>表示层的业务代理：吸收查找\u002F重试\u002F异常翻译\u003C\u002Ftd>\u003Ctd>OrderDelegate\u003C\u002Ftd>\u003C\u002Ftr>\u003Ctr>\u003Ctd>BusinessLookup（业务查找）\u003C\u002Ftd>\u003Ctd>负责定位业务服务（JNDI 的封装）\u003C\u002Ftd>\u003Ctd>ServiceLookup\u003C\u002Ftd>\u003C\u002Ftr>\u003Ctr>\u003Ctd>BusinessService（业务服务）\u003C\u002Ftd>\u003Ctd>业务层的接口，可能本地可能远程\u003C\u002Ftd>\u003Ctd>OrderService \u002F RemoteOrderService\u003C\u002Ftd>\u003C\u002Ftr>\u003C\u002Ftbody>\u003C\u002Ftable>\n\n一句话理解分工：**表示层说业务话（\"下一单\"），业务代表说技术话（\"查服务、建连接、抖了就重试、远程异常翻成本地异常\"），业务层只管业务。**\n\n注意它不是 GoF 二十三式之一，是 Sun 的 J2EE 模式目录成员——那个年代\"业务逻辑住在远程容器里\"是默认设定，这个模式就是为远程边界而生的。\n\n## 二、优点与缺点\n\n**优点：**\n\n1. **表示层干净**：Controller 里只剩业务调用，没有一行 JNDI、没有一坨重试循环——回看 ㉖，MVC 的职责分离才真正完整。\n2. **机械细节集中一处**：重试几次、异常怎么翻译，改一处全体生效——反面教材里这些代码散在每个 Controller 方法里，改一次全项目搜索。\n3. **异常翻译**：RemoteException、ConnectException 这些技术异常被翻译成业务异常，表示层不用 import 一堆远程包——**远程性被封装掉了**。\n4. **服务解耦**：业务层从远程 EJB 迁回本地 Bean、或换成 HTTP 服务，只改业务代表内部，表示层无感。\n\n**缺点：**\n\n1. **多一层皮**：每个业务服务通常配一个代表类，接口容易沦为业务服务的镜像——为分层而分层。\n2. **单点偏差**：重试、超时策略集中在代表身上，配错了影响所有入口（比如重试放大了下游压力——重试风暴）。\n3. **滞后性陷阱**：代表封装的是\"当时的业务接口\"，业务接口演化时代表要跟着改，慢一步就是两层不同步。\n\n## 三、什么时候用\n\n判断标准：**表示层与业务层之间存在\"脏边界\"——服务查找、远程协议、重试容错、异常翻译，任何一样存在，就值得修缓冲带。**\n\n- **远程业务层**：EJB、RMI、早期的 WebService——本模式出生场景\n- **微服务客户端**：调下游服务的所有机械逻辑（负载均衡、熔断、重试）想从 Controller 里赶走\n- **服务来源会变**：今天本地 Bean、明天远程服务、后天改 HTTP——代表层挡住迁移波\n\n反面对照：**同进程调用**——直接调，加一层纯属自虐；**调用极简单且稳定**——一个工具方法搞定，不必立类；**已有框架代劳**——用了 Feign\u002FDubbo 这类声明式客户端（第五节的当代转世），再手写业务代表就是双层保险过头。\n\n## 四、实现方式：远程下单\n\n### 4.1 反面教材：表示层直面远程服务\n\n先造一个\"远程服务\"——模拟网络抖动（前 N 次调用失败）：\n\n```java\npublic class RemoteOrderService implements OrderService {\n\n    private int pendingFailures;\n\n    public RemoteOrderService(int pendingFailures) {\n        this.pendingFailures = pendingFailures;\n    }\n\n    @Override\n    public String placeOrder(Order order) {\n        System.out.println(\"      [远程调用] placeOrder(\" + order.getId() + \") ... 网络往返中\");\n        if (pendingFailures-- > 0) {\n            throw new IllegalStateException(\"网络抖动：连接超时（RemoteException 一族）\");\n        }\n        return \"订单 \" + order.getId() + \" 已受理（\" + order.getProductName()\n                + \" x\" + order.getQuantity() + \"）\";\n    }\n    \u002F\u002F queryStock 同款，略\n}\n```\n\n然后看 Controller 直面它时的惨状：\n\n```java\npublic class BadController {\n\n    public String submitOrder(Order order) {\n        System.out.println(\"    [JNDI 查找] OrderService ...（每个方法都要查一遍）\");\n        OrderService service = new RemoteOrderService(1);   \u002F\u002F 模拟查找结果\n        for (int attempt = 1; attempt \u003C= 3; attempt++) {\n            try {\n                return \"下单成功：\" + service.placeOrder(order);\n            } catch (IllegalStateException e) {\n                System.out.println(\"    [Controller] 捕获远程异常 → 人肉翻译成业务话术（第 \" + attempt + \" 次失败）\");\n            }\n        }\n        return \"系统繁忙，请稍后再试\";\n    }\n\n    public String checkStock(String productId) {\n        System.out.println(\"    [JNDI 查找] OrderService ...（同款查找，再抄一遍）\");\n        OrderService service = new RemoteOrderService(0);\n        try {\n            return \"库存：\" + service.queryStock(productId);\n        } catch (IllegalStateException e) {\n            return \"系统繁忙，请稍后再试\";\n        }\n    }\n}\n```\n\n运行输出：\n\n```\n========== 反面教材：表示层直面远程服务 ==========\n[表示层] controller.submitOrder(订单 #1001)\n    [JNDI 查找] OrderService ...（每个方法都要查一遍）\n      [远程调用] placeOrder(1001) ... 网络往返中\n    [Controller] 捕获远程异常 → 人肉翻译成业务话术（第 1 次失败）\n      [远程调用] placeOrder(1001) ... 网络往返中\n  结果：下单成功：订单 1001 已受理（机械键盘 x1）\n[表示层] controller.checkStock(\"P101\")\n    [JNDI 查找] OrderService ...（同款查找，再抄一遍）\n      [远程调用] queryStock(P101) ... 网络往返中\n  结果：库存：42\n  —— 查找、重试、异常翻译，每个方法抄一遍，Controller 快成网管了 ——\n```\n\n功能没问题，问题在**重复**：查找、调用、异常翻译这套流程每个方法抄一遍；十个 Controller 方法就是十份拷贝；哪天重试策略从 3 次改成 5 次，全项目正则搜索。\n\n### 4.2 正解：业务代表 + 业务查找\n\n**业务查找**（JNDI 的封装，查到就缓存——这个缓存下一步会长大成 ㉜ 服务定位器模式）：\n\n```java\npublic class ServiceLookup {\n\n    private OrderService orderService;\n\n    public OrderService getOrderService() {\n        if (orderService == null) {\n            System.out.println(\"      [JNDI 查找] OrderService ...（又慢又容易出错的一步）\");\n            orderService = new RemoteOrderService(2);\n        }\n        return orderService;\n    }\n}\n```\n\n**业务代表**：查找、重试、异常翻译一肩挑：\n\n```java\npublic class OrderDelegate {\n\n    private final ServiceLookup lookup = new ServiceLookup();\n\n    \u002F** 表示层只需要这一行 *\u002F\n    public String placeOrder(Order order) {\n        OrderService service = lookup.getOrderService();\n        IllegalStateException last = null;\n        for (int attempt = 1; attempt \u003C= 3; attempt++) {\n            try {\n                return service.placeOrder(order);\n            } catch (IllegalStateException e) {\n                System.out.println(\"      [业务代表] 第 \" + attempt + \" 次尝试失败，自动重试 ...\");\n                last = e;\n            }\n        }\n        \u002F\u002F 技术异常在这里翻译成业务异常，表示层拿不到 RemoteException\n        throw new IllegalStateException(\"远程服务连续失败，请联系管理员（根因：\" + last.getMessage() + \"）\");\n    }\n\n    public int queryStock(String productId) {\n        return lookup.getOrderService().queryStock(productId);\n    }\n}\n```\n\n**表示层的画风**，只剩业务话：\n\n```java\nOrderDelegate delegate = new OrderDelegate();\n\nSystem.out.println(\"  结果：\" + delegate.placeOrder(new Order(\"2001\", \"无线鼠标\", 2)));\nSystem.out.println(\"  结果：库存 \" + delegate.queryStock(\"P101\"));\n```\n\n运行输出：\n\n```\n========== 正解：业务代表替表示层挡枪 ==========\n[表示层] delegate.placeOrder(订单 #2001)  ← 表示层只写这一行\n      [JNDI 查找] OrderService ...（又慢又容易出错的一步）\n      [远程调用] placeOrder(2001) ... 网络往返中\n      [业务代表] 第 1 次尝试失败，自动重试 ...\n      [远程调用] placeOrder(2001) ... 网络往返中\n      [业务代表] 第 2 次尝试失败，自动重试 ...\n      [远程调用] placeOrder(2001) ... 网络往返中\n  结果：订单 2001 已受理（无线鼠标 x2）\n[表示层] delegate.queryStock(\"P101\")  ← 服务引用已缓存，不再 JNDI\n      [远程调用] queryStock(P101) ... 网络往返中\n  结果：库存 42\n  —— 表示层只见业务语义，远程细节全被代表消化 ——\n```\n\n三处对照读出含金量：网络抖了两次，**表示层毫不知情**（重试在代表内部消化）；JNDI 只查了一次（ServiceLookup 缓存了服务引用）；第二次调用**跳过查找直达远程**。反面教材里 Controller 的十份拷贝，在这里是一份。\n\n### 4.3 三处细节\n\n**细节一：异常翻译是本模式的灵魂。** 注意 placeOrder 失败到底时抛的还是 IllegalStateException，但**消息已经变成业务话术**（\"远程服务连续失败，请联系管理员\"）——真实项目里该抛自定义的 OrderServiceException，表示层 catch 它时不需要 import 任何远程包。判断一个业务代表合不合格，就看表示层代码里还有没有远程世界的痕迹。\n\n**细节二：重试要警惕\"重试风暴\"。** 本文演示 3 次重试很温柔，但想想：下游服务过载时，上游 10 个入口每个重试 3 次——流量瞬间放大成 30 倍，下游直接压死。生产级的重试必须配熔断（连续失败就熔一段时间）和退避（每次等待翻倍）——这两个词留给微服务时代，但思想种子在这里。\n\n**细节三：代表接口不必镜像业务接口。** 本文 OrderDelegate 的方法与 OrderService 一一对应，是最简单的形制；实战里代表可以按**表示层的用例**重新组织——比如 `checkout(Cart)` 一个方法背后调远程的下单 + 扣库存 + 发通知三个服务。代表的接口是给表示层用的，不是给业务层的——它是缓冲带，不是传声筒。\n\n## 五、在框架里找它\n\n1. **OpenFeign（Spring Cloud）**：写个接口加 @FeignClient 注解，方法上写路径，框架生成实现——负载均衡、重试、超时全在框架层消化。Controller 注入这个接口直接调，见不到一行 RestTemplate。**声明式客户端就是业务代表思想的当代转世**：接口你定义，脏活框架干。\n\n2. **Dubbo 的集群容错**：failover（失败自动切换重试）、failfast（快速失败）、failsafe（失败忽略）——Dubbo 在消费者侧把\"重试\"做成了可配置策略。本文手写的 for 循环，在生产框架里是 Cluster 层的一个策略类（⑮ 的味道）。\n\n3. **Resilience4j \u002F Hystrix**：熔断、重试、舱壁、降级四件套——专门治理\"远程调用不可靠\"的框架。业务代表模式里代表身上的职责，被拆成了可组合的装饰器（⑩ 的味道）。\n\n4. **EJB 时代原装**：Session Facade + Business Delegate + Service Locator 三件套是当年 Sun 蓝皮书的标准答案——本文 ServiceLookup 里那行缓存，就是 Service Locator 模式的种子，㉜ 正式展开。\n\n## 六、业务代表 vs 外观 vs 代理——中间人三兄弟对账\n\n细心的读者已经发现：业务代表长得像 ⑪ 外观，也像 ⑬ 代理——都是\"中间垫一层\"。三个中间人摊开对账：\n\n\u003Ctable>\u003Cthead>\u003Ctr>\u003Cth>\u003C\u002Fth>\u003Cth>外观（⑪）\u003C\u002Fth>\u003Cth>代理（⑬）\u003C\u002Fth>\u003Cth>业务代表（㉗）\u003C\u002Fth>\u003C\u002Ftr>\u003C\u002Fthead>\u003Ctbody>\u003Ctr>\u003Ctd>出发点\u003C\u002Ftd>\u003Ctd>子系统调用太碎\u003C\u002Ftd>\u003Ctd>对目标的访问需要管控\u003C\u002Ftd>\u003Ctd>表示层与业务层之间边界太脏\u003C\u002Ftd>\u003C\u002Ftr>\u003Ctr>\u003Ctd>接口形态\u003C\u002Ftd>\u003Ctd>通常重新设计、更粗\u003C\u002Ftd>\u003Ctd>与目标**一模一样**，透明替换\u003C\u002Ftd>\u003Ctd>按表示层需要设计，可重组\u003C\u002Ftd>\u003C\u002Ftr>\u003Ctr>\u003Ctd>典型行为\u003C\u002Ftd>\u003Ctd>多个调用收口成一个\u003C\u002Ftd>\u003Ctd>延迟加载、权限检查、缓存\u003C\u002Ftd>\u003Ctd>服务查找、重试容错、异常翻译\u003C\u002Ftd>\u003C\u002Ftr>\u003Ctr>\u003Ctd>出身\u003C\u002Ftd>\u003Ctd>GoF 1994\u003C\u002Ftd>\u003Ctd>GoF 1994\u003C\u002Ftd>\u003Ctd>J2EE 蓝皮书\u003C\u002Ftd>\u003C\u002Ftr>\u003Ctr>\u003Ctd>一句话\u003C\u002Ftd>\u003Ctd>替你**少打电话**\u003C\u002Ftd>\u003Ctd>替你**管控电话**\u003C\u002Ftd>\u003Ctd>替你**接通电话并处理杂音**\u003C\u002Ftd>\u003C\u002Ftr>\u003C\u002Ftbody>\u003C\u002Ftable>\n\n三者可以叠加：业务代表内部用代理拿远程引用、用外观收口多个服务调用——中间人不是排他的，**按\"你替我挡的是什么\"来选**：挡复杂度选外观，挡访问选代理，挡远程的脏选业务代表。\n\n## 写在最后\n\n- 业务代表 = 表示层与业务层之间的缓冲带：查找、重试、异常翻译一肩挑\n- 表示层只见业务语义——判断合格的标准是它代码里没有远程世界的痕迹\n- 重试虽好，当心重试风暴：生产级必须配熔断与退避\n- 代表接口按表示层用例设计，是缓冲带不是传声筒\n- 中间人三兄弟：外观挡复杂度、代理挡访问、业务代表挡脏边界——可叠加，按需选\n\n㉗ 的订单实体只有三个字段，一次远程调用就传输完毕——这是实体世界的\"好养型\"。可真实的领域模型里，\"客户\"这种实体由地址、信用、联系方式、订单历史一堆小对象组成；远程场景下，每个小对象都可能是独立的网络往返。EJB 的答案：把一组依赖对象捆成一个**粗粒度实体**统一管理——组合实体模式，下一篇见。\n","\u003Cblockquote>\n \u003Cp>J2EE 模式第二站。\u003C\u002Fp>\n\u003C\u002Fblockquote>\n\u003Cp>㉖ 的 Controller 直接持有 Catalog、直接调用，丝滑顺畅——因为它们在同一个进程里，一行代码就是一次方法调用的全部成本。可 EJB 时代的真实画风是：\u003Cstrong>业务层住在另一台服务器上\u003C\u002Fstrong>。表示层每次调用业务，都要先 JNDI 查找服务、再建网络连接、调完还得翻译远程异常、失败了还得琢磨要不要重试……Controller 迟早被这些\"远程破事\"淹没，沦为一个兼职网管。\u003C\u002Fp>\n\u003Cp>\u003Cstrong>在表示层和业务层之间修一条缓冲带：查找、重试、异常翻译这些与业务无关的机械细节，全部由它一个人消化\u003C\u002Fstrong>——业务代表模式，J2EE 八式里的清道夫。\u003C\u002Fp>\n\u003Ch2>一、业务代表模式是什么\u003C\u002Fh2>\n\u003Cp>J2EE 蓝皮书里的定义大意：业务代表（Business Delegate）是表示层的一个对象，\u003Cstrong>它替表示层去查找、访问业务服务，并处理服务定位、远程调用、异常翻译等底层细节\u003C\u002Fstrong>，让表示层只跟业务语义打交道。\u003C\u002Fp>\n\u003Cp>三个角色：\u003C\u002Fp>\n\u003Ctable>\n \u003Cthead>\n  \u003Ctr>\n   \u003Cth>角色\u003C\u002Fth>\n   \u003Cth>干什么\u003C\u002Fth>\n   \u003Cth>本文对应\u003C\u002Fth>\n  \u003C\u002Ftr>\n \u003C\u002Fthead>\n \u003Ctbody>\n  \u003Ctr>\n   \u003Ctd>BusinessDelegate（业务代表）\u003C\u002Ftd>\n   \u003Ctd>表示层的业务代理：吸收查找\u002F重试\u002F异常翻译\u003C\u002Ftd>\n   \u003Ctd>OrderDelegate\u003C\u002Ftd>\n  \u003C\u002Ftr>\n  \u003Ctr>\n   \u003Ctd>BusinessLookup（业务查找）\u003C\u002Ftd>\n   \u003Ctd>负责定位业务服务（JNDI 的封装）\u003C\u002Ftd>\n   \u003Ctd>ServiceLookup\u003C\u002Ftd>\n  \u003C\u002Ftr>\n  \u003Ctr>\n   \u003Ctd>BusinessService（业务服务）\u003C\u002Ftd>\n   \u003Ctd>业务层的接口，可能本地可能远程\u003C\u002Ftd>\n   \u003Ctd>OrderService \u002F RemoteOrderService\u003C\u002Ftd>\n  \u003C\u002Ftr>\n \u003C\u002Ftbody>\n\u003C\u002Ftable>\n\u003Cp>一句话理解分工：\u003Cstrong>表示层说业务话（\"下一单\"），业务代表说技术话（\"查服务、建连接、抖了就重试、远程异常翻成本地异常\"），业务层只管业务。\u003C\u002Fstrong>\u003C\u002Fp>\n\u003Cp>注意它不是 GoF 二十三式之一，是 Sun 的 J2EE 模式目录成员——那个年代\"业务逻辑住在远程容器里\"是默认设定，这个模式就是为远程边界而生的。\u003C\u002Fp>\n\u003Ch2>二、优点与缺点\u003C\u002Fh2>\n\u003Cp>\u003Cstrong>优点：\u003C\u002Fstrong>\u003C\u002Fp>\n\u003Col>\n \u003Cli>\u003Cstrong>表示层干净\u003C\u002Fstrong>：Controller 里只剩业务调用，没有一行 JNDI、没有一坨重试循环——回看 ㉖，MVC 的职责分离才真正完整。\u003C\u002Fli>\n \u003Cli>\u003Cstrong>机械细节集中一处\u003C\u002Fstrong>：重试几次、异常怎么翻译，改一处全体生效——反面教材里这些代码散在每个 Controller 方法里，改一次全项目搜索。\u003C\u002Fli>\n \u003Cli>\u003Cstrong>异常翻译\u003C\u002Fstrong>：RemoteException、ConnectException 这些技术异常被翻译成业务异常，表示层不用 import 一堆远程包——\u003Cstrong>远程性被封装掉了\u003C\u002Fstrong>。\u003C\u002Fli>\n \u003Cli>\u003Cstrong>服务解耦\u003C\u002Fstrong>：业务层从远程 EJB 迁回本地 Bean、或换成 HTTP 服务，只改业务代表内部，表示层无感。\u003C\u002Fli>\n\u003C\u002Fol>\n\u003Cp>\u003Cstrong>缺点：\u003C\u002Fstrong>\u003C\u002Fp>\n\u003Col>\n \u003Cli>\u003Cstrong>多一层皮\u003C\u002Fstrong>：每个业务服务通常配一个代表类，接口容易沦为业务服务的镜像——为分层而分层。\u003C\u002Fli>\n \u003Cli>\u003Cstrong>单点偏差\u003C\u002Fstrong>：重试、超时策略集中在代表身上，配错了影响所有入口（比如重试放大了下游压力——重试风暴）。\u003C\u002Fli>\n \u003Cli>\u003Cstrong>滞后性陷阱\u003C\u002Fstrong>：代表封装的是\"当时的业务接口\"，业务接口演化时代表要跟着改，慢一步就是两层不同步。\u003C\u002Fli>\n\u003C\u002Fol>\n\u003Ch2>三、什么时候用\u003C\u002Fh2>\n\u003Cp>判断标准：\u003Cstrong>表示层与业务层之间存在\"脏边界\"——服务查找、远程协议、重试容错、异常翻译，任何一样存在，就值得修缓冲带。\u003C\u002Fstrong>\u003C\u002Fp>\n\u003Cul>\n \u003Cli>\u003Cstrong>远程业务层\u003C\u002Fstrong>：EJB、RMI、早期的 WebService——本模式出生场景\u003C\u002Fli>\n \u003Cli>\u003Cstrong>微服务客户端\u003C\u002Fstrong>：调下游服务的所有机械逻辑（负载均衡、熔断、重试）想从 Controller 里赶走\u003C\u002Fli>\n \u003Cli>\u003Cstrong>服务来源会变\u003C\u002Fstrong>：今天本地 Bean、明天远程服务、后天改 HTTP——代表层挡住迁移波\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Cp>反面对照：\u003Cstrong>同进程调用\u003C\u002Fstrong>——直接调，加一层纯属自虐；\u003Cstrong>调用极简单且稳定\u003C\u002Fstrong>——一个工具方法搞定，不必立类；\u003Cstrong>已有框架代劳\u003C\u002Fstrong>——用了 Feign\u002FDubbo 这类声明式客户端（第五节的当代转世），再手写业务代表就是双层保险过头。\u003C\u002Fp>\n\u003Ch2>四、实现方式：远程下单\u003C\u002Fh2>\n\u003Ch3>4.1 反面教材：表示层直面远程服务\u003C\u002Fh3>\n\u003Cp>先造一个\"远程服务\"——模拟网络抖动（前 N 次调用失败）：\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-java\">public class RemoteOrderService implements OrderService {\n\n    private int pendingFailures;\n\n    public RemoteOrderService(int pendingFailures) {\n        this.pendingFailures = pendingFailures;\n    }\n\n    @Override\n    public String placeOrder(Order order) {\n        System.out.println(\"      [远程调用] placeOrder(\" + order.getId() + \") ... 网络往返中\");\n        if (pendingFailures-- &gt; 0) {\n            throw new IllegalStateException(\"网络抖动：连接超时（RemoteException 一族）\");\n        }\n        return \"订单 \" + order.getId() + \" 已受理（\" + order.getProductName()\n                + \" x\" + order.getQuantity() + \"）\";\n    }\n    \u002F\u002F queryStock 同款，略\n}\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>然后看 Controller 直面它时的惨状：\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-java\">public class BadController {\n\n    public String submitOrder(Order order) {\n        System.out.println(\"    [JNDI 查找] OrderService ...（每个方法都要查一遍）\");\n        OrderService service = new RemoteOrderService(1);   \u002F\u002F 模拟查找结果\n        for (int attempt = 1; attempt &lt;= 3; attempt++) {\n            try {\n                return \"下单成功：\" + service.placeOrder(order);\n            } catch (IllegalStateException e) {\n                System.out.println(\"    [Controller] 捕获远程异常 → 人肉翻译成业务话术（第 \" + attempt + \" 次失败）\");\n            }\n        }\n        return \"系统繁忙，请稍后再试\";\n    }\n\n    public String checkStock(String productId) {\n        System.out.println(\"    [JNDI 查找] OrderService ...（同款查找，再抄一遍）\");\n        OrderService service = new RemoteOrderService(0);\n        try {\n            return \"库存：\" + service.queryStock(productId);\n        } catch (IllegalStateException e) {\n            return \"系统繁忙，请稍后再试\";\n        }\n    }\n}\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>运行输出：\u003C\u002Fp>\n\u003Cpre>\u003Ccode>========== 反面教材：表示层直面远程服务 ==========\n[表示层] controller.submitOrder(订单 #1001)\n    [JNDI 查找] OrderService ...（每个方法都要查一遍）\n      [远程调用] placeOrder(1001) ... 网络往返中\n    [Controller] 捕获远程异常 → 人肉翻译成业务话术（第 1 次失败）\n      [远程调用] placeOrder(1001) ... 网络往返中\n  结果：下单成功：订单 1001 已受理（机械键盘 x1）\n[表示层] controller.checkStock(\"P101\")\n    [JNDI 查找] OrderService ...（同款查找，再抄一遍）\n      [远程调用] queryStock(P101) ... 网络往返中\n  结果：库存：42\n  —— 查找、重试、异常翻译，每个方法抄一遍，Controller 快成网管了 ——\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>功能没问题，问题在\u003Cstrong>重复\u003C\u002Fstrong>：查找、调用、异常翻译这套流程每个方法抄一遍；十个 Controller 方法就是十份拷贝；哪天重试策略从 3 次改成 5 次，全项目正则搜索。\u003C\u002Fp>\n\u003Ch3>4.2 正解：业务代表 + 业务查找\u003C\u002Fh3>\n\u003Cp>\u003Cstrong>业务查找\u003C\u002Fstrong>（JNDI 的封装，查到就缓存——这个缓存下一步会长大成 ㉜ 服务定位器模式）：\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-java\">public class ServiceLookup {\n\n    private OrderService orderService;\n\n    public OrderService getOrderService() {\n        if (orderService == null) {\n            System.out.println(\"      [JNDI 查找] OrderService ...（又慢又容易出错的一步）\");\n            orderService = new RemoteOrderService(2);\n        }\n        return orderService;\n    }\n}\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>\u003Cstrong>业务代表\u003C\u002Fstrong>：查找、重试、异常翻译一肩挑：\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-java\">public class OrderDelegate {\n\n    private final ServiceLookup lookup = new ServiceLookup();\n\n    \u002F** 表示层只需要这一行 *\u002F\n    public String placeOrder(Order order) {\n        OrderService service = lookup.getOrderService();\n        IllegalStateException last = null;\n        for (int attempt = 1; attempt &lt;= 3; attempt++) {\n            try {\n                return service.placeOrder(order);\n            } catch (IllegalStateException e) {\n                System.out.println(\"      [业务代表] 第 \" + attempt + \" 次尝试失败，自动重试 ...\");\n                last = e;\n            }\n        }\n        \u002F\u002F 技术异常在这里翻译成业务异常，表示层拿不到 RemoteException\n        throw new IllegalStateException(\"远程服务连续失败，请联系管理员（根因：\" + last.getMessage() + \"）\");\n    }\n\n    public int queryStock(String productId) {\n        return lookup.getOrderService().queryStock(productId);\n    }\n}\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>\u003Cstrong>表示层的画风\u003C\u002Fstrong>，只剩业务话：\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-java\">OrderDelegate delegate = new OrderDelegate();\n\nSystem.out.println(\"  结果：\" + delegate.placeOrder(new Order(\"2001\", \"无线鼠标\", 2)));\nSystem.out.println(\"  结果：库存 \" + delegate.queryStock(\"P101\"));\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>运行输出：\u003C\u002Fp>\n\u003Cpre>\u003Ccode>========== 正解：业务代表替表示层挡枪 ==========\n[表示层] delegate.placeOrder(订单 #2001)  ← 表示层只写这一行\n      [JNDI 查找] OrderService ...（又慢又容易出错的一步）\n      [远程调用] placeOrder(2001) ... 网络往返中\n      [业务代表] 第 1 次尝试失败，自动重试 ...\n      [远程调用] placeOrder(2001) ... 网络往返中\n      [业务代表] 第 2 次尝试失败，自动重试 ...\n      [远程调用] placeOrder(2001) ... 网络往返中\n  结果：订单 2001 已受理（无线鼠标 x2）\n[表示层] delegate.queryStock(\"P101\")  ← 服务引用已缓存，不再 JNDI\n      [远程调用] queryStock(P101) ... 网络往返中\n  结果：库存 42\n  —— 表示层只见业务语义，远程细节全被代表消化 ——\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>三处对照读出含金量：网络抖了两次，\u003Cstrong>表示层毫不知情\u003C\u002Fstrong>（重试在代表内部消化）；JNDI 只查了一次（ServiceLookup 缓存了服务引用）；第二次调用\u003Cstrong>跳过查找直达远程\u003C\u002Fstrong>。反面教材里 Controller 的十份拷贝，在这里是一份。\u003C\u002Fp>\n\u003Ch3>4.3 三处细节\u003C\u002Fh3>\n\u003Cp>\u003Cstrong>细节一：异常翻译是本模式的灵魂。\u003C\u002Fstrong> 注意 placeOrder 失败到底时抛的还是 IllegalStateException，但\u003Cstrong>消息已经变成业务话术\u003C\u002Fstrong>（\"远程服务连续失败，请联系管理员\"）——真实项目里该抛自定义的 OrderServiceException，表示层 catch 它时不需要 import 任何远程包。判断一个业务代表合不合格，就看表示层代码里还有没有远程世界的痕迹。\u003C\u002Fp>\n\u003Cp>\u003Cstrong>细节二：重试要警惕\"重试风暴\"。\u003C\u002Fstrong> 本文演示 3 次重试很温柔，但想想：下游服务过载时，上游 10 个入口每个重试 3 次——流量瞬间放大成 30 倍，下游直接压死。生产级的重试必须配熔断（连续失败就熔一段时间）和退避（每次等待翻倍）——这两个词留给微服务时代，但思想种子在这里。\u003C\u002Fp>\n\u003Cp>\u003Cstrong>细节三：代表接口不必镜像业务接口。\u003C\u002Fstrong> 本文 OrderDelegate 的方法与 OrderService 一一对应，是最简单的形制；实战里代表可以按\u003Cstrong>表示层的用例\u003C\u002Fstrong>重新组织——比如 \u003Ccode>checkout(Cart)\u003C\u002Fcode> 一个方法背后调远程的下单 + 扣库存 + 发通知三个服务。代表的接口是给表示层用的，不是给业务层的——它是缓冲带，不是传声筒。\u003C\u002Fp>\n\u003Ch2>五、在框架里找它\u003C\u002Fh2>\n\u003Col>\n \u003Cli>\n  \u003Cp>\u003Cstrong>OpenFeign（Spring Cloud）\u003C\u002Fstrong>：写个接口加 @FeignClient 注解，方法上写路径，框架生成实现——负载均衡、重试、超时全在框架层消化。Controller 注入这个接口直接调，见不到一行 RestTemplate。\u003Cstrong>声明式客户端就是业务代表思想的当代转世\u003C\u002Fstrong>：接口你定义，脏活框架干。\u003C\u002Fp>\u003C\u002Fli>\n \u003Cli>\n  \u003Cp>\u003Cstrong>Dubbo 的集群容错\u003C\u002Fstrong>：failover（失败自动切换重试）、failfast（快速失败）、failsafe（失败忽略）——Dubbo 在消费者侧把\"重试\"做成了可配置策略。本文手写的 for 循环，在生产框架里是 Cluster 层的一个策略类（⑮ 的味道）。\u003C\u002Fp>\u003C\u002Fli>\n \u003Cli>\n  \u003Cp>\u003Cstrong>Resilience4j \u002F Hystrix\u003C\u002Fstrong>：熔断、重试、舱壁、降级四件套——专门治理\"远程调用不可靠\"的框架。业务代表模式里代表身上的职责，被拆成了可组合的装饰器（⑩ 的味道）。\u003C\u002Fp>\u003C\u002Fli>\n \u003Cli>\n  \u003Cp>\u003Cstrong>EJB 时代原装\u003C\u002Fstrong>：Session Facade + Business Delegate + Service Locator 三件套是当年 Sun 蓝皮书的标准答案——本文 ServiceLookup 里那行缓存，就是 Service Locator 模式的种子，㉜ 正式展开。\u003C\u002Fp>\u003C\u002Fli>\n\u003C\u002Fol>\n\u003Ch2>六、业务代表 vs 外观 vs 代理——中间人三兄弟对账\u003C\u002Fh2>\n\u003Cp>细心的读者已经发现：业务代表长得像 ⑪ 外观，也像 ⑬ 代理——都是\"中间垫一层\"。三个中间人摊开对账：\u003C\u002Fp>\n\u003Ctable>\n \u003Cthead>\n  \u003Ctr>\n   \u003Cth>\u003C\u002Fth>\n   \u003Cth>外观（⑪）\u003C\u002Fth>\n   \u003Cth>代理（⑬）\u003C\u002Fth>\n   \u003Cth>业务代表（㉗）\u003C\u002Fth>\n  \u003C\u002Ftr>\n \u003C\u002Fthead>\n \u003Ctbody>\n  \u003Ctr>\n   \u003Ctd>出发点\u003C\u002Ftd>\n   \u003Ctd>子系统调用太碎\u003C\u002Ftd>\n   \u003Ctd>对目标的访问需要管控\u003C\u002Ftd>\n   \u003Ctd>表示层与业务层之间边界太脏\u003C\u002Ftd>\n  \u003C\u002Ftr>\n  \u003Ctr>\n   \u003Ctd>接口形态\u003C\u002Ftd>\n   \u003Ctd>通常重新设计、更粗\u003C\u002Ftd>\n   \u003Ctd>与目标**一模一样**，透明替换\u003C\u002Ftd>\n   \u003Ctd>按表示层需要设计，可重组\u003C\u002Ftd>\n  \u003C\u002Ftr>\n  \u003Ctr>\n   \u003Ctd>典型行为\u003C\u002Ftd>\n   \u003Ctd>多个调用收口成一个\u003C\u002Ftd>\n   \u003Ctd>延迟加载、权限检查、缓存\u003C\u002Ftd>\n   \u003Ctd>服务查找、重试容错、异常翻译\u003C\u002Ftd>\n  \u003C\u002Ftr>\n  \u003Ctr>\n   \u003Ctd>出身\u003C\u002Ftd>\n   \u003Ctd>GoF 1994\u003C\u002Ftd>\n   \u003Ctd>GoF 1994\u003C\u002Ftd>\n   \u003Ctd>J2EE 蓝皮书\u003C\u002Ftd>\n  \u003C\u002Ftr>\n  \u003Ctr>\n   \u003Ctd>一句话\u003C\u002Ftd>\n   \u003Ctd>替你**少打电话**\u003C\u002Ftd>\n   \u003Ctd>替你**管控电话**\u003C\u002Ftd>\n   \u003Ctd>替你**接通电话并处理杂音**\u003C\u002Ftd>\n  \u003C\u002Ftr>\n \u003C\u002Ftbody>\n\u003C\u002Ftable>\n\u003Cp>三者可以叠加：业务代表内部用代理拿远程引用、用外观收口多个服务调用——中间人不是排他的，\u003Cstrong>按\"你替我挡的是什么\"来选\u003C\u002Fstrong>：挡复杂度选外观，挡访问选代理，挡远程的脏选业务代表。\u003C\u002Fp>\n\u003Ch2>写在最后\u003C\u002Fh2>\n\u003Cul>\n \u003Cli>业务代表 = 表示层与业务层之间的缓冲带：查找、重试、异常翻译一肩挑\u003C\u002Fli>\n \u003Cli>表示层只见业务语义——判断合格的标准是它代码里没有远程世界的痕迹\u003C\u002Fli>\n \u003Cli>重试虽好，当心重试风暴：生产级必须配熔断与退避\u003C\u002Fli>\n \u003Cli>代表接口按表示层用例设计，是缓冲带不是传声筒\u003C\u002Fli>\n \u003Cli>中间人三兄弟：外观挡复杂度、代理挡访问、业务代表挡脏边界——可叠加，按需选\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Cp>㉗ 的订单实体只有三个字段，一次远程调用就传输完毕——这是实体世界的\"好养型\"。可真实的领域模型里，\"客户\"这种实体由地址、信用、联系方式、订单历史一堆小对象组成；远程场景下，每个小对象都可能是独立的网络往返。EJB 的答案：把一组依赖对象捆成一个\u003Cstrong>粗粒度实体\u003C\u002Fstrong>统一管理——组合实体模式，下一篇见。\u003C\u002Fp>",[],"index,follow",null,{"code":4,"message":5,"data":44},[45,54,63],{"id":46,"slug":47,"title":48,"summary":49,"coverUrl":9,"categoryId":32,"status":33,"topFlag":4,"viewCount":50,"likeCount":4,"wordCount":51,"readingMinutes":52,"publishedAt":53},"2113935261715927041","design-pattern-32","设计模式修炼之路 ㉜ 服务定位器模式——服务只找一次，之后全靠缓存","查找与缓存一处收口、注册中心只被打扰 N 次、缓存的是服务不是结果、种子从 ㉗ 长成 static 注册台、字符串名字没有编译器保护、与业务代表对账",1926,7172,18,"2025-06-20T10:10:19",{"id":55,"slug":56,"title":57,"summary":58,"coverUrl":9,"categoryId":32,"status":33,"topFlag":4,"viewCount":59,"likeCount":4,"wordCount":60,"readingMinutes":61,"publishedAt":62},"2113935321048551425","design-pattern-33","设计模式修炼之路 ㉝ 传输对象模式——一趟网络，带走整份数据","J2EE 模式第八站，也是 J2EE 八式的收官一站。",2516,8059,21,"2025-07-09T21:16:41",{"id":64,"slug":65,"title":66,"summary":67,"coverUrl":9,"categoryId":32,"status":33,"topFlag":4,"viewCount":68,"likeCount":4,"wordCount":69,"readingMinutes":70,"publishedAt":71},"2113935015761940481","design-pattern-28","设计模式修炼之路 ㉘ 组合实体模式——一窝小对象，一次网络往返","一窝小对象一次网络往返、读放大与 N+1、一次提交就是事务边界、依赖对象没有远程身份、JPA 聚合根转世",3270,7543,19,"2025-04-07T11:55:37",{"code":4,"message":5,"data":73},{"prev":74,"next":82},{"id":75,"slug":76,"title":77,"summary":78,"coverUrl":9,"categoryId":32,"status":33,"topFlag":4,"viewCount":79,"likeCount":4,"wordCount":80,"readingMinutes":61,"publishedAt":81},"2113935519934058498","design-pattern-26","设计模式修炼之路 ㉖ MVC 模式——改界面不动业务，改业务不动界面","比 GoF 早十五年、依赖方向定生死、业务逻辑永远住 Model、换视图零成本、MVC→MVP→MVVM 进化树",2596,8085,"2025-02-16T16:19:17",{"id":64,"slug":65,"title":66,"summary":67,"coverUrl":9,"categoryId":32,"status":33,"topFlag":4,"viewCount":68,"likeCount":4,"wordCount":69,"readingMinutes":70,"publishedAt":71},{"code":4,"message":5,"data":84},[85,91,96,102],{"id":86,"name":87,"slug":88,"description":89,"sortOrder":4,"articleCount":90},"1946000000000000001","antlr4","tech","编程与技术笔记","13",{"id":32,"name":92,"slug":93,"description":94,"sortOrder":4,"articleCount":95},"设计模式","design-pattern","设计模式系列","34",{"id":97,"name":98,"slug":99,"description":100,"sortOrder":33,"articleCount":101},"1946000000000000002","随笔","essay","生活与思考","0",{"id":103,"name":104,"slug":105,"description":106,"sortOrder":107,"articleCount":108},"2105200093764698113","工具","tools","好用的工具推荐",2,"1",{"html":110,"toc":111},"\u003Cblockquote>\n \u003Cp>J2EE 模式第二站。\u003C\u002Fp>\n\u003C\u002Fblockquote>\n\u003Cp>㉖ 的 Controller 直接持有 Catalog、直接调用，丝滑顺畅——因为它们在同一个进程里，一行代码就是一次方法调用的全部成本。可 EJB 时代的真实画风是：\u003Cstrong>业务层住在另一台服务器上\u003C\u002Fstrong>。表示层每次调用业务，都要先 JNDI 查找服务、再建网络连接、调完还得翻译远程异常、失败了还得琢磨要不要重试……Controller 迟早被这些\"远程破事\"淹没，沦为一个兼职网管。\u003C\u002Fp>\n\u003Cp>\u003Cstrong>在表示层和业务层之间修一条缓冲带：查找、重试、异常翻译这些与业务无关的机械细节，全部由它一个人消化\u003C\u002Fstrong>——业务代表模式，J2EE 八式里的清道夫。\u003C\u002Fp>\n\u003Ch2 id=\"toc-1\">一、业务代表模式是什么\u003C\u002Fh2>\n\u003Cp>J2EE 蓝皮书里的定义大意：业务代表（Business Delegate）是表示层的一个对象，\u003Cstrong>它替表示层去查找、访问业务服务，并处理服务定位、远程调用、异常翻译等底层细节\u003C\u002Fstrong>，让表示层只跟业务语义打交道。\u003C\u002Fp>\n\u003Cp>三个角色：\u003C\u002Fp>\n\u003Ctable>\n \u003Cthead>\n  \u003Ctr>\n   \u003Cth>角色\u003C\u002Fth>\n   \u003Cth>干什么\u003C\u002Fth>\n   \u003Cth>本文对应\u003C\u002Fth>\n  \u003C\u002Ftr>\n \u003C\u002Fthead>\n \u003Ctbody>\n  \u003Ctr>\n   \u003Ctd>BusinessDelegate（业务代表）\u003C\u002Ftd>\n   \u003Ctd>表示层的业务代理：吸收查找\u002F重试\u002F异常翻译\u003C\u002Ftd>\n   \u003Ctd>OrderDelegate\u003C\u002Ftd>\n  \u003C\u002Ftr>\n  \u003Ctr>\n   \u003Ctd>BusinessLookup（业务查找）\u003C\u002Ftd>\n   \u003Ctd>负责定位业务服务（JNDI 的封装）\u003C\u002Ftd>\n   \u003Ctd>ServiceLookup\u003C\u002Ftd>\n  \u003C\u002Ftr>\n  \u003Ctr>\n   \u003Ctd>BusinessService（业务服务）\u003C\u002Ftd>\n   \u003Ctd>业务层的接口，可能本地可能远程\u003C\u002Ftd>\n   \u003Ctd>OrderService \u002F RemoteOrderService\u003C\u002Ftd>\n  \u003C\u002Ftr>\n \u003C\u002Ftbody>\n\u003C\u002Ftable>\n\u003Cp>一句话理解分工：\u003Cstrong>表示层说业务话（\"下一单\"），业务代表说技术话（\"查服务、建连接、抖了就重试、远程异常翻成本地异常\"），业务层只管业务。\u003C\u002Fstrong>\u003C\u002Fp>\n\u003Cp>注意它不是 GoF 二十三式之一，是 Sun 的 J2EE 模式目录成员——那个年代\"业务逻辑住在远程容器里\"是默认设定，这个模式就是为远程边界而生的。\u003C\u002Fp>\n\u003Ch2 id=\"toc-2\">二、优点与缺点\u003C\u002Fh2>\n\u003Cp>\u003Cstrong>优点：\u003C\u002Fstrong>\u003C\u002Fp>\n\u003Col>\n \u003Cli>\u003Cstrong>表示层干净\u003C\u002Fstrong>：Controller 里只剩业务调用，没有一行 JNDI、没有一坨重试循环——回看 ㉖，MVC 的职责分离才真正完整。\u003C\u002Fli>\n \u003Cli>\u003Cstrong>机械细节集中一处\u003C\u002Fstrong>：重试几次、异常怎么翻译，改一处全体生效——反面教材里这些代码散在每个 Controller 方法里，改一次全项目搜索。\u003C\u002Fli>\n \u003Cli>\u003Cstrong>异常翻译\u003C\u002Fstrong>：RemoteException、ConnectException 这些技术异常被翻译成业务异常，表示层不用 import 一堆远程包——\u003Cstrong>远程性被封装掉了\u003C\u002Fstrong>。\u003C\u002Fli>\n \u003Cli>\u003Cstrong>服务解耦\u003C\u002Fstrong>：业务层从远程 EJB 迁回本地 Bean、或换成 HTTP 服务，只改业务代表内部，表示层无感。\u003C\u002Fli>\n\u003C\u002Fol>\n\u003Cp>\u003Cstrong>缺点：\u003C\u002Fstrong>\u003C\u002Fp>\n\u003Col>\n \u003Cli>\u003Cstrong>多一层皮\u003C\u002Fstrong>：每个业务服务通常配一个代表类，接口容易沦为业务服务的镜像——为分层而分层。\u003C\u002Fli>\n \u003Cli>\u003Cstrong>单点偏差\u003C\u002Fstrong>：重试、超时策略集中在代表身上，配错了影响所有入口（比如重试放大了下游压力——重试风暴）。\u003C\u002Fli>\n \u003Cli>\u003Cstrong>滞后性陷阱\u003C\u002Fstrong>：代表封装的是\"当时的业务接口\"，业务接口演化时代表要跟着改，慢一步就是两层不同步。\u003C\u002Fli>\n\u003C\u002Fol>\n\u003Ch2 id=\"toc-3\">三、什么时候用\u003C\u002Fh2>\n\u003Cp>判断标准：\u003Cstrong>表示层与业务层之间存在\"脏边界\"——服务查找、远程协议、重试容错、异常翻译，任何一样存在，就值得修缓冲带。\u003C\u002Fstrong>\u003C\u002Fp>\n\u003Cul>\n \u003Cli>\u003Cstrong>远程业务层\u003C\u002Fstrong>：EJB、RMI、早期的 WebService——本模式出生场景\u003C\u002Fli>\n \u003Cli>\u003Cstrong>微服务客户端\u003C\u002Fstrong>：调下游服务的所有机械逻辑（负载均衡、熔断、重试）想从 Controller 里赶走\u003C\u002Fli>\n \u003Cli>\u003Cstrong>服务来源会变\u003C\u002Fstrong>：今天本地 Bean、明天远程服务、后天改 HTTP——代表层挡住迁移波\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Cp>反面对照：\u003Cstrong>同进程调用\u003C\u002Fstrong>——直接调，加一层纯属自虐；\u003Cstrong>调用极简单且稳定\u003C\u002Fstrong>——一个工具方法搞定，不必立类；\u003Cstrong>已有框架代劳\u003C\u002Fstrong>——用了 Feign\u002FDubbo 这类声明式客户端（第五节的当代转世），再手写业务代表就是双层保险过头。\u003C\u002Fp>\n\u003Ch2 id=\"toc-4\">四、实现方式：远程下单\u003C\u002Fh2>\n\u003Ch3 id=\"toc-5\">4.1 反面教材：表示层直面远程服务\u003C\u002Fh3>\n\u003Cp>先造一个\"远程服务\"——模拟网络抖动（前 N 次调用失败）：\u003C\u002Fp>\n\u003Cpre class=\"shiki shiki-themes github-light github-dark\" style=\"background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8\" tabindex=\"0\">\u003Ccode>\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">public\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> class\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\"> RemoteOrderService\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> implements\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\"> OrderService\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> {\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">    private\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> int\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> pendingFailures;\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">    public\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\"> RemoteOrderService\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">int\u003C\u002Fspan>\u003Cspan style=\"color:#E36209;--shiki-dark:#FFAB70\"> pendingFailures\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">) {\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#005CC5;--shiki-dark:#79B8FF\">        this\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">.pendingFailures \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">=\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> pendingFailures;\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">    }\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">    @\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">Override\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">    public\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> String \u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">placeOrder\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(Order \u003C\u002Fspan>\u003Cspan style=\"color:#E36209;--shiki-dark:#FFAB70\">order\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">) {\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">        System.out.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">println\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(\u003C\u002Fspan>\u003Cspan style=\"color:#032F62;--shiki-dark:#9ECBFF\">\"      [远程调用] placeOrder(\"\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> +\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> order.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">getId\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">() \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">+\u003C\u002Fspan>\u003Cspan style=\"color:#032F62;--shiki-dark:#9ECBFF\"> \") ... 网络往返中\"\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">);\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">        if\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> (pendingFailures\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">--\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> >\u003C\u002Fspan>\u003Cspan style=\"color:#005CC5;--shiki-dark:#79B8FF\"> 0\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">) {\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">            throw\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> new\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\"> IllegalStateException\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(\u003C\u002Fspan>\u003Cspan style=\"color:#032F62;--shiki-dark:#9ECBFF\">\"网络抖动：连接超时（RemoteException 一族）\"\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">);\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">        }\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">        return\u003C\u002Fspan>\u003Cspan style=\"color:#032F62;--shiki-dark:#9ECBFF\"> \"订单 \"\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> +\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> order.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">getId\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">() \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">+\u003C\u002Fspan>\u003Cspan style=\"color:#032F62;--shiki-dark:#9ECBFF\"> \" 已受理（\"\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> +\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> order.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">getProductName\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">()\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">                +\u003C\u002Fspan>\u003Cspan style=\"color:#032F62;--shiki-dark:#9ECBFF\"> \" x\"\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> +\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> order.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">getQuantity\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">() \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">+\u003C\u002Fspan>\u003Cspan style=\"color:#032F62;--shiki-dark:#9ECBFF\"> \"）\"\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">;\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">    }\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#6A737D;--shiki-dark:#6A737D\">    \u002F\u002F queryStock 同款，略\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">}\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>然后看 Controller 直面它时的惨状：\u003C\u002Fp>\n\u003Cpre class=\"shiki shiki-themes github-light github-dark\" style=\"background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8\" tabindex=\"0\">\u003Ccode>\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">public\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> class\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\"> BadController\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> {\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">    public\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> String \u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">submitOrder\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(Order \u003C\u002Fspan>\u003Cspan style=\"color:#E36209;--shiki-dark:#FFAB70\">order\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">) {\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">        System.out.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">println\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(\u003C\u002Fspan>\u003Cspan style=\"color:#032F62;--shiki-dark:#9ECBFF\">\"    [JNDI 查找] OrderService ...（每个方法都要查一遍）\"\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">);\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">        OrderService service \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">=\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> new\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\"> RemoteOrderService\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(\u003C\u002Fspan>\u003Cspan style=\"color:#005CC5;--shiki-dark:#79B8FF\">1\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">);   \u003C\u002Fspan>\u003Cspan style=\"color:#6A737D;--shiki-dark:#6A737D\">\u002F\u002F 模拟查找结果\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">        for\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> (\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">int\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> attempt \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">=\u003C\u002Fspan>\u003Cspan style=\"color:#005CC5;--shiki-dark:#79B8FF\"> 1\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">; attempt \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">&#x3C;=\u003C\u002Fspan>\u003Cspan style=\"color:#005CC5;--shiki-dark:#79B8FF\"> 3\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">; attempt\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">++\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">) {\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">            try\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> {\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">                return\u003C\u002Fspan>\u003Cspan style=\"color:#032F62;--shiki-dark:#9ECBFF\"> \"下单成功：\"\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> +\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> service.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">placeOrder\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(order);\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">            } \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">catch\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> (IllegalStateException \u003C\u002Fspan>\u003Cspan style=\"color:#E36209;--shiki-dark:#FFAB70\">e\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">) {\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">                System.out.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">println\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(\u003C\u002Fspan>\u003Cspan style=\"color:#032F62;--shiki-dark:#9ECBFF\">\"    [Controller] 捕获远程异常 → 人肉翻译成业务话术（第 \"\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> +\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> attempt \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">+\u003C\u002Fspan>\u003Cspan style=\"color:#032F62;--shiki-dark:#9ECBFF\"> \" 次失败）\"\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">);\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">            }\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">        }\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">        return\u003C\u002Fspan>\u003Cspan style=\"color:#032F62;--shiki-dark:#9ECBFF\"> \"系统繁忙，请稍后再试\"\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">;\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">    }\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">    public\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> String \u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">checkStock\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(String \u003C\u002Fspan>\u003Cspan style=\"color:#E36209;--shiki-dark:#FFAB70\">productId\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">) {\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">        System.out.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">println\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(\u003C\u002Fspan>\u003Cspan style=\"color:#032F62;--shiki-dark:#9ECBFF\">\"    [JNDI 查找] OrderService ...（同款查找，再抄一遍）\"\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">);\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">        OrderService service \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">=\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> new\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\"> RemoteOrderService\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(\u003C\u002Fspan>\u003Cspan style=\"color:#005CC5;--shiki-dark:#79B8FF\">0\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">);\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">        try\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> {\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">            return\u003C\u002Fspan>\u003Cspan style=\"color:#032F62;--shiki-dark:#9ECBFF\"> \"库存：\"\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> +\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> service.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">queryStock\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(productId);\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">        } \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">catch\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> (IllegalStateException \u003C\u002Fspan>\u003Cspan style=\"color:#E36209;--shiki-dark:#FFAB70\">e\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">) {\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">            return\u003C\u002Fspan>\u003Cspan style=\"color:#032F62;--shiki-dark:#9ECBFF\"> \"系统繁忙，请稍后再试\"\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">;\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">        }\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">    }\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">}\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>运行输出：\u003C\u002Fp>\n\u003Cpre class=\"shiki shiki-themes github-light github-dark\" style=\"background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8\" tabindex=\"0\">\u003Ccode>\u003Cspan class=\"line\">\u003Cspan>========== 反面教材：表示层直面远程服务 ==========\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>[表示层] controller.submitOrder(订单 #1001)\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>    [JNDI 查找] OrderService ...（每个方法都要查一遍）\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>      [远程调用] placeOrder(1001) ... 网络往返中\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>    [Controller] 捕获远程异常 → 人肉翻译成业务话术（第 1 次失败）\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>      [远程调用] placeOrder(1001) ... 网络往返中\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>  结果：下单成功：订单 1001 已受理（机械键盘 x1）\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>[表示层] controller.checkStock(\"P101\")\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>    [JNDI 查找] OrderService ...（同款查找，再抄一遍）\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>      [远程调用] queryStock(P101) ... 网络往返中\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>  结果：库存：42\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>  —— 查找、重试、异常翻译，每个方法抄一遍，Controller 快成网管了 ——\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>\u003C\u002Fspan>\u003C\u002Fspan>\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>功能没问题，问题在\u003Cstrong>重复\u003C\u002Fstrong>：查找、调用、异常翻译这套流程每个方法抄一遍；十个 Controller 方法就是十份拷贝；哪天重试策略从 3 次改成 5 次，全项目正则搜索。\u003C\u002Fp>\n\u003Ch3 id=\"toc-6\">4.2 正解：业务代表 + 业务查找\u003C\u002Fh3>\n\u003Cp>\u003Cstrong>业务查找\u003C\u002Fstrong>（JNDI 的封装，查到就缓存——这个缓存下一步会长大成 ㉜ 服务定位器模式）：\u003C\u002Fp>\n\u003Cpre class=\"shiki shiki-themes github-light github-dark\" style=\"background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8\" tabindex=\"0\">\u003Ccode>\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">public\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> class\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\"> ServiceLookup\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> {\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">    private\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> OrderService orderService;\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">    public\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> OrderService \u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">getOrderService\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">() {\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">        if\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> (orderService \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">==\u003C\u002Fspan>\u003Cspan style=\"color:#005CC5;--shiki-dark:#79B8FF\"> null\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">) {\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">            System.out.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">println\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(\u003C\u002Fspan>\u003Cspan style=\"color:#032F62;--shiki-dark:#9ECBFF\">\"      [JNDI 查找] OrderService ...（又慢又容易出错的一步）\"\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">);\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">            orderService \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">=\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> new\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\"> RemoteOrderService\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(\u003C\u002Fspan>\u003Cspan style=\"color:#005CC5;--shiki-dark:#79B8FF\">2\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">);\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">        }\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">        return\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> orderService;\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">    }\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">}\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>\u003Cstrong>业务代表\u003C\u002Fstrong>：查找、重试、异常翻译一肩挑：\u003C\u002Fp>\n\u003Cpre class=\"shiki shiki-themes github-light github-dark\" style=\"background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8\" tabindex=\"0\">\u003Ccode>\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">public\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> class\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\"> OrderDelegate\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> {\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">    private\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> final\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> ServiceLookup lookup \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">=\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> new\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\"> ServiceLookup\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">();\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#6A737D;--shiki-dark:#6A737D\">    \u002F** 表示层只需要这一行 *\u002F\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">    public\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> String \u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">placeOrder\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(Order \u003C\u002Fspan>\u003Cspan style=\"color:#E36209;--shiki-dark:#FFAB70\">order\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">) {\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">        OrderService service \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">=\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> lookup.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">getOrderService\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">();\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">        IllegalStateException last \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">=\u003C\u002Fspan>\u003Cspan style=\"color:#005CC5;--shiki-dark:#79B8FF\"> null\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">;\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">        for\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> (\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">int\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> attempt \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">=\u003C\u002Fspan>\u003Cspan style=\"color:#005CC5;--shiki-dark:#79B8FF\"> 1\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">; attempt \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">&#x3C;=\u003C\u002Fspan>\u003Cspan style=\"color:#005CC5;--shiki-dark:#79B8FF\"> 3\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">; attempt\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">++\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">) {\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">            try\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> {\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">                return\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> service.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">placeOrder\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(order);\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">            } \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">catch\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> (IllegalStateException \u003C\u002Fspan>\u003Cspan style=\"color:#E36209;--shiki-dark:#FFAB70\">e\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">) {\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">                System.out.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">println\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(\u003C\u002Fspan>\u003Cspan style=\"color:#032F62;--shiki-dark:#9ECBFF\">\"      [业务代表] 第 \"\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> +\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> attempt \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">+\u003C\u002Fspan>\u003Cspan style=\"color:#032F62;--shiki-dark:#9ECBFF\"> \" 次尝试失败，自动重试 ...\"\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">);\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">                last \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">=\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> e;\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">            }\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">        }\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#6A737D;--shiki-dark:#6A737D\">        \u002F\u002F 技术异常在这里翻译成业务异常，表示层拿不到 RemoteException\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">        throw\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> new\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\"> IllegalStateException\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(\u003C\u002Fspan>\u003Cspan style=\"color:#032F62;--shiki-dark:#9ECBFF\">\"远程服务连续失败，请联系管理员（根因：\"\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> +\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> last.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">getMessage\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">() \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">+\u003C\u002Fspan>\u003Cspan style=\"color:#032F62;--shiki-dark:#9ECBFF\"> \"）\"\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">);\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">    }\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">    public\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> int\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\"> queryStock\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(String \u003C\u002Fspan>\u003Cspan style=\"color:#E36209;--shiki-dark:#FFAB70\">productId\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">) {\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">        return\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> lookup.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">getOrderService\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">().\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">queryStock\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(productId);\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">    }\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">}\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>\u003Cstrong>表示层的画风\u003C\u002Fstrong>，只剩业务话：\u003C\u002Fp>\n\u003Cpre class=\"shiki shiki-themes github-light github-dark\" style=\"background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8\" tabindex=\"0\">\u003Ccode>\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">OrderDelegate delegate \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">=\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> new\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\"> OrderDelegate\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">();\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">System.out.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">println\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(\u003C\u002Fspan>\u003Cspan style=\"color:#032F62;--shiki-dark:#9ECBFF\">\"  结果：\"\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> +\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> delegate.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">placeOrder\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">new\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\"> Order\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(\u003C\u002Fspan>\u003Cspan style=\"color:#032F62;--shiki-dark:#9ECBFF\">\"2001\"\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">, \u003C\u002Fspan>\u003Cspan style=\"color:#032F62;--shiki-dark:#9ECBFF\">\"无线鼠标\"\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">, \u003C\u002Fspan>\u003Cspan style=\"color:#005CC5;--shiki-dark:#79B8FF\">2\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">)));\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">System.out.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">println\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(\u003C\u002Fspan>\u003Cspan style=\"color:#032F62;--shiki-dark:#9ECBFF\">\"  结果：库存 \"\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> +\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> delegate.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">queryStock\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(\u003C\u002Fspan>\u003Cspan style=\"color:#032F62;--shiki-dark:#9ECBFF\">\"P101\"\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">));\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>运行输出：\u003C\u002Fp>\n\u003Cpre class=\"shiki shiki-themes github-light github-dark\" style=\"background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8\" tabindex=\"0\">\u003Ccode>\u003Cspan class=\"line\">\u003Cspan>========== 正解：业务代表替表示层挡枪 ==========\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>[表示层] delegate.placeOrder(订单 #2001)  ← 表示层只写这一行\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>      [JNDI 查找] OrderService ...（又慢又容易出错的一步）\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>      [远程调用] placeOrder(2001) ... 网络往返中\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>      [业务代表] 第 1 次尝试失败，自动重试 ...\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>      [远程调用] placeOrder(2001) ... 网络往返中\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>      [业务代表] 第 2 次尝试失败，自动重试 ...\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>      [远程调用] placeOrder(2001) ... 网络往返中\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>  结果：订单 2001 已受理（无线鼠标 x2）\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>[表示层] delegate.queryStock(\"P101\")  ← 服务引用已缓存，不再 JNDI\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>      [远程调用] queryStock(P101) ... 网络往返中\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>  结果：库存 42\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>  —— 表示层只见业务语义，远程细节全被代表消化 ——\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>\u003C\u002Fspan>\u003C\u002Fspan>\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>三处对照读出含金量：网络抖了两次，\u003Cstrong>表示层毫不知情\u003C\u002Fstrong>（重试在代表内部消化）；JNDI 只查了一次（ServiceLookup 缓存了服务引用）；第二次调用\u003Cstrong>跳过查找直达远程\u003C\u002Fstrong>。反面教材里 Controller 的十份拷贝，在这里是一份。\u003C\u002Fp>\n\u003Ch3 id=\"toc-7\">4.3 三处细节\u003C\u002Fh3>\n\u003Cp>\u003Cstrong>细节一：异常翻译是本模式的灵魂。\u003C\u002Fstrong> 注意 placeOrder 失败到底时抛的还是 IllegalStateException，但\u003Cstrong>消息已经变成业务话术\u003C\u002Fstrong>（\"远程服务连续失败，请联系管理员\"）——真实项目里该抛自定义的 OrderServiceException，表示层 catch 它时不需要 import 任何远程包。判断一个业务代表合不合格，就看表示层代码里还有没有远程世界的痕迹。\u003C\u002Fp>\n\u003Cp>\u003Cstrong>细节二：重试要警惕\"重试风暴\"。\u003C\u002Fstrong> 本文演示 3 次重试很温柔，但想想：下游服务过载时，上游 10 个入口每个重试 3 次——流量瞬间放大成 30 倍，下游直接压死。生产级的重试必须配熔断（连续失败就熔一段时间）和退避（每次等待翻倍）——这两个词留给微服务时代，但思想种子在这里。\u003C\u002Fp>\n\u003Cp>\u003Cstrong>细节三：代表接口不必镜像业务接口。\u003C\u002Fstrong> 本文 OrderDelegate 的方法与 OrderService 一一对应，是最简单的形制；实战里代表可以按\u003Cstrong>表示层的用例\u003C\u002Fstrong>重新组织——比如 \u003Ccode>checkout(Cart)\u003C\u002Fcode> 一个方法背后调远程的下单 + 扣库存 + 发通知三个服务。代表的接口是给表示层用的，不是给业务层的——它是缓冲带，不是传声筒。\u003C\u002Fp>\n\u003Ch2 id=\"toc-8\">五、在框架里找它\u003C\u002Fh2>\n\u003Col>\n \u003Cli>\n  \u003Cp>\u003Cstrong>OpenFeign（Spring Cloud）\u003C\u002Fstrong>：写个接口加 @FeignClient 注解，方法上写路径，框架生成实现——负载均衡、重试、超时全在框架层消化。Controller 注入这个接口直接调，见不到一行 RestTemplate。\u003Cstrong>声明式客户端就是业务代表思想的当代转世\u003C\u002Fstrong>：接口你定义，脏活框架干。\u003C\u002Fp>\u003C\u002Fli>\n \u003Cli>\n  \u003Cp>\u003Cstrong>Dubbo 的集群容错\u003C\u002Fstrong>：failover（失败自动切换重试）、failfast（快速失败）、failsafe（失败忽略）——Dubbo 在消费者侧把\"重试\"做成了可配置策略。本文手写的 for 循环，在生产框架里是 Cluster 层的一个策略类（⑮ 的味道）。\u003C\u002Fp>\u003C\u002Fli>\n \u003Cli>\n  \u003Cp>\u003Cstrong>Resilience4j \u002F Hystrix\u003C\u002Fstrong>：熔断、重试、舱壁、降级四件套——专门治理\"远程调用不可靠\"的框架。业务代表模式里代表身上的职责，被拆成了可组合的装饰器（⑩ 的味道）。\u003C\u002Fp>\u003C\u002Fli>\n \u003Cli>\n  \u003Cp>\u003Cstrong>EJB 时代原装\u003C\u002Fstrong>：Session Facade + Business Delegate + Service Locator 三件套是当年 Sun 蓝皮书的标准答案——本文 ServiceLookup 里那行缓存，就是 Service Locator 模式的种子，㉜ 正式展开。\u003C\u002Fp>\u003C\u002Fli>\n\u003C\u002Fol>\n\u003Ch2 id=\"toc-9\">六、业务代表 vs 外观 vs 代理——中间人三兄弟对账\u003C\u002Fh2>\n\u003Cp>细心的读者已经发现：业务代表长得像 ⑪ 外观，也像 ⑬ 代理——都是\"中间垫一层\"。三个中间人摊开对账：\u003C\u002Fp>\n\u003Ctable>\n \u003Cthead>\n  \u003Ctr>\n   \u003Cth>\u003C\u002Fth>\n   \u003Cth>外观（⑪）\u003C\u002Fth>\n   \u003Cth>代理（⑬）\u003C\u002Fth>\n   \u003Cth>业务代表（㉗）\u003C\u002Fth>\n  \u003C\u002Ftr>\n \u003C\u002Fthead>\n \u003Ctbody>\n  \u003Ctr>\n   \u003Ctd>出发点\u003C\u002Ftd>\n   \u003Ctd>子系统调用太碎\u003C\u002Ftd>\n   \u003Ctd>对目标的访问需要管控\u003C\u002Ftd>\n   \u003Ctd>表示层与业务层之间边界太脏\u003C\u002Ftd>\n  \u003C\u002Ftr>\n  \u003Ctr>\n   \u003Ctd>接口形态\u003C\u002Ftd>\n   \u003Ctd>通常重新设计、更粗\u003C\u002Ftd>\n   \u003Ctd>与目标**一模一样**，透明替换\u003C\u002Ftd>\n   \u003Ctd>按表示层需要设计，可重组\u003C\u002Ftd>\n  \u003C\u002Ftr>\n  \u003Ctr>\n   \u003Ctd>典型行为\u003C\u002Ftd>\n   \u003Ctd>多个调用收口成一个\u003C\u002Ftd>\n   \u003Ctd>延迟加载、权限检查、缓存\u003C\u002Ftd>\n   \u003Ctd>服务查找、重试容错、异常翻译\u003C\u002Ftd>\n  \u003C\u002Ftr>\n  \u003Ctr>\n   \u003Ctd>出身\u003C\u002Ftd>\n   \u003Ctd>GoF 1994\u003C\u002Ftd>\n   \u003Ctd>GoF 1994\u003C\u002Ftd>\n   \u003Ctd>J2EE 蓝皮书\u003C\u002Ftd>\n  \u003C\u002Ftr>\n  \u003Ctr>\n   \u003Ctd>一句话\u003C\u002Ftd>\n   \u003Ctd>替你**少打电话**\u003C\u002Ftd>\n   \u003Ctd>替你**管控电话**\u003C\u002Ftd>\n   \u003Ctd>替你**接通电话并处理杂音**\u003C\u002Ftd>\n  \u003C\u002Ftr>\n \u003C\u002Ftbody>\n\u003C\u002Ftable>\n\u003Cp>三者可以叠加：业务代表内部用代理拿远程引用、用外观收口多个服务调用——中间人不是排他的，\u003Cstrong>按\"你替我挡的是什么\"来选\u003C\u002Fstrong>：挡复杂度选外观，挡访问选代理，挡远程的脏选业务代表。\u003C\u002Fp>\n\u003Ch2 id=\"toc-10\">写在最后\u003C\u002Fh2>\n\u003Cul>\n \u003Cli>业务代表 = 表示层与业务层之间的缓冲带：查找、重试、异常翻译一肩挑\u003C\u002Fli>\n \u003Cli>表示层只见业务语义——判断合格的标准是它代码里没有远程世界的痕迹\u003C\u002Fli>\n \u003Cli>重试虽好，当心重试风暴：生产级必须配熔断与退避\u003C\u002Fli>\n \u003Cli>代表接口按表示层用例设计，是缓冲带不是传声筒\u003C\u002Fli>\n \u003Cli>中间人三兄弟：外观挡复杂度、代理挡访问、业务代表挡脏边界——可叠加，按需选\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Cp>㉗ 的订单实体只有三个字段，一次远程调用就传输完毕——这是实体世界的\"好养型\"。可真实的领域模型里，\"客户\"这种实体由地址、信用、联系方式、订单历史一堆小对象组成；远程场景下，每个小对象都可能是独立的网络往返。EJB 的答案：把一组依赖对象捆成一个\u003Cstrong>粗粒度实体\u003C\u002Fstrong>统一管理——组合实体模式，下一篇见。\u003C\u002Fp>",[112,115,118,121,124,128,131,134,137,140],{"id":113,"text":114,"level":107},"toc-1","一、业务代表模式是什么",{"id":116,"text":117,"level":107},"toc-2","二、优点与缺点",{"id":119,"text":120,"level":107},"toc-3","三、什么时候用",{"id":122,"text":123,"level":107},"toc-4","四、实现方式：远程下单",{"id":125,"text":126,"level":127},"toc-5","4.1 反面教材：表示层直面远程服务",3,{"id":129,"text":130,"level":127},"toc-6","4.2 正解：业务代表 + 业务查找",{"id":132,"text":133,"level":127},"toc-7","4.3 三处细节",{"id":135,"text":136,"level":107},"toc-8","五、在框架里找它",{"id":138,"text":139,"level":107},"toc-9","六、业务代表 vs 外观 vs 代理——中间人三兄弟对账",{"id":141,"text":142,"level":107},"toc-10","写在最后"]