ServiceLoader 通过读取 META-INF/services/ 以下文件加载实现类与界面全限定名同名按 classpath 顺序搜索、不去重、懒加载的例子需要手动处理加载冲突和异常。ServiceLoader 如何加载接口Java 的ServiceLoader无需反射扫描全类路径也无需自动读取任何配置文件-它只识别一个位置META-INF/services/与界面全限定名完全一致的文件。例如您定义了界面com.example.LogHandler那必须在 jar 包里放META-INF/services/com.example.LogHandler文件内容是一行一行实现类的全名(如com.example.Slf4jLogHandler。类加载器只使用当前线程的上下类加载器Thread.currentThread().getContextClassLoader()去加载这个文件和它的类别而不是使用它ServiceLoader所在类别的加载器文件名大小写必须严格匹配接口名即使有更多的空间或换行ServiceLoader.load()它将静静地跳过这种实现多个 jar 在实现同一接口的不同实现时ServiceLoader按 classpath 顺序遍历**不保证唯一性**也不重——你要判断哪个应该用。为什么 new 实例失败但没有报错常见现象调用serviceIterator.next()报NoClassDefFoundError或InstantiationException但前面hasNext()返回true日志里没有堆栈。这是因为ServiceLoader懒惰的加载机制它只在next()真正尝试的时候Class.forName()newInstance()Java 9 是getDeclaredConstructor().newInstance()而hasNext()只分析配置文件和验证类名是否存在不触发类加载。如果实现类依赖于某个 jar 里面的类但是应该 jar 没进 classpathnext()它会爆炸部分会被异常吞下(尤其是在 for-each 循环里实现类型结构器的异常抛掷 public、无参与结构造器将导致实例失败建议永远用 try-catch 包住next()并且打印完全异常不要依赖hasNext()做安全判断如何避免插件化开发 ClassLoader 冲突当您的主程序和插件 jar 都依赖同一个三方库(例如org.slf4j:slf4j-api但是如果版本不同很容易出现LinkageError或者找不到方法。SPI 它本身并不能解决这个问题它只是一个发现工具类加载责任仍然掌握在你手中。插件 jar 应该 **shaded**(重命名包)依赖自己或声明为provided(由宿主提供)冲突不能直接打包 jar不要使用插件AppClassLoader加载自己建议为每个插件创建独立的URLClassLoader并显式设置 parent 为Thread.currentThread().getContextClassLoader()ServiceLoader.load(serviceInterface, pluginClassLoader)—— 插件本身的类加载器必须引入否则会在主程序中找到根本无法加载插件中的类加载器和 Spring 的 Conditional、Dubbo 的 ExtensionLoader 有啥区别别把ServiceLoader当成“轻量 Spring。它没有条件组装没有优先级不支持参数注入也没有缓存实例-每次next()都 new 新对象。Conditional基于环境、类存在性、属性值等的开关是编译期/启动期的决策ServiceLoader是纯操作时按约定查文件没有逻辑Dubbo 的ExtensionLoader支持 SPI 文件里写nameclass支持自适应扩展支持映射 IOC 注入而原生ServiceLoader类名只能写在配置文件中甚至没有别名若需热插拔、卸载、版本隔离、依赖检查ServiceLoader一步都做不到要自己补一整套加载。 生命周期管理真的用起来ServiceLoader它是启动时的“配置文件驱动的工厂”。不要指望它承受插件系统的所有重量。最容易被忽视的是它不处理类别之间的依赖关系无论你的初始化顺序如何——这些都必须自己完成。