今天咱们聊聊那种看着像铁桶一样的“责任链”。别急着喊出“起初、其次”,咱直接说,这玩意儿就是把一群功夫老把式按严丝合缝的甲胄绑在一块,哪位也不许插队,哪位也别想单独施展绝活。 这玩意儿在那会儿叫“工厂模式”,目前叫“依赖注入”,本质上就是给一个超级英雄穿上多层铠甲。

你想,要是让 `MyService` 这个超级英雄自己出拳,结局被 `AuthService` 给锁死了,每走一步都得喊一声令牌,那得多矫情啊?便,你在 `MyService` 里把 `AuthService` 和 `MessageQueue` 都塞进去,让它们各自找个新主儿。`MyService` 手里有了 `AuthService` 的钥匙,手里又有 `MessageQueue` 的通行证。后面的人来了,直接跟前面的人打招呼。 这就好比咱们现实里的职场。你当了个技术总监,你手下有资深架构师,还有前端写代码的。你不需求自己去背代码,你只需求告诉前端:“这个接口里的数据格式,别按照我刚刚写的来,得改成 RESTful 的。”前端听懂了就行。你只管去和架构师开会,听他如何调接口。 这就构成了典型的链条。`MyService` 是中间环节的君主,它本身不干活,只负责传递指令和协调资源。上面的 `AuthService` 负责逻辑判断,下面的 `MessageQueue` 负责异步消息。哪位也不过问哪位,直到指令从链条顶端流到底端。 有人问,如此松,万一后面断了如何办?这就得看这链条有多“硬”。

看看那些老派的老搞技术的,他们喜爱用 `final` 关键字,把每个环节都死锁住。

比如 `MessageQueue` 就是个 `final` 对象,哪位也不能改。`AuthService` 也是 `final`,哪位也不能改。`MyService` 更是一个 `final`。一旦你改了 `MessageQueue` 的接口,你得把所有依赖它的地方都改一遍,生怕漏了哪位。

这在 Java 里叫 `Chain` 接口,目前更叫 `Chain`。 你看这个设计有多美。所有依赖 `MyService` 的地方,都写成了 `myService = new MyService()`。哪位也不清楚底层是哪位做的,哪位也不关心,就如此个对象。`MyService` 自己也不知道它手里到底攥着哪位,只知道上面来的请求得往下传,下面有人回来请求时就得往上传。中间人 `AuthService` 就连不知道自己被哪位调用,出于它的代码里全是 `service = service.service();` 这种写法,根本看不出哪位是 `MyService`,哪位是 `AuthService`。 这种设计的优势是极致。系统简洁、无耦合、可测试。你只需求测试 `MyService` 这一层,剩下的全交给测试打。`MyService` 是个虚拟的壳,它只是把你供给的接口包装了一下。测试时,你随意注入个 `MockAuthService` 进去,`MyService` 一查,发现接口不对,直接报错,你不用管底层有没有人响应,直接把测试用例扔进去就行。 可是,这种设计也有个致命伤,就是要是链条忒长,它就成了一座孤岛。你给 `MyService` 加了一层 `AuthService`,再给 `AuthService` 加一层 `MessageQueue`,结局你连 `MessageQueue` 都说不清楚。你在 `YourController` 里注入 `MessageQueue`,但 `MyService` 根本不知道它是哪位绑的。`AuthService` 也不知道 `MessageQueue` 的存有。整个链条里每个人都蒙在鼓里,就像一群人在烂泥里打滚,哪位都没法看清整体结构。 这时候,大家就出现了两种截然不同的处理方式。一种是极端的,像某些老派架构师,坚持要用 `final` 锁死链条。他们情愿让系统臃肿,情愿让测试点点断,也要保证每个环节都无法被轻易修改。在他们眼里,`final` 就是真理,一切变动都是赌博。 另一种则是现代的大厂做法,也就是你上面说的依赖注入。你在 `MyService` 里注入 `AuthService` 和 `MessageQueue`,你就连不需求在代码里显式写调用链。`MyService` 只管接住请求,交给 `AuthService` 处理,拿到结局再交给 `MessageQueue` 异步处理。当你用 `@RequestLoop` 要么 `@Value` 注入进去时,`MyService` 根本不知道它下面挂了啥。它只知道“我要执行这个任务”。至于这个任务具体是哪位做的,没人管。 这就差了一层,那是“解耦”和“透明性”。 举个例子,刚刚那个 `MyService`,目前加上了 `MessageQueue`。当你调用 `MyService` 时,它内部会先问 `MyService` 到底该点啥。

然后它会问 `MessageQueue`:“我要发个消息?” `MessageQueue` 回复:“好的,我收到。” 然后 `MessageQueue` 会去后台拉数据,异步执行。整个过程 `MyService` 根本感知不到这段链路的存有。 再看一个例子,比如你在 `AuthService` 里调用了 `MyService`。`AuthService` 不需求关心 `MyService` 到底有没有依赖 `MessageQueue`。它只需求调用 `MyService` 的接口,拿到一个字符串结局。字符串里可能包含 `{"status": "success", "msg": ...}`。`AuthService` 拿到结局后,把状态码直接回给 `Controller`。`Controller` 收到结局,不管它是 `MessageQueue` 发回来的,还是 `MyService` 直接回的,它只管处理数据,传给视图。 这就构成了所谓的“透明调用”(Transparent Call)。你从 `Controller` 启动,调 `MyService`,再调 `AuthService`,再调 `MessageQueue`。中间的每一步都在各自的私有空间里运行,它们互不干扰,互不泄露。`MyService` 不知道 `AuthService` 有没有依赖 `MessageQueue`,`AuthService` 也不知道 `MessageQueue` 是如何运作的。它们就像沙漏里的沙子,各自流动,最终汇聚成河。 这种模式的核心在于“信任”和“封装”。你信任 `MyService` 是可靠的,信任 `AuthService` 是可靠的,也信任 `MessageQueue` 是可靠的。你不需求知道每一根链条背后到底有啥魔法,你只需求知道接口是这样的。 自然,这种“哑铃”链条(中间人透明)有一个风险,就是性能瓶颈。`MyService` 每调用一次 `AuthService`,就形成一次网络 RPC 请求。

要是 `MyService` 调用次数成千上万,每次都要去 RPC 调用 `AuthService`,那 `AuthService` 就成了一个庞大的瓶颈,并且每次调用都有额外的延迟。 故此,目前大量高性能系统(比如阿里、华为的某些核心模块)启动做“链中链”。

要是 `MessageQueue` 也依赖 `AuthService`,那 `MessageQueue` 内部又得有个 `AuthService` 来调用 `MyService`。

这样,`MyService` 就没有直接调用 `MessageQueue` 了,而是通过 `AuthService` 间接访问。

这样,`MessageQueue` 和 `MyService` 之间就没有了直接的 RPC 调用,性能就提上来了。 但这又引出了一个新的难题:要是链条忒细,哪位来证明 `MyService` 确实拿到了结局?要是 `MyService` 内部又调了 `AuthService`,而 `AuthService` 内部又调了 `MessageQueue`,`MyService` 拿到结局后,它如何知道调用链是通的? 这就涉及到“链头的证明”了。在 `MessageQueue` 发出异步消息后,它会等业务结局回来。业务结局从这里启动,要是 `AuthService` 和 `MyService` 的调用链被切断了,那么业务结局就一辈子拿不到。`MessageQueue` 就一辈子在等,它不知道调用链是不是还活着。它只能靠 `MyService` 内部知道了结局,要么靠外部监控(比如 JMS 的确认消息)来保证。 故此,这种“透明调用”模式,实际上是在“性能”和“可靠性”之间走钢丝。忒透明,性能好但可能不可靠;忒不透明,性能差但稳定。 这就回到了早期的 `Factory` 模式。`MyService` 是个工厂,它自己生成 `AuthService` 和 `MessageQueue`。`MyService` 内部直接持有了这两个对象的引用。

这样,`MyService` 内部就有整个的调用链了,`MyService` 知道它调了 `AuthService`,`AuthService` 知道它调了 `MessageQueue`。 这样的益处是:`MessageQueue` 不需求感知 `AuthService`,它只管发消息。`AuthService` 也不需求感知 `MyService`,它只管逻辑判断。

可是,`MyService` 自己手里拿着钥匙。

要是 `MyService` 挂了,要么被某个地方持有了副功能,整个链条就会彻底断开。 故此,`Factory` 和 `Dependency Injection` 实际上是同一个东西的不同叫法。`MyService` 就是那个工厂,它负责创建并持有链条里的所有成员。`Dependency Injection` 就是把创建的责任移到了其他地方,让 `MyService` 只负责持有。 在实际工程中,我们极少用 `final` 锁死链条,出于那样忒痛苦,维护成本高。我们更倾向于依赖注入。`MyService` 内部注入 `AuthService`,`MessageQueue` 也注入 `AuthService` 和 `MessageQueue` 自己。

这样,`AuthService` 内部再注入 `MyService` 和 `MessageQueue`。 这样,`MessageQueue` 就只知道 `MyService` 和 `AuthService` 存有,它不知道它们具体是哪位。`MyService` 内部直接持有 `AuthService` 和 `MessageQueue` 的引用。当你执行 `MyService` 时,它会自动调用 `AuthService`,拿到结局,再调用 `MessageQueue`。`MessageQueue` 收到消息,异步执行,最终回结局。 `MyService` 拿到结局后,它知道调用链是整个的,出于它是直接调用的。`MessageQueue` 拿到消息,它知道 `MyService` 收到了,出于它是异步回调。 这样,链条就内化了,`MyService` 和 `MessageQueue` 之间没有了直接的 RPC 调用。`MyService` 只依赖接口,`MessageQueue` 只依赖接口。`AuthService` 也同理。整个链条被“透明化”了,哪位都不知道下面是哪位,哪位只知道接口。 这种设计让系统变得更健壮,也更好办测试。你只需求测试 `MyService` 的接口,其他的细节都不需求关心。 自然,这种设计也有代价。内存占用可能变高,出于你需求持有多个引用。代码可读性可能会变差,出于 `MyService` 内部就是 `new AuthService() { ... }` 这种结构,看起来有点绕。 故此,这就是责任链模式的终极形态:一个由接口定义的、透明化的、异步的、解耦的调用链条。它不追求绝对的“最终状态”,它追求的是“调用”的整个性。

只要接口调用了,就认定是整个的;只要消息发出去了,就认定是成功的。 这种模式就像一条河流。上游的水源可能在几公里外,下游的河道可能在几百米。中间并没有人知道水是从哪来的,也没有人知道水流到了哪儿的源头。但这没关系,只要下游能用到水,上游能支撑下游,河流就通畅了。 这就是责任链的魅力,也是一种无奈。它用代码上的不清楚性,换取了系统架构上的清楚度。它告诉我们:有时候,我们不需求知道真相,只需求知道规则。

只要规则是透明的,真相就无所谓了。

只要接口对了,调用就对了。 最终的最终,这种链条不是死板的。它是活的。你能够随时在 `MyService` 里加一层 `Metrics`,在 `AuthService` 里加一层 `Logging`。

这些新的环没有纳入原有的链条,它们只是新增的节点。`MyService` 依然只依赖接口,`MessageQueue` 依然只依赖接口。新的环只负责监控和日志,不负责业务逻辑的流转。 这就是为啥现代微服务架构越来越喜爱这种“链上链”的方式。它准你快速构建复杂的业务逻辑,与此同时保持系统的稳定性和可扩展性。 总而言之,责任链模式并不是一个完美的解,它更像是一个妥协方案。它用“透明”换取了“灵活”,用“解耦”牺牲了“性能”和“可观测性”。但好在,它充足灵活,充足强大,充足支撑起那些庞大的、复杂的、日益增长的业务系统。它教会我们,有时候,能把难题变小,把链条缩短,把依赖压扁,才是解决难题的关键。