<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Dubbo on 牛哥聊技术</title><link>https://www.lingcoder.com/tags/dubbo/</link><description>Recent content in Dubbo on 牛哥聊技术</description><generator>Hugo -- gohugo.io</generator><language>zh</language><lastBuildDate>Mon, 19 Jul 2021 16:00:00 +0800</lastBuildDate><atom:link href="https://www.lingcoder.com/tags/dubbo/index.xml" rel="self" type="application/rss+xml"/><item><title>微服务通信用 OpenFeign 还是 Dubbo</title><link>https://www.lingcoder.com/p/openfeign-vs-dubbo/</link><pubDate>Mon, 19 Jul 2021 16:00:00 +0800</pubDate><guid>https://www.lingcoder.com/p/openfeign-vs-dubbo/</guid><description>&lt;img src="https://www.lingcoder.com/p/openfeign-vs-dubbo/cover.svg" alt="Featured image of post 微服务通信用 OpenFeign 还是 Dubbo" /&gt;&lt;h2 id="一句话先把问题摆出来"&gt;&lt;a href="#%e4%b8%80%e5%8f%a5%e8%af%9d%e5%85%88%e6%8a%8a%e9%97%ae%e9%a2%98%e6%91%86%e5%87%ba%e6%9d%a5" class="header-anchor"&gt;&lt;/a&gt;一句话先把问题摆出来
&lt;/h2&gt;
 &lt;blockquote&gt;
 &lt;p&gt;&lt;strong&gt;服务之间应该用 HTTP 还是 RPC 通信？&lt;/strong&gt;&lt;/p&gt;

 &lt;/blockquote&gt;
&lt;p&gt;这是任何一个 Java 微服务团队都绕不过去的选择。社区里两套生态各有铁粉：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Spring Cloud + OpenFeign&lt;/strong&gt;：基于 HTTP/REST，整套 Spring 全家桶最自然的延伸&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Apache Dubbo&lt;/strong&gt;：阿里开源的 RPC 框架，国内大厂的事实标准&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;新人选型时常常陷入一个误区——&lt;strong&gt;纠结于&amp;quot;谁性能好&amp;quot;&lt;/strong&gt;。但只看 QPS 这件事会让你错过更重要的因素：协议哲学、生态契合度、运维成本、组织能力。&lt;/p&gt;
&lt;p&gt;本文把这两套方案的差异、各自优劣、以及不同场景下的选型建议讲清楚。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="协议层http-与-rpc-的本质差异"&gt;&lt;a href="#%e5%8d%8f%e8%ae%ae%e5%b1%82http-%e4%b8%8e-rpc-%e7%9a%84%e6%9c%ac%e8%b4%a8%e5%b7%ae%e5%bc%82" class="header-anchor"&gt;&lt;/a&gt;协议层：HTTP 与 RPC 的本质差异
&lt;/h2&gt;&lt;h3 id="openfeignhttp-客户端的声明式"&gt;&lt;a href="#openfeignhttp-%e5%ae%a2%e6%88%b7%e7%ab%af%e7%9a%84%e5%a3%b0%e6%98%8e%e5%bc%8f" class="header-anchor"&gt;&lt;/a&gt;OpenFeign：HTTP 客户端的&amp;quot;声明式&amp;quot;
&lt;/h3&gt;&lt;p&gt;Feign 的本质是&lt;strong&gt;对 HTTP 客户端的封装&lt;/strong&gt;。你定义一个接口，加几个注解，Feign 用动态代理把方法调用翻译成 HTTP 请求：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;span class="lnt"&gt;5
&lt;/span&gt;&lt;span class="lnt"&gt;6
&lt;/span&gt;&lt;span class="lnt"&gt;7
&lt;/span&gt;&lt;span class="lnt"&gt;8
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-java" data-lang="java"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nd"&gt;@FeignClient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;order-service&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;public&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;interface&lt;/span&gt; &lt;span class="nc"&gt;OrderClient&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nd"&gt;@GetMapping&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;/orders/{id}&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;OrderVO&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;getById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nd"&gt;@PathVariable&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Long&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nd"&gt;@PostMapping&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;/orders&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;OrderVO&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nd"&gt;@RequestBody&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;OrderCreateDTO&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;dto&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;调用方写起来像调本地方法：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-java" data-lang="java"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;OrderVO&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;order&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;orderClient&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;1001L&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;但底层走的是标准 HTTP——序列化通常是 JSON，传输是文本，每次请求都有完整的 HTTP 头开销。&lt;strong&gt;所以 Feign 的本质就是一个&amp;quot;会的协议&amp;quot;
被打包成&amp;quot;省心的姿势&amp;quot;&lt;/strong&gt;。&lt;/p&gt;
&lt;h3 id="dubbo原生-rpc"&gt;&lt;a href="#dubbo%e5%8e%9f%e7%94%9f-rpc" class="header-anchor"&gt;&lt;/a&gt;Dubbo：原生 RPC
&lt;/h3&gt;&lt;p&gt;Dubbo 是为 RPC 而生的。客户端和服务端都是 Java，&lt;strong&gt;直接通过自定义的二进制协议（Dubbo 协议、Triple 协议等）传输 Java 对象&lt;/strong&gt;。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt; 1
&lt;/span&gt;&lt;span class="lnt"&gt; 2
&lt;/span&gt;&lt;span class="lnt"&gt; 3
&lt;/span&gt;&lt;span class="lnt"&gt; 4
&lt;/span&gt;&lt;span class="lnt"&gt; 5
&lt;/span&gt;&lt;span class="lnt"&gt; 6
&lt;/span&gt;&lt;span class="lnt"&gt; 7
&lt;/span&gt;&lt;span class="lnt"&gt; 8
&lt;/span&gt;&lt;span class="lnt"&gt; 9
&lt;/span&gt;&lt;span class="lnt"&gt;10
&lt;/span&gt;&lt;span class="lnt"&gt;11
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-java" data-lang="java"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// 服务端&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nd"&gt;@DubboService&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;public&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;OrderServiceImpl&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;implements&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;OrderService&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nd"&gt;@Override&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;public&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;OrderVO&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;getById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Long&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// 调用端&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nd"&gt;@DubboReference&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;private&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;OrderService&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;orderService&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;OrderVO&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;order&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;orderService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;1001L&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;注意——&lt;strong&gt;两端共享同一个接口&lt;/strong&gt;（Java interface），通过注册中心拉到地址后建立长连接。没有 URL，没有 HTTP 头，序列化默认是 Hessian
或 Kryo（紧凑二进制）。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="性能差距到底差多少"&gt;&lt;a href="#%e6%80%a7%e8%83%bd%e5%b7%ae%e8%b7%9d%e5%88%b0%e5%ba%95%e5%b7%ae%e5%a4%9a%e5%b0%91" class="header-anchor"&gt;&lt;/a&gt;性能差距：到底差多少
&lt;/h2&gt;&lt;p&gt;实测数据（同机房、千兆内网、相同业务复杂度）：&lt;/p&gt;
&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th style="text-align: left"&gt;指标&lt;/th&gt;
 &lt;th style="text-align: left"&gt;OpenFeign + Spring Cloud&lt;/th&gt;
 &lt;th style="text-align: left"&gt;Dubbo（默认 dubbo 协议）&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td style="text-align: left"&gt;单次 RTT&lt;/td&gt;
 &lt;td style="text-align: left"&gt;5-15 ms&lt;/td&gt;
 &lt;td style="text-align: left"&gt;1-3 ms&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td style="text-align: left"&gt;序列化大小&lt;/td&gt;
 &lt;td style="text-align: left"&gt;JSON，相对大&lt;/td&gt;
 &lt;td style="text-align: left"&gt;Hessian/Kryo，2-5 倍小&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td style="text-align: left"&gt;单连接 QPS&lt;/td&gt;
 &lt;td style="text-align: left"&gt;数千&lt;/td&gt;
 &lt;td style="text-align: left"&gt;数万&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td style="text-align: left"&gt;CPU 序列化开销&lt;/td&gt;
 &lt;td style="text-align: left"&gt;较高&lt;/td&gt;
 &lt;td style="text-align: left"&gt;较低&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;strong&gt;Dubbo 在性能上确实有显著优势&lt;/strong&gt;——但请注意三点：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;上述差距在&amp;quot;调用频率高、链路深&amp;quot;时才显著放大；多数业务一次接口调用 50-100ms，5ms 和 1ms 的差距完全淹没在业务里&lt;/li&gt;
&lt;li&gt;Feign 改用 HTTP/2 + Protobuf 就能逼近 RPC 性能（Spring 6 / Spring Cloud 2022+ 支持）&lt;/li&gt;
&lt;li&gt;性能问题大多在数据库、外部 API、缓存上，&lt;strong&gt;通信协议很少是瓶颈&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;

 &lt;blockquote&gt;
 &lt;p&gt;&lt;strong&gt;不建议把『性能』当成选 Dubbo 的唯一理由。&lt;/strong&gt; 如果你不能拿出一个真实的、被 Profiler 证实的、序列化耗时占主导的瓶颈，那性能差距对你来说几乎没意义。&lt;/p&gt;

 &lt;/blockquote&gt;
&lt;hr&gt;
&lt;h2 id="生态契合度谁配谁更顺手"&gt;&lt;a href="#%e7%94%9f%e6%80%81%e5%a5%91%e5%90%88%e5%ba%a6%e8%b0%81%e9%85%8d%e8%b0%81%e6%9b%b4%e9%a1%ba%e6%89%8b" class="header-anchor"&gt;&lt;/a&gt;生态契合度：谁配谁更顺手
&lt;/h2&gt;&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th style="text-align: left"&gt;维度&lt;/th&gt;
 &lt;th style="text-align: left"&gt;Spring Cloud + Feign&lt;/th&gt;
 &lt;th style="text-align: left"&gt;Dubbo&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td style="text-align: left"&gt;注册中心&lt;/td&gt;
 &lt;td style="text-align: left"&gt;Eureka / Nacos / Consul&lt;/td&gt;
 &lt;td style="text-align: left"&gt;Zookeeper / Nacos&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td style="text-align: left"&gt;配置中心&lt;/td&gt;
 &lt;td style="text-align: left"&gt;Spring Cloud Config / Nacos&lt;/td&gt;
 &lt;td style="text-align: left"&gt;Apollo / Nacos&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td style="text-align: left"&gt;网关&lt;/td&gt;
 &lt;td style="text-align: left"&gt;Spring Cloud Gateway&lt;/td&gt;
 &lt;td style="text-align: left"&gt;通常前置 Nginx + 业务网关&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td style="text-align: left"&gt;熔断&lt;/td&gt;
 &lt;td style="text-align: left"&gt;Resilience4j / Sentinel&lt;/td&gt;
 &lt;td style="text-align: left"&gt;Sentinel（原生集成）&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td style="text-align: left"&gt;链路追踪&lt;/td&gt;
 &lt;td style="text-align: left"&gt;Sleuth + Zipkin&lt;/td&gt;
 &lt;td style="text-align: left"&gt;Skywalking / 自建 Filter&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td style="text-align: left"&gt;服务治理&lt;/td&gt;
 &lt;td style="text-align: left"&gt;基础&lt;/td&gt;
 &lt;td style="text-align: left"&gt;强（路由、灰度、限流、调度都内置）&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td style="text-align: left"&gt;跨语言&lt;/td&gt;
 &lt;td style="text-align: left"&gt;天然 ✓（HTTP）&lt;/td&gt;
 &lt;td style="text-align: left"&gt;△（Triple 协议支持，gRPC 互通）&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td style="text-align: left"&gt;调试工具&lt;/td&gt;
 &lt;td style="text-align: left"&gt;Postman / curl 直接打&lt;/td&gt;
 &lt;td style="text-align: left"&gt;Telnet / Dubbo Admin&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Spring Cloud 的强项是&lt;strong&gt;全家桶生态完整&lt;/strong&gt;，Feign 只是其中一环；Dubbo 的强项是&lt;strong&gt;服务治理特性原生且强大&lt;/strong&gt;——做到 Spring Cloud
同等治理能力，需要拼接一堆组件。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="跨语言被低估的因素"&gt;&lt;a href="#%e8%b7%a8%e8%af%ad%e8%a8%80%e8%a2%ab%e4%bd%8e%e4%bc%b0%e7%9a%84%e5%9b%a0%e7%b4%a0" class="header-anchor"&gt;&lt;/a&gt;跨语言：被低估的因素
&lt;/h2&gt;&lt;p&gt;这是很多团队选型时&lt;strong&gt;最被低估的因素&lt;/strong&gt;。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Feign 走 HTTP/REST&lt;/strong&gt;：只要对方能发 HTTP 请求，谁都能调。Python、Go、Node 团队对接你的服务零门槛&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Dubbo 默认是 Java-only&lt;/strong&gt;：跨语言要切到 Triple 协议（基于 gRPC），配置和工具链都比 HTTP 复杂&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;如果你的组织是&lt;strong&gt;多语言团队&lt;/strong&gt;——前端 Node、数据团队 Python、运营内部系统是 PHP——选 Feign 几乎没有疑问。强行用 Dubbo
让其他语言对接，会成为长期摩擦。&lt;/p&gt;
&lt;p&gt;如果你的组织&lt;strong&gt;清一色 Java&lt;/strong&gt;，跨语言不是问题，那 Dubbo 的劣势就消失了。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="调试与可观测性"&gt;&lt;a href="#%e8%b0%83%e8%af%95%e4%b8%8e%e5%8f%af%e8%a7%82%e6%b5%8b%e6%80%a7" class="header-anchor"&gt;&lt;/a&gt;调试与可观测性
&lt;/h2&gt;&lt;h3 id="feign-优势"&gt;&lt;a href="#feign-%e4%bc%98%e5%8a%bf" class="header-anchor"&gt;&lt;/a&gt;Feign 优势
&lt;/h3&gt;&lt;ul&gt;
&lt;li&gt;HTTP 接口能直接用 Postman / curl 调试&lt;/li&gt;
&lt;li&gt;浏览器开发工具能看请求响应&lt;/li&gt;
&lt;li&gt;Nginx / 网关日志能直接读&lt;/li&gt;
&lt;li&gt;Tcpdump / Wireshark 抓包能看明文&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="dubbo-劣势"&gt;&lt;a href="#dubbo-%e5%8a%a3%e5%8a%bf" class="header-anchor"&gt;&lt;/a&gt;Dubbo 劣势
&lt;/h3&gt;&lt;ul&gt;
&lt;li&gt;二进制协议要专用工具（Dubbo Admin、Telnet 命令）&lt;/li&gt;
&lt;li&gt;Mock 和压测要起 Java 客户端&lt;/li&gt;
&lt;li&gt;排查 &amp;ldquo;客户端发了什么&amp;rdquo; 比 HTTP 麻烦得多&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;span class="lnt"&gt;5
&lt;/span&gt;&lt;span class="lnt"&gt;6
&lt;/span&gt;&lt;span class="lnt"&gt;7
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Feign 接口随时打：&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;curl http://order-service/orders/1001
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Dubbo 类似的事要用 telnet：&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;telnet 192.168.1.10 &lt;span class="m"&gt;20880&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;ls com.xx.OrderService
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;invoke com.xx.OrderService.getById&lt;span class="o"&gt;(&lt;/span&gt;1001&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;可观测性是长期运维的核心痛点——&lt;strong&gt;调试方便程度对开发体验的影响远超 5ms 的性能差距&lt;/strong&gt;。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="学习曲线与团队上手成本"&gt;&lt;a href="#%e5%ad%a6%e4%b9%a0%e6%9b%b2%e7%ba%bf%e4%b8%8e%e5%9b%a2%e9%98%9f%e4%b8%8a%e6%89%8b%e6%88%90%e6%9c%ac" class="header-anchor"&gt;&lt;/a&gt;学习曲线与团队上手成本
&lt;/h2&gt;&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th style="text-align: left"&gt;因素&lt;/th&gt;
 &lt;th style="text-align: left"&gt;Feign&lt;/th&gt;
 &lt;th style="text-align: left"&gt;Dubbo&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td style="text-align: left"&gt;新人上手&lt;/td&gt;
 &lt;td style="text-align: left"&gt;几乎零门槛（懂 HTTP 即可）&lt;/td&gt;
 &lt;td style="text-align: left"&gt;中等（注解 + 注册中心 + 协议）&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td style="text-align: left"&gt;大团队培训&lt;/td&gt;
 &lt;td style="text-align: left"&gt;简单&lt;/td&gt;
 &lt;td style="text-align: left"&gt;中等&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td style="text-align: left"&gt;调试体验&lt;/td&gt;
 &lt;td style="text-align: left"&gt;优&lt;/td&gt;
 &lt;td style="text-align: left"&gt;中&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td style="text-align: left"&gt;扩展定制&lt;/td&gt;
 &lt;td style="text-align: left"&gt;需懂 OkHttp / RestTemplate&lt;/td&gt;
 &lt;td style="text-align: left"&gt;需懂 Filter / Invoker SPI&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;新人入职第一周能用的是 Feign，三周后才能驾驭 Dubbo——团队规模一大，这个差距会成倍放大。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="服务治理深度dubbo-的硬实力"&gt;&lt;a href="#%e6%9c%8d%e5%8a%a1%e6%b2%bb%e7%90%86%e6%b7%b1%e5%ba%a6dubbo-%e7%9a%84%e7%a1%ac%e5%ae%9e%e5%8a%9b" class="header-anchor"&gt;&lt;/a&gt;服务治理深度：Dubbo 的硬实力
&lt;/h2&gt;&lt;p&gt;如果说性能是 Dubbo 被高估的优势，那&lt;strong&gt;服务治理&lt;/strong&gt;才是它真正的护城河。&lt;/p&gt;
&lt;p&gt;Dubbo 原生支持的能力清单（Spring Cloud 多数都要拼装实现）：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;多版本并行&lt;/strong&gt;：&lt;code&gt;@DubboReference(version = &amp;quot;1.0.0&amp;quot;)&lt;/code&gt; 直接路由到指定版本服务&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;基于条件的路由规则&lt;/strong&gt;：根据 Header / 参数 / 客户端 IP 路由到不同实例（灰度发布利器）&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;应用级 / 接口级粒度的治理&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;管理后台 Dubbo Admin&lt;/strong&gt; 可视化调整路由、权重、限流，无需改代码&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;服务分组&lt;/strong&gt;：环境/业务隔离&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;这些在 Spring Cloud 体系里要靠 Gateway + 自定义 Filter + Sentinel + Service Mesh 拼出来——能做，但维护成本高。&lt;/p&gt;
&lt;p&gt;如果你做的是&lt;strong&gt;复杂的内部多团队协作系统&lt;/strong&gt;（金融、电商核心链路），Dubbo 的治理能力会显著省心。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="选型决策树"&gt;&lt;a href="#%e9%80%89%e5%9e%8b%e5%86%b3%e7%ad%96%e6%a0%91" class="header-anchor"&gt;&lt;/a&gt;选型决策树
&lt;/h2&gt;&lt;pre class="mermaid" style="visibility:hidden"&gt;flowchart TD
 Start([选 Feign 还是 Dubbo?])
 Multi{多语言团队?}
 Start --&gt; Multi
 Multi --&gt;|是| Feign1[Feign / REST]
 Multi --&gt;|否，纯 Java| Scale{服务规模?}
 Scale --&gt;|小型，&lt; 20 服务| Feign2[Feign / REST]
 Scale --&gt;|中大型| Gov{是否需要复杂治理?&lt;br/&gt;多版本/灰度/路由}
 Gov --&gt;|不需要| Feign3[Feign 也够用]
 Gov --&gt;|需要| Perf{对单次 RPC 性能敏感?}
 Perf --&gt;|敏感| Dubbo1[Dubbo]
 Perf --&gt;|不敏感| Choice[基于团队熟悉度选]&lt;/pre&gt;&lt;hr&gt;
&lt;h2 id="一些被反复争论的话题"&gt;&lt;a href="#%e4%b8%80%e4%ba%9b%e8%a2%ab%e5%8f%8d%e5%a4%8d%e4%ba%89%e8%ae%ba%e7%9a%84%e8%af%9d%e9%a2%98" class="header-anchor"&gt;&lt;/a&gt;一些被反复争论的话题
&lt;/h2&gt;&lt;h3 id="争论-1spring-cloud-已经够强dubbo-没必要"&gt;&lt;a href="#%e4%ba%89%e8%ae%ba-1spring-cloud-%e5%b7%b2%e7%bb%8f%e5%a4%9f%e5%bc%badubbo-%e6%b2%a1%e5%bf%85%e8%a6%81" class="header-anchor"&gt;&lt;/a&gt;争论 1：Spring Cloud 已经够强，Dubbo 没必要
&lt;/h3&gt;&lt;p&gt;&lt;strong&gt;部分对&lt;/strong&gt;——如果你的业务复杂度还没碰到治理瓶颈，Spring Cloud 完全够。但有些治理特性（多版本、规则路由）原生 Spring Cloud
真的拼不出 Dubbo 那么顺手。&lt;/p&gt;
&lt;h3 id="争论-2dubbo-把简单事情复杂化"&gt;&lt;a href="#%e4%ba%89%e8%ae%ba-2dubbo-%e6%8a%8a%e7%ae%80%e5%8d%95%e4%ba%8b%e6%83%85%e5%a4%8d%e6%9d%82%e5%8c%96" class="header-anchor"&gt;&lt;/a&gt;争论 2：Dubbo 把简单事情复杂化
&lt;/h3&gt;&lt;p&gt;&lt;strong&gt;部分对&lt;/strong&gt;——Dubbo 的概念多（Provider/Consumer/Registry/Monitor，加上 Filter/Invoker/Cluster），新人确实一脸懵；但一旦上手，业务代码就和
Feign 一样简洁。&lt;/p&gt;
&lt;h3 id="争论-3现在该用-grpc"&gt;&lt;a href="#%e4%ba%89%e8%ae%ba-3%e7%8e%b0%e5%9c%a8%e8%af%a5%e7%94%a8-grpc" class="header-anchor"&gt;&lt;/a&gt;争论 3：现在该用 gRPC
&lt;/h3&gt;&lt;p&gt;gRPC 是个好选择，性能接近 Dubbo，跨语言天然支持。&lt;strong&gt;但生态在 Java 圈子相对薄弱&lt;/strong&gt;——和 Spring 集成、注册中心选型、可观测性都要自己组装。Triple
协议（Dubbo 3）就是兼容 gRPC 的，意图是把&amp;quot;跨语言 + Dubbo 治理&amp;quot;一并拿下。&lt;/p&gt;
&lt;h3 id="争论-4直接用-service-mesh"&gt;&lt;a href="#%e4%ba%89%e8%ae%ba-4%e7%9b%b4%e6%8e%a5%e7%94%a8-service-mesh" class="header-anchor"&gt;&lt;/a&gt;争论 4：直接用 Service Mesh
&lt;/h3&gt;&lt;p&gt;Mesh 把通信和治理下沉到 Sidecar，应用层只用最朴素的 HTTP/gRPC。&lt;strong&gt;理论上是终极方案&lt;/strong&gt;，但运维 K8s + Istio 的复杂度让多数中小团队望而却步。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="实战建议"&gt;&lt;a href="#%e5%ae%9e%e6%88%98%e5%bb%ba%e8%ae%ae" class="header-anchor"&gt;&lt;/a&gt;实战建议
&lt;/h2&gt;&lt;p&gt;我自己在团队里推的一份简版规则：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;新启动的中小项目&lt;/strong&gt;：直接 Spring Cloud + Feign。生态完整、上手快、调试方便，性能问题先不要担心&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;已有 Spring Cloud 体系的成熟团队&lt;/strong&gt;：继续用 Feign，需要更高性能时&lt;strong&gt;点对点改 gRPC&lt;/strong&gt;，不要整体迁 Dubbo&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;大量内部 Java 服务、纯 Java 技术栈、强治理需求&lt;/strong&gt;：Dubbo（或 Dubbo 3）。多版本、灰度路由这些原生能力会省心&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;有跨语言需求&lt;/strong&gt;：Feign / gRPC，不要 Dubbo（除非用 Triple）&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;绝不要&amp;quot;大力出奇迹&amp;quot;全家桶迁移&lt;/strong&gt;：从 Spring Cloud 全部迁到 Dubbo 是高成本低收益的事，&lt;strong&gt;点对点重构&lt;/strong&gt;才是务实做法&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;span class="lnt"&gt;5
&lt;/span&gt;&lt;span class="lnt"&gt;6
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-java" data-lang="java"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// 一个混合栈的例子：核心高频接口走 Dubbo，对外业务走 Feign&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nd"&gt;@FeignClient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;user-service&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;public&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;interface&lt;/span&gt; &lt;span class="nc"&gt;UserClient&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// 对外 / 跨语言&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nd"&gt;@DubboReference&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;version&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;1.0.0&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;private&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;InventoryService&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;inventory&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// 内部高频&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;混合用没什么问题，只要团队两套都熟。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="小结"&gt;&lt;a href="#%e5%b0%8f%e7%bb%93" class="header-anchor"&gt;&lt;/a&gt;小结
&lt;/h2&gt;&lt;p&gt;把全文压成一句话：&lt;/p&gt;

 &lt;blockquote&gt;
 &lt;p&gt;**OpenFeign 让通信变简单，Dubbo 让治理变强大。多数项目应当从 Feign 开始，治理瓶颈真正出现时再选择性引入 Dubbo——而不是反过来。
**&lt;/p&gt;

 &lt;/blockquote&gt;
&lt;p&gt;性能、生态、跨语言、调试、团队成本是五个真正影响选型的维度。&lt;strong&gt;别让&amp;quot;哪个性能好&amp;quot;这个伪问题误导你&lt;/strong&gt;
——通信协议很少是系统瓶颈，能让团队走得远的选择，永远是简单、可调试、生态完整的那一个。&lt;/p&gt;</description></item><item><title>理解 Java SPI：JDBC、SLF4J、Spring Boot 背后的机制</title><link>https://www.lingcoder.com/p/java-spi-mechanism/</link><pubDate>Tue, 22 Aug 2017 15:30:00 +0800</pubDate><guid>https://www.lingcoder.com/p/java-spi-mechanism/</guid><description>&lt;img src="https://www.lingcoder.com/p/java-spi-mechanism/cover.svg" alt="Featured image of post 理解 Java SPI：JDBC、SLF4J、Spring Boot 背后的机制" /&gt;&lt;h2 id="一个看似平淡的问题"&gt;&lt;a href="#%e4%b8%80%e4%b8%aa%e7%9c%8b%e4%bc%bc%e5%b9%b3%e6%b7%a1%e7%9a%84%e9%97%ae%e9%a2%98" class="header-anchor"&gt;&lt;/a&gt;一个看似平淡的问题
&lt;/h2&gt;&lt;p&gt;写过 Java 的人都用过 JDBC：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-java" data-lang="java"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;Class&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;forName&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;com.mysql.cj.jdbc.Driver&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// 早期写法&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;Connection&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;conn&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;DriverManager&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getConnection&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;但不少人没想过——&lt;strong&gt;&lt;code&gt;DriverManager&lt;/code&gt; 怎么知道要去加载 MySQL 的驱动？&lt;/strong&gt; 它代码里又没写 &lt;code&gt;import com.mysql...&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;你换成 PostgreSQL 时，&lt;strong&gt;只要 pom 里依赖换一下，DriverManager 就自动识别了 PG 的驱动&lt;/strong&gt;——没改一行 Java 代码。&lt;/p&gt;
&lt;p&gt;这背后的机制叫 &lt;strong&gt;SPI（Service Provider Interface）&lt;/strong&gt;。它是 JDK 留给框架开发者的&amp;quot;扩展点&amp;quot;，让框架定义接口、第三方提供实现、运行时自动发现并加载。&lt;/p&gt;
&lt;p&gt;理解 SPI 是看懂 JDBC、Servlet、SLF4J、Spring Boot 自动装配、Dubbo 等所有&amp;quot;自动加载实现&amp;quot;框架的钥匙。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="一spi-在做什么"&gt;&lt;a href="#%e4%b8%80spi-%e5%9c%a8%e5%81%9a%e4%bb%80%e4%b9%88" class="header-anchor"&gt;&lt;/a&gt;一、SPI 在做什么
&lt;/h2&gt;&lt;p&gt;API 与 SPI 的对比：&lt;/p&gt;
&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th style="text-align: left"&gt;&lt;/th&gt;
 &lt;th style="text-align: left"&gt;API（Application Programming Interface）&lt;/th&gt;
 &lt;th style="text-align: left"&gt;SPI（Service Provider Interface）&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td style="text-align: left"&gt;调用方向&lt;/td&gt;
 &lt;td style="text-align: left"&gt;应用调框架&lt;/td&gt;
 &lt;td style="text-align: left"&gt;框架调应用&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td style="text-align: left"&gt;谁定义接口&lt;/td&gt;
 &lt;td style="text-align: left"&gt;框架&lt;/td&gt;
 &lt;td style="text-align: left"&gt;框架&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td style="text-align: left"&gt;谁实现接口&lt;/td&gt;
 &lt;td style="text-align: left"&gt;框架&lt;/td&gt;
 &lt;td style="text-align: left"&gt;应用 / 第三方&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td style="text-align: left"&gt;例子&lt;/td&gt;
 &lt;td style="text-align: left"&gt;Math.max() / List.add()&lt;/td&gt;
 &lt;td style="text-align: left"&gt;JDBC Driver / SLF4J Logger 实现 / 编解码器&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;strong&gt;最简洁的理解：API 是『你调我』，SPI 是『我调你』&lt;/strong&gt;。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="二jdk-原生-spiserviceloader"&gt;&lt;a href="#%e4%ba%8cjdk-%e5%8e%9f%e7%94%9f-spiserviceloader" class="header-anchor"&gt;&lt;/a&gt;二、JDK 原生 SPI：ServiceLoader
&lt;/h2&gt;&lt;p&gt;JDK 提供的最朴素 SPI 工具——&lt;code&gt;java.util.ServiceLoader&lt;/code&gt;。三步：&lt;/p&gt;
&lt;h3 id="1-框架定义接口"&gt;&lt;a href="#1-%e6%a1%86%e6%9e%b6%e5%ae%9a%e4%b9%89%e6%8e%a5%e5%8f%a3" class="header-anchor"&gt;&lt;/a&gt;1. 框架定义接口
&lt;/h3&gt;&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;span class="lnt"&gt;5
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-java" data-lang="java"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;package&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;org.example.payment&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;public&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;interface&lt;/span&gt; &lt;span class="nc"&gt;PaymentChannel&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;String&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;getName&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;boolean&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;charge&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;String&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;BigDecimal&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;h3 id="2-应用第三方写实现"&gt;&lt;a href="#2-%e5%ba%94%e7%94%a8%e7%ac%ac%e4%b8%89%e6%96%b9%e5%86%99%e5%ae%9e%e7%8e%b0" class="header-anchor"&gt;&lt;/a&gt;2. 应用/第三方写实现
&lt;/h3&gt;&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;span class="lnt"&gt;5
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-java" data-lang="java"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;package&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;com.alipay.spi&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;public&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;AlipayChannel&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;implements&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;PaymentChannel&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nd"&gt;@Override&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;public&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;String&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;getName&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;alipay&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nd"&gt;@Override&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;public&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;boolean&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;charge&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;String&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;u&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;BigDecimal&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;h3 id="3-在-meta-infservices-下声明"&gt;&lt;a href="#3-%e5%9c%a8-meta-infservices-%e4%b8%8b%e5%a3%b0%e6%98%8e" class="header-anchor"&gt;&lt;/a&gt;3. 在 META-INF/services 下声明
&lt;/h3&gt;&lt;p&gt;文件名：&lt;code&gt;META-INF/services/org.example.payment.PaymentChannel&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;文件内容：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;com.alipay.spi.AlipayChannel
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;com.wechat.spi.WechatChannel
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;h3 id="框架加载所有实现"&gt;&lt;a href="#%e6%a1%86%e6%9e%b6%e5%8a%a0%e8%bd%bd%e6%89%80%e6%9c%89%e5%ae%9e%e7%8e%b0" class="header-anchor"&gt;&lt;/a&gt;框架加载所有实现
&lt;/h3&gt;&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-java" data-lang="java"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;ServiceLoader&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;PaymentChannel&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;loader&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ServiceLoader&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;load&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;PaymentChannel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;PaymentChannel&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;loader&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;System&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;out&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;found: &amp;#34;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getName&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;&lt;strong&gt;只要 classpath 里有对应实现的 jar，就自动被发现&lt;/strong&gt;——这就是 JDBC 不需要写 &lt;code&gt;Class.forName&lt;/code&gt; 也能加载驱动的秘密。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="三jdbc-40-后的-spi-实践"&gt;&lt;a href="#%e4%b8%89jdbc-40-%e5%90%8e%e7%9a%84-spi-%e5%ae%9e%e8%b7%b5" class="header-anchor"&gt;&lt;/a&gt;三、JDBC 4.0 后的 SPI 实践
&lt;/h2&gt;&lt;p&gt;JDBC 4.0 之前，每次都要 &lt;code&gt;Class.forName(&amp;quot;com.mysql.jdbc.Driver&amp;quot;)&lt;/code&gt;——&lt;strong&gt;靠类初始化触发驱动注册&lt;/strong&gt;：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-java" data-lang="java"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// MySQL Driver 类内部&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;static&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;DriverManager&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;registerDriver&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Driver&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;JDBC 4.0 之后，MySQL 驱动 jar 里加了：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;META-INF/services/java.sql.Driver
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;└─ com.mysql.cj.jdbc.Driver
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;&lt;code&gt;DriverManager&lt;/code&gt; 启动时用 &lt;code&gt;ServiceLoader&lt;/code&gt; 自动找所有 &lt;code&gt;Driver&lt;/code&gt; 实现，&lt;strong&gt;&lt;code&gt;Class.forName&lt;/code&gt; 这行代码就成了历史遗物&lt;/strong&gt;。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="四serviceloader-的几个限制"&gt;&lt;a href="#%e5%9b%9bserviceloader-%e7%9a%84%e5%87%a0%e4%b8%aa%e9%99%90%e5%88%b6" class="header-anchor"&gt;&lt;/a&gt;四、ServiceLoader 的几个限制
&lt;/h2&gt;&lt;h3 id="1-加载所有实现不能选择性加载"&gt;&lt;a href="#1-%e5%8a%a0%e8%bd%bd%e6%89%80%e6%9c%89%e5%ae%9e%e7%8e%b0%e4%b8%8d%e8%83%bd%e9%80%89%e6%8b%a9%e6%80%a7%e5%8a%a0%e8%bd%bd" class="header-anchor"&gt;&lt;/a&gt;1. 加载所有实现，不能选择性加载
&lt;/h3&gt;&lt;p&gt;&lt;code&gt;ServiceLoader.load()&lt;/code&gt; 会&lt;strong&gt;实例化所有实现&lt;/strong&gt;——10 个支付通道都用不上你只想要支付宝？没办法，全部都会被 new 出来。&lt;/p&gt;
&lt;h3 id="2-实现必须有无参构造器"&gt;&lt;a href="#2-%e5%ae%9e%e7%8e%b0%e5%bf%85%e9%a1%bb%e6%9c%89%e6%97%a0%e5%8f%82%e6%9e%84%e9%80%a0%e5%99%a8" class="header-anchor"&gt;&lt;/a&gt;2. 实现必须有无参构造器
&lt;/h3&gt;&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-java" data-lang="java"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;public&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;AlipayChannel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;String&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;appId&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// ❌ 不行&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;public&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;AlipayChannel&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// ✓&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;需要参数？只能后续 setter 注入。&lt;/p&gt;
&lt;h3 id="3-没有按-key-取实现的能力"&gt;&lt;a href="#3-%e6%b2%a1%e6%9c%89%e6%8c%89-key-%e5%8f%96%e5%ae%9e%e7%8e%b0%e7%9a%84%e8%83%bd%e5%8a%9b" class="header-anchor"&gt;&lt;/a&gt;3. 没有&amp;quot;按 key 取实现&amp;quot;的能力
&lt;/h3&gt;&lt;p&gt;业务里通常是&amp;quot;知道用户选了 alipay，加载支付宝实现&amp;quot;。&lt;code&gt;ServiceLoader&lt;/code&gt; 只能一股脑把所有实现都返回，再让你自己按 &lt;code&gt;getName()&lt;/code&gt; 过滤。&lt;/p&gt;
&lt;h3 id="4-不支持-ioc-注入"&gt;&lt;a href="#4-%e4%b8%8d%e6%94%af%e6%8c%81-ioc-%e6%b3%a8%e5%85%a5" class="header-anchor"&gt;&lt;/a&gt;4. 不支持 IoC 注入
&lt;/h3&gt;&lt;p&gt;实现里如果想 &lt;code&gt;@Autowired&lt;/code&gt; 其他 Bean？不行——&lt;code&gt;ServiceLoader&lt;/code&gt; 不知道 Spring 容器存在。&lt;/p&gt;
&lt;h3 id="5-类加载器问题"&gt;&lt;a href="#5-%e7%b1%bb%e5%8a%a0%e8%bd%bd%e5%99%a8%e9%97%ae%e9%a2%98" class="header-anchor"&gt;&lt;/a&gt;5. 类加载器问题
&lt;/h3&gt;&lt;p&gt;线程上下文 ClassLoader 是个老大难——容器化部署、OSGi 等场景下 &lt;code&gt;ServiceLoader.load()&lt;/code&gt; 看不到某些 jar 的实现。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="五dubbo-spijdk-spi-的强化版"&gt;&lt;a href="#%e4%ba%94dubbo-spijdk-spi-%e7%9a%84%e5%bc%ba%e5%8c%96%e7%89%88" class="header-anchor"&gt;&lt;/a&gt;五、Dubbo SPI：JDK SPI 的强化版
&lt;/h2&gt;&lt;p&gt;Dubbo 团队觉得 JDK SPI 不够用，自己写了一套 SPI——位置一样在 &lt;code&gt;META-INF/services&lt;/code&gt; / &lt;code&gt;META-INF/dubbo&lt;/code&gt; 下，但能力翻倍。&lt;/p&gt;
&lt;h3 id="文件格式key--实现类"&gt;&lt;a href="#%e6%96%87%e4%bb%b6%e6%a0%bc%e5%bc%8fkey--%e5%ae%9e%e7%8e%b0%e7%b1%bb" class="header-anchor"&gt;&lt;/a&gt;文件格式：&lt;code&gt;key = 实现类&lt;/code&gt;
&lt;/h3&gt;&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;# META-INF/dubbo/org.apache.dubbo.rpc.Protocol
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;dubbo = org.apache.dubbo.rpc.protocol.dubbo.DubboProtocol
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;http = org.apache.dubbo.rpc.protocol.http.HttpProtocol
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;hessian = org.apache.dubbo.rpc.protocol.hessian.HessianProtocol
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;h3 id="按-key-取实现"&gt;&lt;a href="#%e6%8c%89-key-%e5%8f%96%e5%ae%9e%e7%8e%b0" class="header-anchor"&gt;&lt;/a&gt;按 key 取实现
&lt;/h3&gt;&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-java" data-lang="java"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;ExtensionLoader&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Protocol&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;loader&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ExtensionLoader&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getExtensionLoader&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Protocol&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;Protocol&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;dubbo&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;loader&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getExtension&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;dubbo&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// ★ 按 key 加载&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;不需要把所有实现都 new 一遍。&lt;/p&gt;
&lt;h3 id="自适应扩展adaptive"&gt;&lt;a href="#%e8%87%aa%e9%80%82%e5%ba%94%e6%89%a9%e5%b1%95adaptive" class="header-anchor"&gt;&lt;/a&gt;自适应扩展（Adaptive）
&lt;/h3&gt;&lt;p&gt;最强大的特性——&lt;strong&gt;根据运行时 URL 参数自动选择实现&lt;/strong&gt;：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;span class="lnt"&gt;5
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-java" data-lang="java"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nd"&gt;@Adaptive&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;public&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;interface&lt;/span&gt; &lt;span class="nc"&gt;Protocol&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Exporter&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;export&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Invoker&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;invoker&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;throws&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;RpcException&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Invoker&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;refer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Class&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;type&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;URL&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;调用时根据 &lt;code&gt;url.protocol&lt;/code&gt; 决定具体走哪个实现，&lt;strong&gt;业务代码完全不感知&lt;/strong&gt;。这是 Dubbo 路由、协议适配、序列化适配等很多机制的底座。&lt;/p&gt;
&lt;h3 id="依赖注入"&gt;&lt;a href="#%e4%be%9d%e8%b5%96%e6%b3%a8%e5%85%a5" class="header-anchor"&gt;&lt;/a&gt;依赖注入
&lt;/h3&gt;&lt;p&gt;Dubbo SPI 加载实现时会自动注入它依赖的其他 SPI 实现——&lt;strong&gt;有简化版 IoC&lt;/strong&gt;：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-java" data-lang="java"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;public&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;DubboProtocol&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;implements&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Protocol&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;private&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Codec&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;codec&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// 自动按类型注入&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;public&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;setCodec&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Codec&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;codec&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;codec&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;codec&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;h3 id="ioc--aop"&gt;&lt;a href="#ioc--aop" class="header-anchor"&gt;&lt;/a&gt;IOC + AOP
&lt;/h3&gt;&lt;p&gt;加上 Wrapper 机制，能在所有实现外包一层&amp;quot;拦截器&amp;quot;——这就是 Dubbo 链式 Filter 的原理。&lt;/p&gt;
&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th style="text-align: left"&gt;&lt;/th&gt;
 &lt;th style="text-align: left"&gt;JDK SPI&lt;/th&gt;
 &lt;th style="text-align: left"&gt;Dubbo SPI&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td style="text-align: left"&gt;按 key 加载&lt;/td&gt;
 &lt;td style="text-align: left"&gt;✗&lt;/td&gt;
 &lt;td style="text-align: left"&gt;✓&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td style="text-align: left"&gt;懒加载实现&lt;/td&gt;
 &lt;td style="text-align: left"&gt;✗&lt;/td&gt;
 &lt;td style="text-align: left"&gt;✓&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td style="text-align: left"&gt;依赖注入&lt;/td&gt;
 &lt;td style="text-align: left"&gt;✗&lt;/td&gt;
 &lt;td style="text-align: left"&gt;✓&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td style="text-align: left"&gt;AOP 包装&lt;/td&gt;
 &lt;td style="text-align: left"&gt;✗&lt;/td&gt;
 &lt;td style="text-align: left"&gt;✓&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td style="text-align: left"&gt;自适应扩展&lt;/td&gt;
 &lt;td style="text-align: left"&gt;✗&lt;/td&gt;
 &lt;td style="text-align: left"&gt;✓&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;hr&gt;
&lt;h2 id="六spring-factoriesspring-自己的-spi"&gt;&lt;a href="#%e5%85%adspring-factoriesspring-%e8%87%aa%e5%b7%b1%e7%9a%84-spi" class="header-anchor"&gt;&lt;/a&gt;六、Spring Factories：Spring 自己的 SPI
&lt;/h2&gt;&lt;p&gt;Spring Boot 也实现了类似的 SPI——&lt;code&gt;spring.factories&lt;/code&gt;：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;span class="lnt"&gt;5
&lt;/span&gt;&lt;span class="lnt"&gt;6
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;# META-INF/spring.factories
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; com.example.MyAutoConfiguration
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;org.springframework.boot.env.EnvironmentPostProcessor=\
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; com.example.MyEnvProcessor
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Spring Boot 启动时会扫描所有 jar 的 &lt;code&gt;spring.factories&lt;/code&gt;，按 key 加载对应类——&lt;strong&gt;这就是 starter 自动装配的本质&lt;/strong&gt;。&lt;/p&gt;

 &lt;blockquote&gt;
 &lt;p&gt;Spring Boot 2.7 后逐步迁移到 &lt;code&gt;META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports&lt;/code&gt;
，理由是按&amp;quot;接口名 = 文件名&amp;quot;的方式更清晰。&lt;/p&gt;

 &lt;/blockquote&gt;
&lt;p&gt;总结这几套 SPI 的演进：&lt;/p&gt;
&lt;pre class="mermaid" style="visibility:hidden"&gt;flowchart LR
 JDK["JDK ServiceLoader&lt;br/&gt;(全加载, 无 key)"]
 Spring["Spring Factories&lt;br/&gt;(按 key 配置)"]
 Dubbo["Dubbo SPI&lt;br/&gt;(IoC + AOP + 自适应)"]

 JDK --&gt; Spring --&gt; Dubbo&lt;/pre&gt;&lt;hr&gt;
&lt;h2 id="七写一个自己的-spi-框架极简版"&gt;&lt;a href="#%e4%b8%83%e5%86%99%e4%b8%80%e4%b8%aa%e8%87%aa%e5%b7%b1%e7%9a%84-spi-%e6%a1%86%e6%9e%b6%e6%9e%81%e7%ae%80%e7%89%88" class="header-anchor"&gt;&lt;/a&gt;七、写一个自己的 SPI 框架（极简版）
&lt;/h2&gt;&lt;p&gt;理解 SPI 最快的方式是自己写一个。来个最小可工作版本：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt; 1
&lt;/span&gt;&lt;span class="lnt"&gt; 2
&lt;/span&gt;&lt;span class="lnt"&gt; 3
&lt;/span&gt;&lt;span class="lnt"&gt; 4
&lt;/span&gt;&lt;span class="lnt"&gt; 5
&lt;/span&gt;&lt;span class="lnt"&gt; 6
&lt;/span&gt;&lt;span class="lnt"&gt; 7
&lt;/span&gt;&lt;span class="lnt"&gt; 8
&lt;/span&gt;&lt;span class="lnt"&gt; 9
&lt;/span&gt;&lt;span class="lnt"&gt;10
&lt;/span&gt;&lt;span class="lnt"&gt;11
&lt;/span&gt;&lt;span class="lnt"&gt;12
&lt;/span&gt;&lt;span class="lnt"&gt;13
&lt;/span&gt;&lt;span class="lnt"&gt;14
&lt;/span&gt;&lt;span class="lnt"&gt;15
&lt;/span&gt;&lt;span class="lnt"&gt;16
&lt;/span&gt;&lt;span class="lnt"&gt;17
&lt;/span&gt;&lt;span class="lnt"&gt;18
&lt;/span&gt;&lt;span class="lnt"&gt;19
&lt;/span&gt;&lt;span class="lnt"&gt;20
&lt;/span&gt;&lt;span class="lnt"&gt;21
&lt;/span&gt;&lt;span class="lnt"&gt;22
&lt;/span&gt;&lt;span class="lnt"&gt;23
&lt;/span&gt;&lt;span class="lnt"&gt;24
&lt;/span&gt;&lt;span class="lnt"&gt;25
&lt;/span&gt;&lt;span class="lnt"&gt;26
&lt;/span&gt;&lt;span class="lnt"&gt;27
&lt;/span&gt;&lt;span class="lnt"&gt;28
&lt;/span&gt;&lt;span class="lnt"&gt;29
&lt;/span&gt;&lt;span class="lnt"&gt;30
&lt;/span&gt;&lt;span class="lnt"&gt;31
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-java" data-lang="java"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;public&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;MySpi&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;private&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;static&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;final&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Map&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Class&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;?&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Map&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Object&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;CACHE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;HashMap&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;public&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;static&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;load&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Class&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;type&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;String&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Map&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Object&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;map&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;CACHE&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;computeIfAbsent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;type&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;MySpi&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;loadAll&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;map&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;private&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;static&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Map&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Object&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;loadAll&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Class&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;?&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;type&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Map&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Object&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;map&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;HashMap&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;String&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;META-INF/myspi/&amp;#34;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;type&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getName&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Enumeration&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;URL&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;urls&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;MySpi&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getClassLoader&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="na"&gt;getResources&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;while&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;urls&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;hasMoreElements&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;BufferedReader&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;br&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;BufferedReader&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;InputStreamReader&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;urls&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;nextElement&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="na"&gt;openStream&lt;/span&gt;&lt;span class="p"&gt;())))&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;String&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;line&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;while&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;line&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;br&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;readLine&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// 格式：key=class&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;String&lt;/span&gt;&lt;span class="o"&gt;[]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;kv&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;line&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;=&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;2&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Class&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;?&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;impl&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Class&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;forName&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;kv&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="n"&gt;1&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;trim&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;map&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;put&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;kv&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="n"&gt;0&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;trim&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;impl&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getDeclaredConstructor&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="na"&gt;newInstance&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;catch&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Exception&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;throw&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;RuntimeException&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;map&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;使用：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;# META-INF/myspi/org.example.PaymentChannel
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;alipay = com.alipay.AlipayChannel
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;wechat = com.wechat.WechatChannel
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-java" data-lang="java"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;PaymentChannel&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;MySpi&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;load&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;PaymentChannel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;alipay&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;charge&lt;/span&gt;&lt;span class="p"&gt;(...);&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;加上&amp;quot;懒加载 + IoC + AOP&amp;quot;就是 Dubbo 那一套——&lt;strong&gt;核心思想都是&amp;quot;配置文件声明 + ClassLoader 加载&amp;quot;&lt;/strong&gt;。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="八spi-的常见踩坑"&gt;&lt;a href="#%e5%85%abspi-%e7%9a%84%e5%b8%b8%e8%a7%81%e8%b8%a9%e5%9d%91" class="header-anchor"&gt;&lt;/a&gt;八、SPI 的常见踩坑
&lt;/h2&gt;&lt;h3 id="1-文件路径写错"&gt;&lt;a href="#1-%e6%96%87%e4%bb%b6%e8%b7%af%e5%be%84%e5%86%99%e9%94%99" class="header-anchor"&gt;&lt;/a&gt;1. 文件路径写错
&lt;/h3&gt;&lt;p&gt;&lt;code&gt;META-INF/services/全限定接口名&lt;/code&gt;——文件名要&lt;strong&gt;完整包名&lt;/strong&gt;，不是简单名字。少一个 &lt;code&gt;org.example.&lt;/code&gt; 就什么都加载不到。&lt;/p&gt;
&lt;h3 id="2-文件-bom"&gt;&lt;a href="#2-%e6%96%87%e4%bb%b6-bom" class="header-anchor"&gt;&lt;/a&gt;2. 文件 BOM
&lt;/h3&gt;&lt;p&gt;UTF-8 with BOM 在某些 JDK 版本里会被读成&amp;quot;乱码包名&amp;quot;。&lt;strong&gt;用纯 UTF-8 不带 BOM&lt;/strong&gt; 保存。&lt;/p&gt;
&lt;h3 id="3-实现类必须-public-无参构造器"&gt;&lt;a href="#3-%e5%ae%9e%e7%8e%b0%e7%b1%bb%e5%bf%85%e9%a1%bb-public-%e6%97%a0%e5%8f%82%e6%9e%84%e9%80%a0%e5%99%a8" class="header-anchor"&gt;&lt;/a&gt;3. 实现类必须 public 无参构造器
&lt;/h3&gt;&lt;p&gt;&lt;code&gt;AbstractXxxFactory&lt;/code&gt; 这种抽象类、private 构造器、没有无参构造器都加载失败。&lt;/p&gt;
&lt;h3 id="4-类加载器隔离"&gt;&lt;a href="#4-%e7%b1%bb%e5%8a%a0%e8%bd%bd%e5%99%a8%e9%9a%94%e7%a6%bb" class="header-anchor"&gt;&lt;/a&gt;4. 类加载器隔离
&lt;/h3&gt;&lt;p&gt;容器化部署时，框架代码可能用不同的 ClassLoader——&lt;code&gt;ServiceLoader.load(Class)&lt;/code&gt; 默认用调用线程的
ContextClassLoader，复杂场景要显式指定。&lt;/p&gt;
&lt;h3 id="5-spi-失败静默"&gt;&lt;a href="#5-spi-%e5%a4%b1%e8%b4%a5%e9%9d%99%e9%bb%98" class="header-anchor"&gt;&lt;/a&gt;5. SPI 失败静默
&lt;/h3&gt;&lt;p&gt;&lt;code&gt;ServiceLoader&lt;/code&gt; 加载失败默认&lt;strong&gt;抛出 &lt;code&gt;ServiceConfigurationError&lt;/code&gt;&lt;/strong&gt;，但&lt;strong&gt;异常往往被框架吞了&lt;/strong&gt;。生产排查时需要打开 SPI 加载的
DEBUG 日志。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="九什么场景该自己用-spi"&gt;&lt;a href="#%e4%b9%9d%e4%bb%80%e4%b9%88%e5%9c%ba%e6%99%af%e8%af%a5%e8%87%aa%e5%b7%b1%e7%94%a8-spi" class="header-anchor"&gt;&lt;/a&gt;九、什么场景该自己用 SPI
&lt;/h2&gt;&lt;p&gt;不要为了&amp;quot;看起来高级&amp;quot;而用 SPI——&lt;strong&gt;只有真正需要&amp;quot;框架代码不知道实现，但运行时能加载&amp;quot;时才值得&lt;/strong&gt;：&lt;/p&gt;
&lt;p&gt;✅ 适合 SPI 的场景：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;写一个&lt;strong&gt;插件式框架&lt;/strong&gt;——核心库定义接口，第三方提供实现&lt;/li&gt;
&lt;li&gt;写一个&lt;strong&gt;SDK&lt;/strong&gt;——支持多种序列化、多种存储后端&lt;/li&gt;
&lt;li&gt;写一个&lt;strong&gt;多通道适配器&lt;/strong&gt;——支付、消息推送、对象存储&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;❌ 不适合 SPI 的场景：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;业务逻辑分发——直接用 &lt;code&gt;Map&amp;lt;String, Strategy&amp;gt;&lt;/code&gt; 注入更清晰&lt;/li&gt;
&lt;li&gt;简单的策略模式——枚举 + 工厂方法更简单&lt;/li&gt;
&lt;li&gt;编译时已知所有实现——用接口注入就够了&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;记住一条：&amp;quot;&lt;strong&gt;SPI 是给框架开发者的工具&lt;/strong&gt;&amp;quot;——业务开发用 Spring 的 &lt;code&gt;Map&amp;lt;String, T&amp;gt;&lt;/code&gt; 注入或者一个普通工厂类就够了。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="小结"&gt;&lt;a href="#%e5%b0%8f%e7%bb%93" class="header-anchor"&gt;&lt;/a&gt;小结
&lt;/h2&gt;&lt;p&gt;把全文压一句：&lt;/p&gt;

 &lt;blockquote&gt;
 &lt;p&gt;&lt;strong&gt;SPI 把&amp;quot;框架 → 实现&amp;quot;的依赖反转过来——框架定义接口，运行时由配置文件决定加载哪些实现。&lt;/strong&gt;&lt;/p&gt;

 &lt;/blockquote&gt;
&lt;p&gt;理解 SPI 的几个层次：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;JDK ServiceLoader&lt;/strong&gt;：最朴素，全加载&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Spring Factories&lt;/strong&gt;：按 key 配置，能自动装配&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Dubbo SPI&lt;/strong&gt;：加上 IoC、AOP、自适应——可扩展性的天花板&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;工程上记住：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;写框架/SDK 时考虑 SPI&lt;/li&gt;
&lt;li&gt;业务里别滥用，简单策略用 Map 注入更清爽&lt;/li&gt;
&lt;li&gt;文件路径、命名、字符编码三个坑特别多&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;把 SPI 看懂，你看 JDBC、Servlet、SLF4J、Spring Boot 自动装配的底层原理时&lt;strong&gt;会突然有种『原来都是这一套』的顿悟感&lt;/strong&gt;。&lt;/p&gt;</description></item></channel></rss>