javaSPI扩展
介绍
SPI就是一种服务发现机制。
SPI 是一种将服务接口与服务实现分离以达到解耦、大大提升了程序可扩展性的机制。引入服务提供者就是引入了 spi 接口的实现者,通过本地的注册发现获取到具体的实现类,轻松可插拔。
Java SPI 就是把某个接口的 指定实现类 (通过在指定文件配置的方式)给实例化出来了。
使用
需要准备一个maven工程,以此为蓝本,进行开发。
创建接口及实现类
1 | // 接口 |
创建META-INF/service文件夹,建立文件
这里建立的文件名与接口的全限定名相同,文件的内容为接口实现类的全限定名。
1 | com.lin.javaspi.service.impl.HelloServiceImpl1 |
启动方法
1 | public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException { |
流程
- 应用程序调用ServiceLoader.load方法。
ServiceLoader.load方法内先创建一个新的ServiceLoader,并实例化该类中的成员变量。
- 应用程序通过迭代器接口获取对象实例。
ServiceLoader先判断成员变量providers对象中
LinkedHashMap<String,S>
类型是否有缓存实例对象,如果有缓存,直接返回。- 读取META-INF/services/下的配置文件,获得所有能被实例化的类的名称,值得注意的是,ServiceLoader可以跨越jar包获取META-INF下的配置文件。
- 通过反射方法
Class.forName()
加载类对象,并用instance()
方法将类实例化。 - 把实例化后的类缓存到providers对象中,
LinkedHashMap<String,S>
类型然后返回实例对象。
要求
- 必须要有接口
- 对应要有实现类,且具备空参构造方法。
- 文件夹META-INF/services放置classpath目录下。
- 以“接口全限定名”命名的文件。
- 文件内容为接口实现类的全路径。
场景
调用者根据实际使用需要,启用、扩展、或者替换框架的实现策略
jdbc
不同的数据库使用不同的jdbc驱动,他们实现了相同的接口,java.sql.Driver
日志门面接口实现类加载
slf4j加载不同的提供商的日志实现类
spring
dubbo
总结
优点
使用Java SPI机制的优势是实现解耦,使得第三方服务模块的装配控制的逻辑与调用者的业务代码分离,而不是耦合在一起。应用程序可以根据实际业务情况启用框架扩展或替换框架组件。
相比使用提供接口jar包,供第三方服务模块实现接口的方式,SPI的方式使得源框架,不必关心接口的实现类的路径,可以不用通过下面的方式获取接口实现类:
- 代码硬编码import 导入实现类。
- 指定类全路径反射获取,
Class.forName()
。 - 第三方服务模块把接口实现类实例注册到指定地方,源框架从该处访问实例。
通过SPI的方式,第三方服务模块实现接口后,在第三方的项目代码的META-INF/services目录下的配置文件指定实现类的全路径名,源码框架即可找到实现类。
缺点
- 虽然ServiceLoader也算是使用的延迟加载,但是基本只能通过遍历全部获取,也就是接口的实现类全部加载并实例化一遍。如果你并不想用某些实现类,它也被加载并实例化了,这就造成了浪费。获取某个实现类的方式不够灵活,只能通过Iterator形式获取,不能根据某个参数来获取对应的实现类。
- 多个并发多线程使用ServiceLoader类的实例是不安全的。
相关文章