Java SPI(Service Provider Interface)是一种Java的服务提供者接口机制。它允许在运行时动态加载实现服务接口的类。
基本概念
SPI
机制的基本思想是,定义一个服务接口,多个不同的实现类可以实现这个接口。然后,在classpath
路径下的META-INF/services目录中创建一个以服务接口的全限定名命名的文件,文件内容为实现类的全限定名。这样,当程序运行时,可以通过ServiceLoader工具类来加载所有实现了该服务接口的类。
使用Java SPI的好处是,可以在不修改源代码的情况下,通过配置更换实现类,实现程序的可扩展性。而不需要在代码中显式地引用实现类,从而实现了松耦合的设计。
SPI机制在Java中被广泛应用,例如JDBC中的Driver接口和Servlet容器中的Servlet接口等。通过SPI机制,可以在不同的场景中灵活地替换实现类,提供更多的选择和定制化的功能。同时,SPI机制也增加了程序的灵活性和可维护性。
最简单的实例
我们现在需要使用一个内容搜索接口,搜索的实现可能是基于文件系统的搜索,也可能是基于数据库的搜索,甚至是 rpc
搜索。
基本步骤
- 定义接口
- 在
META-INF/services
目录下,新建接口全限定名(如果是内部接口或内部类需要使用$
)的文件,然后在文件里面加上实现类。- 使用
ServiceLoader
加载接口,进行使用
public class SpiApplication { public interface Search { List<String> searchDoc(String keyword); } public static class FileSearch implements Search{ @Override public List<String> searchDoc(String keyword) { System.out.println("文件搜索 "+keyword); return null; } } public static class DatabaseSearch implements Search{ @Override public List<String> searchDoc(String keyword) { System.out.println("数据搜索 "+keyword); return null; } } public static class RpcSearch implements Search{ @Override public List<String> searchDoc(String keyword) { System.out.println("rpc搜索 "+keyword); return null; } } public static void main(String[] args) { ServiceLoader<Search> s = ServiceLoader.load(Search.class); Iterator<Search> iterator = s.iterator(); while (iterator.hasNext()) { Search search = iterator.next(); search.searchDoc("hello world"); } } }
可以看到输出三行搜索结果,这就是因为 ServiceLoader.load(Search.class)
在加载某接口时,会去 META-INF/services
下找接口的全限定名文件,再根据里面的内容加载相应的实现类。
这就是 spi
的思想,接口的实现由 provider
实现,provider
只用在提交的 jar
包里的 META-INF/services
下根据平台定义的接口新建文件,并添加进相应的实现类内容就好。
使用 jar
包通过 spi
动态实现接口功能
新增 jar
包,打包后加载到其他项目运行。
public interface Search { List<String> searchDoc(String keyword); } //- public class DefaultSearch implements Search{ @Override public List<String> searchDoc(String keyword) { System.out.println("default search"); return null; } } //- public class MainApplicaction { public static void main(String[] args) { ServiceLoader<Search> s = ServiceLoader.load(Search.class); Iterator<Search> iterator = s.iterator(); while (iterator.hasNext()) { Search search = iterator.next(); search.searchDoc("hello world"); } System.out.println(">>>> jar main end"); } }
然后 mvn install
到本地,把 jar
包导入到其他项目
package com.example.spi; import com.example.MainApplicaction; import com.example.Search; import java.util.List; public class SpiApplication { public static class FileSearch implements Search { @Override public List<String> searchDoc(String keyword) { System.out.println("文件搜索 "+keyword); return null; } } public static class DatabaseSearch implements Search{ @Override public List<String> searchDoc(String keyword) { System.out.println("数据搜索 "+keyword); return null; } } public static class RpcSearch implements Search{ @Override public List<String> searchDoc(String keyword) { System.out.println("rpc搜索 "+keyword); return null; } } public static void main(String[] args) { MainApplicaction.main(new String[0]); } }
运行结果
文件搜索 hello world 数据搜索 hello world rpc搜索 hello world default search >>>> jar main end
由此可以看到本项目的
META-INF/services
的加载顺序在jar
包前面。