serviceloader通过读取meta-inf/services/下以接口全限定名命名的文本文件加载实现类,每行一个全限定名,依赖约定路径与utf-8编码(无bom),要求实现类有无参构造且非内部类。

Java的ServiceLoader怎么加载接口实现类
它靠读取META-INF/services/下以接口全限定名命名的文件,每行写一个实现类的全限定名。不是自动扫描,不依赖注解或配置中心,纯约定路径+文本解析。
常见错误现象:ServiceLoader.load(MyInterface.class)返回空迭代器,但类路径里明明有实现类——大概率是META-INF/services/com.example.MyInterface文件缺失、路径拼错、类名没写全、或文件编码不是UTF-8(带BOM会失败)。
- 文件必须放在
src/main/resources/META-INF/services/(Maven项目),编译后落在jar根目录同路径下 - 文件名必须严格匹配接口的
toString()结果,比如java.util.spi.LocaleServiceProvider→ 文件名就是java.util.spi.LocaleServiceProvider - 每行只能有一个类名,末尾不能有多余空格或制表符,换行符用LF(Unix风格)最稳妥
- 实现类必须有无参构造函数,且不能是内部类(含匿名类、lambda)、不能是接口或抽象类
ServiceLoader和Spring @Service有什么区别
完全不是一回事:ServiceLoader是JDK原生SPI机制,只做“发现+实例化”,不管理生命周期、不支持依赖注入、不处理AOP;@Service是Spring容器的组件声明,背后是完整的IoC容器控制。
使用场景很不同:你写一个通用SDK(比如日志适配器、加密算法插件),想让用户自行替换实现,又不想强耦合Spring,就用ServiceLoader;你在Spring Boot项目里写业务模块,直接用@Service更自然、更可控。
立即学习“Java免费学习笔记(深入)”;
-
ServiceLoader加载的是ServiceLoader.Provider(Java 9+)或直接new实例,不会走Spring代理,事务、@Async等注解无效 - 同一个接口,Spring可能通过
@Primary或@Qualifier选一个Bean,而ServiceLoader默认把所有匹配实现都拉出来,要自己遍历筛选 - 如果jar包里既有
META-INF/services/配置,又被Spring扫描到,两个机制会各自创建实例,可能引发单例失效或资源重复初始化
为什么ServiceLoader有时找不到服务?常见环境陷阱
不是代码写错了,而是类加载器没对上。它默认用Thread.currentThread().getContextClassLoader()去查资源,不是用当前类的getClassLoader()。
典型问题:Web应用里,Servlet容器(如Tomcat)把你的jar放在WEB-INF/lib/,但ServiceLoader却用到了Bootstrap ClassLoader或System ClassLoader,根本看不到你的META-INF/services/。
- 显式传入ClassLoader:
ServiceLoader.load(MyInterface.class, MyInterface.class.getClassLoader()) - 避免在static块里提前调用
ServiceLoader.load(),此时上下文ClassLoader可能还没切换到WebAppClassLoader - OSGi或模块化(Java 9+)环境下,模块未导出
META-INF/services/所在包,或未声明uses指令,也会静默失败 - Android上默认不支持
ServiceLoader(除非用AndroidX的兼容实现),别直接搬JDK代码
性能和兼容性要注意什么
每次调用ServiceLoader.load()都会重新扫描并解析文件,不是缓存结果。反复调用=反复I/O+反射,尤其在高频路径里容易拖慢。
Java 6引入,Java 9增强为支持延迟加载(stream())、Provider封装(避免提前实例化),但老版本没这些。
- 生产环境建议缓存
ServiceLoader实例,或把加载逻辑提到初始化阶段(如static块、@PostConstruct),不要在方法里每次都load() - Java 9+可用
ServiceLoader.load(MyInterface.class).stream().findFirst()避免无谓实例化;Java 8及以前只能用iterator().hasNext()试探,再手动next() - 注意JDK版本差异:
reload()方法在Java 9+才支持动态重载,旧版只能新建loader - 某些构建工具(如GraalVM native-image)默认不包含
META-INF/services/,需显式配置ResourcesFeature保留
真正麻烦的从来不是怎么写那个文件,而是当它不工作时,你得知道该盯住类加载器链、文件路径编码、还是构建产物结构。










