
Nacos 的 SPI 扩展点有 12 个你只需要知道这 5 个插件机制从原理到优先级排序文章目录Nacos 的 SPI 扩展点有 12 个你只需要知道这 5 个插件机制从原理到优先级排序你想改 Nacos 的行为不用改它的代码Java SPI vs Nacos SPIJava SPI 的工作方式Nacos SPI 的工作方式扩展点加载流程源码走读NacosServiceLoader 的核心方法加载时序排序机制order 值怎么确定Nacos 的 12 个扩展点你只需要知道这 5 个扩展点的插件目录一张图带走Nacos SPI 加载全流程不适用本文描述的情况你想改 Nacos 的行为不用改它的代码去年有个客户要在 Nacos 里接 LDAP 认证。团队里有人第一反应是 fork 一份 Nacos 源码改掉认证逻辑然后自己编译一个版本。不用。Nacos 从 1.4.0 开始引入 SPI 插件机制。认证、健康检查、数据源、配置变更通知——这些核心功能都有扩展点。你只需要写一个实现类打个 jar 包放到 Nacos 的 plugins 目录下。Nacos 的 SPI 机制和 JDK 原生的 SPI 不一样。JDK SPI 用ServiceLoader加载所有实现类都会被实例化不能排序、不能按条件加载。Nacos 自己实现了一套NacosServiceLoader支持优先级排序、条件过滤、单例管理。这篇文章拆开 Nacos SPI 的加载流程源码搞清楚它和 Java SPI 的区别、扩展点的优先级怎么排、你写插件时需要知道的最小接口集合。⚠️ 本文基于 Nacos Server 2.3.x。SPI 插件机制从 1.4.0 开始引入2.0.0 后扩展了鉴权插件和数据源插件接口。如果你用的是 1.4.x可用的扩展点比 2.x 少。Java SPI vs Nacos SPIJava SPI 的工作方式// 接口publicinterfacePayment{voidpay();}// 实现类publicclassAlipayPaymentimplementsPayment{...}// 配置文件META-INF/services/com.example.Paymentcom.example.AlipayPayment// 加载ServiceLoaderPaymentloaderServiceLoader.load(Payment.class);for(Paymentp:loader){p.pay();// 所有实现类都会被实例化和遍历}Java SPI 的问题全部实例化。不管你用不用ServiceLoader 会把所有实现类都 new 一遍。无法排序。多个实现类的加载顺序取决于 classpath不可控。无法按条件过滤。不能根据运行时参数只加载某一个实现。无生命周期管理。加载后没有统一的销毁机制。Nacos SPI 的工作方式// NacosServiceLoader 的加载NacosServiceLoaderIdentityContextBuilderloaderNacosServiceLoader.newServiceLoader(IdentityContextBuilder.class);// 获取所有实现按 order 排序CollectionIdentityContextBuilderbuildersloader.getSingletonServiceList();// 取第一个order 值最小的IdentityContextBuilderbuilderbuilders.iterator().next();特性Java SPINacos SPI加载方式ServiceLoader.load()NacosServiceLoader.newServiceLoader()实例化策略全量实例化按需实例化 单例缓存排序无classpath 顺序Order注解 或getOrder()方法过滤无按type属性过滤配置文件META-INF/services/META-INF/services/生命周期无initialize()shutdown()配置文件的路径是一样的都在META-INF/services/但 Nacos SPI 在加载时多了一层处理逻辑。扩展点加载流程源码走读NacosServiceLoader 的核心方法// com.alibaba.nacos.common.spi.NacosServiceLoaderpublicclassNacosServiceLoaderS{// 接口类型 → 实现类列表的缓存privatefinalMapClassS,CollectionSserviceMapnewConcurrentHashMap();// 创建新的 ServiceLoaderpublicstaticSNacosServiceLoaderSnewServiceLoader(ClassSservice){NacosServiceLoaderSloadernewNacosServiceLoader(service);loader.load();returnloader;}// 核心加载逻辑privatevoidload(){// 1. 通过 JDK ServiceLoader 找到所有实现类ServiceLoaderSjdkLoaderServiceLoader.load(service);// 2. 遍历并实例化for(Simpl:jdkLoader){// 3. 调用 initialize()如果有if(implinstanceofSpiExtension){((SpiExtension)impl).initialize();}// 4. 放入缓存serviceMap.computeIfAbsent(service,k-newArrayList()).add(impl);}// 5. 按 order 排序serviceMap.get(service).sort(Comparator.comparingInt(this::getOrder));}// 获取排序后的实现列表publicCollectionSgetSingletonServiceList(){returnserviceMap.getOrDefault(service,Collections.emptyList());}}加载时序你的插件实现类JDK ServiceLoaderNacosServiceLoaderNacos Server 启动你的插件实现类JDK ServiceLoaderNacosServiceLoaderNacos Server 启动newServiceLoader(IdentityContextBuilder.class)ServiceLoader.load(IdentityContextBuilder.class)扫描 META-INF/services/ 文件返回实现类迭代器new YourPluginImpl()initialize()如果实现了 SpiExtension按 getOrder() 排序返回排序后的实现列表排序机制order 值怎么确定Nacos SPI 的排序有两种方式按优先级从高到低方式一Order注解Order(1)// order 值越小优先级越高publicclassMyAuthPluginimplementsAuthPluginService{// ...}方式二getOrder()方法publicclassMyHealthCheckPluginimplementsHealthCheckProcessor{OverridepublicintgetOrder(){return100;// 值越小越靠前}}order 范围含义使用场景0 - 99最高优先级自定义插件覆盖默认行为100 - 999中等优先级常规扩展1000低优先级兜底/默认实现Nacos 内置的插件实现通常 order 值在 1000 以上。你写的自定义插件设成 1 就能覆盖默认行为。Nacos 的 12 个扩展点你只需要知道这 5 个Nacos 2.x 总共有 12 个 SPI 扩展点。大部分你不需要碰。实际生产中用到最多的是这 5 个扩展点接口用途鉴权插件AuthPluginService自定义登录认证方式身份上下文IdentityContextBuilder从请求中提取身份信息健康检查HealthCheckProcessor自定义健康检查协议数据源ExternalDataSourcePlugin替换 MySQL 为其他数据库配置变更通知ConfigChangeHook配置变更后触发自定义动作剩余 7 个扩展点DNS 服务、指标上报、日志扩展等属于高级定制场景日常开发很少用到。扩展点的插件目录nacos-server/ ├── plugins/ # 插件 jar 包放这里 │ ├── my-auth-plugin.jar │ ├── my-healthcheck-plugin.jar │ └── my-datasource-plugin.jar ├── conf/ │ └── application.properties └── target/ └── nacos-server.jar把插件的 jar 包放到plugins/目录下Nacos 启动时自动扫描加载。不需要改任何配置文件除了插件自身需要的配置参数。一张图带走Nacos SPI 加载全流程Nacos Server 启动扫描 plugins/ 目录加载所有 jar读取 META-INF/services/找到接口→实现类映射NacosServiceLoader实例化实现类调用 initialize()SpiExtension 接口按 Order / getOrder() 排序值越小优先级越高注册到扩展点 Map运行时按 order 调用优先使用 order 最小的实现Nacos Server 启动时扫描 plugins 目录 → 读 META-INF/services → 实例化 → 初始化 → 排序 → 注册。运行时按 order 取优先级最高的实现。不适用本文描述的情况Nacos 1.3.x 及以下。SPI 插件机制从 1.4.0 开始引入。1.3.x 没有 plugins 目录、没有 NacosServiceLoader。如果你需要自定义功能只能 fork 源码修改。Nacos 2.0.x 早期版本。2.0.0 引入了鉴权插件和数据源插件接口但接口在 2.0.x 的小版本迭代中有变更。如果你在 2.0.0 上开发的插件升级到 2.1.x 后可能需要适配接口变更。你们在 Nacos 上开发过自定义插件吗评论区留数字1开发过鉴权插件 2开发过健康检查插件 3开发过数据源插件 4还没开发过先收藏