
1. 委派模式到底是什么它为什么“不算设计模式”却天天在用委派模式Delegate Pattern这个词第一次听到时我也有点懵——翻遍GoF那本《设计模式可复用面向对象软件的基础》23个经典模式里压根没它查JDK源码、Spring框架核心类、甚至Android SDK的Activity委托机制它又无处不在。它不像单例那样有明确的getInstance()签名也不像观察者那样必须实现Observer接口更不靠UML图里的虚线箭头来定义关系。它没有统一的接口规范没有强制的类图结构甚至没有一个被广泛接受的标准实现模板。但你写过Spring MVC的DispatcherServlet吗看过Spring Boot自动配置里那些xxxAutoConfiguration类是怎么把活儿甩给xxxProperties和xxxService的吗或者你调试过MyBatis的SqlSessionTemplate发现它自己不干活只是把所有方法调用原封不动转给内部持有的SqlSession代理对象这些全都是委派模式在真实世界里的毛细血管级落地。它不是“设计模式”而是一种被反复验证、高度内化的编程直觉。就像老木匠不会说“我在用楔形榫卯结构”他只是知道“这块木头得这么凿才能咬住不松”。委派的本质就是把责任明确地、不加修饰地交给另一个对象去完成自己只做协调者、中转站、门面或适配器。它不追求抽象层次的优雅只求职责清晰、修改隔离、复用直接。所以它常被误认为是“组合”或“代理”的变体但关键区别在于代理往往要增强行为比如加日志、事务、权限而委派是纯粹的“你干我传话”组合强调整体-部分的生命周期管理委派则完全不管被委派对象是谁创建、何时销毁只关心“此刻这个事该谁办”。我带过不少刚学完23种模式就去写业务代码的新人他们总想给每个类都套上Strategy或State的壳子结果代码越来越重改一行要动三处。直到某天重构一个支付回调处理器把验签、解密、业务逻辑、通知下游全部拆成独立类主流程只剩几行signValidator.validate()、decryptor.decrypt()、orderService.handle()——那一刻他们才真正懂了委派它不是模式是对“一个类只做一件事”这条铁律最朴素、最锋利的执行工具。它不炫技但能让你的代码在三个月后还能被自己一眼看懂。2. 委派模式的核心设计思路与选型逻辑2.1 为什么不用继承为什么不用策略委派的不可替代性在哪很多人第一反应是“这不就是继承嘛子类复用父类方法。”错。继承是“我是你”委派是“我找你”。举个血淋淋的例子你有个ReportGenerator类需要生成PDF和Excel两种格式。如果用继承你得搞PdfReportGenerator extends ReportGenerator和ExcelReportGenerator extends ReportGenerator然后在主流程里if (type PDF) new PdfReportGenerator().generate()。问题来了当你要加Word格式时得新增一个类还得改if-else当ReportGenerator基类要加个新方法两个子类都得跟着改哪怕它们根本用不到。这就是继承带来的紧耦合与脆弱性。再看策略模式定义ReportStrategy接口PdfStrategy、ExcelStrategy实现它主类持有一个ReportStrategy引用运行时注入。这比继承强但多了一层抽象——你得定义接口、实现类、上下文类还要考虑策略的创建和切换逻辑。而委派呢ReportGenerator直接持有一个PdfExporter对象调用pdfExporter.export(data)需要Excel时换成持有一个ExcelExporter对象调用excelExporter.export(data)。没有接口没有上下文没有策略选择器只有最直接的对象引用和方法转发。它省掉了所有为“可能变化”而预设的抽象层只解决“当前这个变化点在哪里”的问题。我实测过一个电商订单导出模块用策略模式实现PDF/Excel/CSV三格式代码量480行改用委派后核心逻辑压缩到210行新增JSON格式只需加一个JsonExporter类65行和一行赋值exporter new JsonExporter()。上线后运营突然要求导出带水印的PDF策略模式得改PdfStrategy实现还可能影响其他策略委派模式直接让PdfExporter内部调用WatermarkApplier.apply()主流程零改动。这就是委派的底层逻辑它不预测变化只承接变化不构建框架只提供插槽。2.2 委派 vs 代理一字之差生死之别代理Proxy和委派Delegate中文名只差一个字但设计意图天壤之别。代理的核心是控制访问它站在目标对象前面决定“能不能调”、“要不要记录”、“要不要缓存”。比如JDK动态代理生成的$Proxy0每次调用前先走InvocationHandler.invoke()你可以在这里加事务拦截、性能监控、权限校验。代理对象和目标对象通常类型一致实现同一接口代理是目标的“影子”。委派呢它是责任转移。委派对象和被委派对象类型可以完全不同它们之间没有继承或实现关系只有“我知道你有这个能力所以我把这事交给你”。Spring的DispatcherServlet就是教科书级案例它本身不处理HTTP请求而是把doGet、doPost等方法直接委派给内部的HandlerMapping找处理器、HandlerAdapter适配执行、ViewResolver解析视图。DispatcherServlet的类型是HttpServletHandlerMapping是HandlerMapping接口ViewResolver是ViewResolver接口——它们类型不同职责无关唯一联系就是“我需要你帮我干这个活”。DispatcherServlet不控制HandlerMapping的访问它只信任它、调用它、依赖它。提示判断一段代码是不是委派就看那个持有对象的类里有没有大量形如this.delegate.xxx()的方法调用且这些调用前后没有任何额外逻辑比如日志、异常包装、条件判断。如果有大概率是代理如果只是干净利落的转发那就是委派。2.3 Spring生态里委派模式的三大典型场景Spring框架是委派模式的超级应用场它把委派玩成了呼吸般自然的开发习惯。我梳理出三个最硬核、最高频的落地场景第一DispatcherServlet的请求分发链。这是Spring MVC的中枢神经。DispatcherServlet本身几乎不写业务逻辑它的doDispatch()方法就像一个精密的交通指挥台收到请求后先委派给HandlerMapping“去查查这个URL该由谁处理”拿到处理器后再委派给HandlerAdapter“你来适配并执行这个处理器不管它是Controller还是FunctionalInterface”执行完结果再委派给ViewResolver“你来把ModelAndView变成真正的HTML页面”。整个过程没有一个环节是DispatcherServlet自己干的它只是把责任一级级、精准地委派下去。这种设计让Spring MVC极容易扩展——你想换路由规则实现自己的HandlerMapping想支持新类型的处理器写个HandlerAdapter想用Thymeleaf代替JSP换ViewResolver。所有扩展点都通过委派暴露主干代码岿然不动。第二自动配置AutoConfiguration的职责切分。Spring Boot的魔法背后是无数个xxxAutoConfiguration类在默默委派。比如DataSourceAutoConfiguration它不自己创建数据源而是委派给DataSourceProperties读取application.yml里的配置、委派给DataSourceBuilder构造具体的数据源实例、再委派给DataSourceTransactionManager管理事务。每个AutoConfiguration类就像一个项目经理自己不敲代码只负责把需求拆解、分配给专业的“外包团队”即各种Properties、Builder、Manager类。当你自定义DataSourceBean时Spring Boot会自动跳过DataSourceAutoConfiguration的委派逻辑因为你已经提供了“最终交付物”项目经理直接验收不再往下派活。这种委派条件化装配的组合让Spring Boot的自动配置既强大又灵活。第三Bean生命周期管理中的职责剥离。AbstractAutowireCapableBeanFactory是Spring Bean工厂的核心它创建Bean的过程就是一场大型委派协作实例化Bean时委派给InstantiationStrategy可能是CGLIB代理或反射属性填充时委派给BeanWrapper负责类型转换和属性设置初始化前委派给BeanPostProcessor允许你介入初始化前后的处理销毁时委派给DisposableBean或PreDestroy方法。AbstractAutowireCapableBeanFactory自己只维护流程骨架所有脏活累活都委派出去。这也是为什么你能轻松添加自定义的BeanPostProcessor来打印Bean创建日志或者用InstantiationStrategy切换为ASM字节码生成——因为委派机制早已为你预留了插槽。3. 委派模式的实操实现与核心细节3.1 手写一个最小可行委派从零开始理解本质别急着看Spring源码我们先用最原始的Java手写一个委派模式把它的筋骨摸清楚。假设我们要做一个简单的“用户服务”包含注册、登录、发送邮件三个功能。按传统思路可能写一个UserService大类里面堆满方法。现在用委派重构// 第一步定义被委派的组件它们各自专注一件事 public class UserRegistration { public void register(String username, String password) { System.out.println(注册用户: username); // 实际注册逻辑存数据库、发短信验证码... } } public class UserAuthentication { public boolean login(String username, String password) { System.out.println(用户 username 尝试登录); // 实际登录逻辑查密码、生成token... return true; } } public class EmailNotifier { public void sendWelcomeEmail(String email) { System.out.println(发送欢迎邮件到: email); // 实际邮件发送逻辑调用SMTP服务... } }// 第二步创建委派者它不实现业务只组织流程 public class UserService { // 持有被委派对象的引用组合关系 private final UserRegistration registration; private final UserAuthentication authentication; private final EmailNotifier notifier;// 构造函数注入所有依赖这是委派的基石依赖明确、可替换 public UserService(UserRegistration registration, UserAuthentication authentication, EmailNotifier notifier) { this.registration registration; this.authentication authentication; this.notifier notifier; } // 核心所有方法都是干净的转发 public void registerUser(String username, String password) { registration.register(username, password); // 直接调用无包装 notifier.sendWelcomeEmail(username example.com); // 可以组合多个委派 } public boolean loginUser(String username, String password) { return authentication.login(username, password); // 纯转发 }}// 第三步使用体现委派的灵活性 public class Main { public static void main(String[] args) { // 创建具体的被委派对象 UserRegistration reg new UserRegistration(); UserAuthentication auth new UserAuthentication(); EmailNotifier notifier new EmailNotifier(); // 组装委派者 UserService service new UserService(reg, auth, notifier); // 调用委派者的方法它自动把活儿分发出去 service.registerUser(zhangsan, 123456); // 输出 // 注册用户: zhangsan // 发送欢迎邮件到: zhangsanexample.com } }这段代码就是委派模式的DNA没有接口、没有抽象类、没有设计模式标签只有清晰的组合关系和直白的方法转发。它解决了什么第一UserService类体积小、职责单一只负责“注册流程编排”不掺杂任何具体实现细节第二每个被委派类UserRegistration等可以独立测试、独立修改改邮件发送逻辑不影响注册逻辑第三替换成本极低——如果要把邮件发送换成短信只需写个SmsNotifier类然后在Main里把notifier参数换成它UserService代码一行不改。注意这里构造函数注入是关键。如果用new UserRegistration()在UserService内部创建就破坏了委派的松耦合本质变成了硬编码依赖。委派的生命力就在于被委派对象的可替换性。3.2 Spring中委派模式的进阶实现DispatcherServlet源码级拆解光看手写例子不够过瘾我们 dive into Spring源码看看工业级委派如何运转。以Spring 5.3.37的DispatcherServlet为例聚焦doDispatch()这个核心方法删减了日志和异常处理保留主干protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception { HttpServletRequest processedRequest request; HandlerExecutionChain mappedHandler null; boolean multipartRequestParsed false; WebAsyncManager asyncManager WebAsyncUtils.getAsyncManager(request); try { ModelAndView mv null; Exception dispatchException null; try { // 步骤1委派给MultipartResolver处理文件上传如果需要 // 这是委派的第一环把请求预处理交给专门的组件 processedRequest checkMultipart(request); multipartRequestParsed (processedRequest ! request); // 步骤2委派给HandlerMapping查找处理器Controller // 关键调用mappedHandler getHandler(processedRequest); // getHandler()方法内部会遍历所有已注册的HandlerMapping // 调用它们的getHandler()方法找到匹配的Controller mappedHandler getHandler(processedRequest); if (mappedHandler null) { noHandlerFound(processedRequest, response); return; } // 步骤3委派给HandlerAdapter执行处理器 // 关键调用mv ha.handle(processedRequest, response, mappedHandler.getHandler()); // HandlerAdapter根据处理器类型Controller, RestController, 函数式端点选择适配策略 HandlerAdapter ha getHandlerAdapter(mappedHandler.getHandler()); mv ha.handle(processedRequest, response, mappedHandler.getHandler()); // 步骤4委派给ViewResolver解析视图名称为实际视图 // 关键调用view resolveViewName(mv.getViewName(), mv.getModelInternal(), locale, request); if (mv ! null !mv.wasCleared()) { render(mv, processedRequest, response); } } catch (Exception ex) { dispatchException ex; } // ... 异常处理、异步处理等委派分支 } finally { // 清理工作也常委派给RequestAttributes if (asyncManager.isConcurrentHandlingStarted()) { // ... } else { // Reset the standard Servlet-based request attributes for this request. if (requestAttributes ! null) { requestAttributes.reset(); } } } }这段代码里藏着委派模式的精髓委派链条的显式声明getHandler()、getHandlerAdapter()、render()这些方法名本身就是委派意图的宣言。DispatcherServlet不自己去找Controller它说“你HandlerMapping去找”不自己执行Controller它说“你HandlerAdapter来适配并执行”不自己渲染页面它说“你ViewResolver来搞定”。委派对象的动态获取getHandler()不是返回一个固定对象而是遍历this.handlerMappings列表这是一个ListHandlerMapping逐个调用mapping.getHandler(request)第一个返回非null的就胜出。这意味着你可以随时向Spring容器注入新的HandlerMapping实现比如基于注解的RequestMappingHandlerMapping或基于XML的BeanNameUrlHandlerMappingDispatcherServlet无需修改因为它委派的是“所有HandlerMapping的集合”而不是某个具体实现。委派结果的无缝衔接HandlerMapping返回HandlerExecutionChain包含处理器和拦截器链HandlerAdapter接收这个链并执行ViewResolver接收ModelAndView并输出View。每个环节的输出恰好是下一个环节的输入形成一条严丝合缝的责任传递链。这种设计让Spring MVC的扩展性达到极致——你想加一个全局请求日志写个HandlerInterceptor它会被自动加入HandlerExecutionChain你想支持GraphQL写个GraphQlHandlerAdapterSpring会自动把它加入handlerAdapters列表。3.3 在Spring Boot项目中实战委派自定义一个配置加载委派器理论看够了现在动手在一个Spring Boot项目里实践。假设你的项目需要从多种来源加载配置优先从Nacos配置中心失败则降级到本地application.yml最后兜底到硬编码默认值。这不是一个简单的Value能搞定的需要委派模式来组织。第一步定义被委派的配置加载器接口为了类型安全虽非必需但强烈推荐// 配置加载器接口定义统一契约 public interface ConfigLoader { String load(String key, String defaultValue); boolean supports(String source); // 声明自己支持哪种数据源 }第二步实现三个具体加载器各司其职Component Order(1) // 最高优先级 public class NacosConfigLoader implements ConfigLoader { Autowired private ConfigService configService; // Nacos客户端 Override public String load(String key, String defaultValue) { try { String value configService.getConfig(key, DEFAULT_GROUP, 3000); return StringUtils.hasText(value) ? value : defaultValue; } catch (NacosException e) { log.warn(从Nacos加载配置失败key{}, key, e); return defaultValue; } } Override public boolean supports(String source) { return nacos.equalsIgnoreCase(source); } } Component Order(2) // 次高优先级 public class YamlConfigLoader implements ConfigLoader { Autowired private Environment environment; // Spring环境可读取application.yml Override public String load(String key, String defaultValue) { return environment.getProperty(key, defaultValue); } Override public boolean supports(String source) { return yaml.equalsIgnoreCase(source); } } Component Order(3) // 最低优先级兜底 public class DefaultConfigLoader implements ConfigLoader { Override public String load(String key, String defaultValue) { // 硬编码默认值映射表 MapString, String defaults Map.of( app.timeout, 3000, app.retry.count, 3, app.feature.flag, false ); return defaults.getOrDefault(key, defaultValue); } Override public boolean supports(String source) { return default.equalsIgnoreCase(source); } }第三步创建委派配置服务核心Service public class DelegatingConfigService { // 通过Qualifier注入所有ConfigLoader Bean并按Order排序 private final ListConfigLoader loaders; public DelegatingConfigService(ListConfigLoader loaders) { // 按Order注解升序排列确保Nacos优先 this.loaders loaders.stream() .sorted(Comparator.comparingInt(loader - { Order order loader.getClass().getAnnotation(Order.class); return order ! null ? order.value() : Integer.MAX_VALUE; })) .collect(Collectors.toList()); } // 委派核心方法遍历所有加载器按顺序尝试第一个成功就返回 public String getConfig(String key, String defaultValue) { for (ConfigLoader loader : loaders) { try { String value loader.load(key, defaultValue); if (value ! null !value.trim().isEmpty()) { log.debug(配置 {} 加载成功来源{}, key, loader.getClass().getSimpleName()); return value; } } catch (Exception e) { log.warn(配置加载器 {} 加载 {} 失败, loader.getClass().getSimpleName(), key, e); } } return defaultValue; } // 便捷方法支持指定来源用于测试或特殊场景 public String getConfigFromSource(String key, String source, String defaultValue) { return loaders.stream() .filter(loader - loader.supports(source)) .findFirst() .map(loader - loader.load(key, defaultValue)) .orElse(defaultValue); } }第四步在业务中使用享受委派红利RestController public class ConfigController { Autowired private DelegatingConfigService configService; GetMapping(/config/{key}) public String getConfig(PathVariable String key) { // 一行代码背后是三层委派Nacos - YAML - Default return configService.getConfig(key, NOT_FOUND); } // 测试指定来源 GetMapping(/config/nacos/{key}) public String getNacosConfig(PathVariable String key) { return configService.getConfigFromSource(key, nacos, NACOS_NOT_FOUND); } }这个实战案例完美体现了委派模式的威力新增一个配置源比如ZooKeeper你只需写一个ZkConfigLoader实现类加上Component和Order(1.5)其他代码零修改。DelegatingConfigService作为委派者天然支持无限扩展因为它不关心具体有多少个加载器只关心“按顺序问一遍谁有就用谁的”。4. 委派模式的常见问题与排查技巧实录4.1 “委派失效”为什么我的方法调用没走到被委派对象这是新手踩坑最多的问题。现象是UserService调用了registration.register()但断点死活不进UserRegistration.register()方法日志也没输出。原因往往藏在对象生命周期里。排查步骤一确认被委派对象是否真的被注入在UserService的构造函数里加日志public UserService(UserRegistration registration, ...) { System.out.println(registration is null? (registration null)); // 必须是false this.registration registration; }如果输出true说明Spring没找到UserRegistrationBean。检查UserRegistration类上是否有Component或Service它所在的包是否被ComponentScan扫描到是否存在多个同类型Bean导致注入歧义此时需用Qualifier指定排查步骤二检查方法签名是否完全一致委派是硬编码调用registration.register(username, password)必须和UserRegistration.register(String, String)签名100%匹配。常见错误UserRegistration.register(String username, String password)的参数是String但调用时传了int触发了自动装箱实际调用的是register(Integer, Integer)——而这个方法根本不存在编译报错。方法名拼写错误registraton.register()少了个i编译器会报错但如果你用IDEA的自动补全可能补成registerUser()而UserRegistration里根本没有这个方法运行时报NoSuchMethodError。排查步骤三警惕代理对象的“假委派”在Spring AOP环境下UserRegistration可能被CGLIB代理。此时this.registration指向的是代理对象它的register()方法会先进入MethodInterceptor.invoke()。如果你在这个拦截器里写了return null或抛异常委派就中断了。解决方案在EnableAspectJAutoProxy(exposeProxytrue)然后在UserService里用((UserRegistration) AopContext.currentProxy()).register()绕过代理——但这违背了委派初衷更好的做法是确保UserRegistration本身不被AOP切面匹配比如用Scope(prototype)或排除它的包路径。实操心得我给自己定了一条铁律——所有被委派对象的类必须用final修饰除非要被继承并在构造函数里对每个参数做Objects.requireNonNull()校验。这样能在启动时就暴露空指针而不是等到业务调用时才崩。4.2 “循环委派”两个类互相委派程序卡死现象服务启动正常但一调用某个API就CPU 100%线程栈显示A.doX() - B.doY() - A.doX() - B.doY()...无限递归。这是委派模式最危险的陷阱。典型场景还原// A类委派给B public class ServiceA { private final ServiceB serviceB; public ServiceA(ServiceB serviceB) { this.serviceB serviceB; } public void doWork() { System.out.println(A starts); serviceB.doSomething(); // 委派给B } } // B类又委派回A public class ServiceB { private final ServiceA serviceA; public ServiceB(ServiceA serviceA) { this.serviceA serviceA; } public void doSomething() { System.out.println(B starts); serviceA.doWork(); // 又委派回A } }根因分析Spring的循环依赖检测只针对单例Bean的构造器注入。上面代码中ServiceA和ServiceB互相依赖Spring在创建ServiceA时发现需要ServiceB就去创建ServiceB创建ServiceB时又发现需要ServiceA此时Spring会把正在创建的ServiceA半成品提前暴露出来注入给ServiceB。结果就是ServiceA和ServiceB都持有了对方的引用但它们都是未完全初始化的状态。一旦调用立刻陷入死循环。解决方案重构职责问自己A和B真的需要互相调用吗能否把公共逻辑抽成第三个类C让A和B都委派给C这是最干净的解法。延迟加载把其中一个依赖改为ObjectProviderServiceA或ApplicationContext.getBean()让它在真正需要时才获取避免构造期循环。Setter注入替代构造器注入虽然不推荐破坏不可变性但在万不得已时用Autowired public void setServiceA(ServiceA a)让Spring在构造完成后注入避开构造期检测。注意Spring Boot 2.6默认禁止循环引用spring.main.allow-circular-referencesfalse遇到此问题会直接启动失败报BeanCurrentlyInCreationException。这是好事逼你早发现早重构。4.3 “委派链过长”一个请求经过七八个委派调试像走迷宫现象一个HTTP请求进来DispatcherServlet-HandlerMapping-HandlerAdapter-Controller-Service-Repository-JdbcTemplate-Connection整整8层。想查个SQL慢得在8个地方打日志效率极低。破局技巧用MDCMapped Diagnostic Context贯穿全程在DispatcherServlet的doDispatch()最开头生成一个唯一traceId并放入MDCString traceId UUID.randomUUID().toString().replace(-, ); MDC.put(traceId, traceId); try { // 原有委派逻辑 } finally { MDC.clear(); // 清理避免线程复用污染 }然后确保所有被委派的类HandlerMapping、Service等在日志中都输出{traceId}。这样你只要grep一个traceId就能把整个调用链的日志串起来。我用这个方法在一个微服务项目里把平均故障定位时间从45分钟缩短到3分钟。更进一步用Spring Cloud Sleuth它自动为每个Span生成traceId和spanId并集成Zipkin。你不需要改一行业务代码所有委派环节的日志、HTTP调用、DB操作都会被自动打标。打开Zipkin UI输入traceId一张清晰的调用拓扑图就出来了哪个环节耗时最长一目了然。4.4 委派模式的“隐形成本”性能与内存开销委派模式看似轻量但大规模使用时隐性成本不容忽视。我在线上一个QPS 5000的订单服务里做过压测对比了纯委派和直接调用的差异场景平均RTmsGC Young Gen次数/分钟内存占用MB直接调用无委派12.31804203层委派A-B-C-D14.7 (19%)210 (17%)450 (7%)增长主要来自方法调用栈加深每层委派增加一次栈帧JVM需要更多栈空间。对象引用链变长A持有BB持有CC持有DGC时需要遍历更长的引用链来判断对象是否可达。CPU缓存局部性下降被委派对象分散在内存不同位置CPU缓存命中率降低。优化策略关键路径精简委派层数支付核心路径下单、扣库存、减余额严格控制在2层以内非核心路径日志、监控、告警才用多层委派。用final字段减少JIT优化障碍private final ServiceB serviceB;让JVM更容易内联方法调用。批量委派替代单次委派比如不是for (item : list) { exporter.export(item); }而是exporter.exportAll(list);把循环移到被委派对象内部减少方法调用次数。我的血泪教训曾经为追求“绝对解耦”把一个简单的用户信息查询拆成UserController-UserService-UserQueryService-UserMapper-JdbcTemplate五层。上线后发现RT飙升30%回滚后合并为UserController-UserMapper两层RT回归正常。委派不是越多越好而是恰到好处的最少必要委派。5. 委派模式的延伸思考它和现代架构的共生关系5.1 微服务架构下的委派从进程内到跨进程委派模式在单体应用里是对象间的责任转移到了微服务时代它进化成了服务间的职责协同。一个典型的电商下单流程OrderService订单服务不自己校验库存而是委派给InventoryService库存服务的checkStock()APIInventoryService不自己扣减而是委派给WarehouseService仓储服务的reserveGoods()WarehouseService不自己发货而是委派给LogisticsService物流服务的createShipment()。这和DispatcherServlet的委派链神似只是通信方式从方法调用变成了HTTP/gRPC。Spring Cloud OpenFeign就是为这种跨进程委派而生的——你定义一个InventoryClient接口OpenFeign自动生成实现把inventoryClient.checkStock()调用翻译成对http://inventory-service/api/stock/check的HTTP请求。OrderService作为委派者完全不知道底层是HTTP还是gRPC它只关心“把库存检查这件事交给InventoryService去做”。这种架构下委派模式的价值被放大它让每个微服务可以独立演进。InventoryService升级为分布式锁库存只要checkStock()API契约不变OrderService就无需任何修改。这正是委派模式“隔离变化”本质的终极体现。5.2 函数式编程中的委派Lambda作为委派载体Java 8的Lambda让委派变得前所未有的轻量。传统委派需要定义接口、实现类、注入而Lambda可以把“委派什么”直接作为参数传入// 传统委派需要定义Formatter接口和实现类 public interface Formatter { String format(String input); } public class UpperCaseFormatter implements Formatter { ... } service.process(hello, new UpperCaseFormatter()); // Lambda委派一行搞定 service.process(hello, s - s.toUpperCase());service.process()方法内部就是把s - s.toUpperCase()这个函数对象作为委派者持有的“行为”在需要时调用formatter.apply(input)。Spring Framework 5.0大量采用这种风格比如WebMvcConfigurer的addInterceptors()方法接受InterceptorRegistration而后者可以用Lambda注册拦截逻辑。这证明委派模式的核心思想——“把事交给别人干”——可以脱离OO范式融入任何编程范式。5.3 AI时代的委派Spring AI如何把LLM调用变成委派Spring AI 1.0的发布把委派模式推向了新高度。它把大模型LLM调用封装成一个标准的委派接口// Spring AI定义的委派契约 public interface ChatClient { ResponseChatResponse call(RequestChatRequest request); } // 你无需关心底层是调用OpenAI、Azure、还是本地Ollama // 只需注入ChatClient然后委派 Service public class AiAssistantService { private final ChatClient chatClient; // 委派者持有的被委派对象 public AiAssistantService(ChatClient chatClient) { this.chatClient chatClient; } public String answerQuestion(String question) { // 把“回答问题”这件事干净利落地委派给chatClient return chatClient.call(new Request(new ChatRequest(question))) .getResult().getOutput().getContent();