一、概述
SPI的英文全称是Service Provider Interface,是Java内置的一种服务提供发现机制。一般常用于一些框架或组件库的开发,我们最熟悉JDBC就应用到了SPI机制,并且在Spring、Dubbo中也大量应用了SPI机制。SPI机制是针对同一个接口采用不同的实现,提供给不同的用户使用,目的就是为了提高框架或者组件库的扩展性。
二、Java中SPI的实现
代码示例:
- 创建ide开发工具的插件接口
package com.ideax.spi.jdk;
/**
* <p>
* Idea插件接口
* </p>
*
* @author zhangxs
* @since 2023-02-16
*/
public interface IdePluginSpi {
void plugin();
}
- 为Goland开发插件
package com.ideax.spi.jdk;
/**
* <p>
* Goland插件实现类
* </p>
*
* @author zhangxs
* @since 2023-02-16
*/
public class GolandPlugin implements IdePluginSpi {
@Override
public void plugin() {
System.out.println("goland plugin");
}
}
- 为Webstorm开发插件
package com.ideax.spi.jdk;
/**
* <p>
* Webstorm插件实现类
* </p>
*
* @author zhangxs
* @since 2023-02-16
*/
public class WebstormPlugin implements IdePluginSpi {
@Override
public void plugin() {
System.out.println("webstorm plugin");
}
}
- 以Springboot工程为例,在项目的resources目录下新建
META-INF/services/
目录,在该目录下创建com.ideax.spi.jdk.IdePluginSpi
文件,文件中按照如下格式,写入接口的多个实现类全路径
com.ideax.spi.jdk.WebstormPlugin
com.ideax.spi.jdk.GolandPlugin
- 测试代码
package com.ideax.spi.jdk;
import java.util.ServiceLoader;
/**
* <p>
* jdk中的spi测试
* </p>
*
* @author zhangxs
* @since 2023-02-16
*/
public class JdkSpiTest {
public static void main(String[] args) {
ServiceLoader<IdePluginSpi> serviceLoader = ServiceLoader.load(IdePluginSpi.class);
serviceLoader.forEach(IdePluginSpi::plugin);
}
}
- 运行结果
说明: Java内置的SPI实现时,要通过java.util.ServiceLoader
类,解析项目以及jar文件classPath下META-INF/services/
目录中,且以接口全路径命名的文件,同时加载该文件中指定的该接口的实现类,通过这种方式,实现对指定接口实现类的调用。
三、Spring中SPI的实现
代码示例:
- 创建一个数据库链接接口
package com.ideax.spi.spring;
/**
* <p>
* 数据库链接接口
* </p>
*
* @author zhangxs
* @since 2023-02-16
*/
public interface DatabaseSpi {
void getConnection();
}
- PostgreSql实现类
package com.ideax.spi.spring;
/**
* <p>
* PostgreSql实现类
* </p>
*
* @author zhangxs
* @since 2023-02-16
*/
public class PostgreSqlDatabase implements DatabaseSpi {
@Override
public void getConnection() {
System.out.println("this database is PostgreSql");
}
}
- MySql实现类
package com.ideax.spi.spring;
/**
* <p>
* MySql实现类
* </p>
*
* @author zhangxs
* @since 2023-02-16
*/
public class MySqlDatabase implements DatabaseSpi {
@Override
public void getConnection() {
System.out.println("this database is MySql");
}
}
- 创建一个缓存链接接口
package com.ideax.spi.spring;
/**
* <p>
* 缓存链接接口
* </p>
*
* @author zhangxs
* @since 2023-02-16
*/
public interface CacheSpi {
void getConnection();
}
- Memecache实现类
package com.ideax.spi.spring;
/**
* <p>
* Memecache实现类
* </p>
*
* @author zhangxs
* @since 2023-02-16
*/
public class MemecacheCache implements CacheSpi {
@Override
public void getConnection() {
System.out.println("this cache is memecache");
}
}
- Redis实现类
package com.ideax.spi.spring;
/**
* <p>
* redis实现类
* </p>
*
* @author zhangxs
* @since 2023-02-16
*/
public class RedisCache implements CacheSpi {
@Override
public void getConnection() {
System.out.println("this cache is redis");
}
}
- 以Springboot工程为例,在项目的resources目录下新建
META-INF
目录,在该目录下创建spring.factories
文件,文件中按照如下格式,写入接口的多个实现类全路径
com.ideax.spi.spring.DatabaseSpi=\
com.ideax.spi.spring.MySqlDatabase,\
com.ideax.spi.spring.PostgreSqlDatabase
com.ideax.spi.spring.CacheSpi=\
com.ideax.spi.spring.RedisCache,\
com.ideax.spi.spring.MemecacheCache
- 测试代码
package com.ideax.spi.spring;
import org.springframework.core.io.support.SpringFactoriesLoader;
import java.util.List;
/**
* <p>
* Spring中的Spi测试
* </p>
*
* @author zhangxs
* @since 2023-02-16
*/
public class SpringSpiTest {
public static void main(String[] args) {
List<DatabaseSpi> databaseSpis = SpringFactoriesLoader
.loadFactories(DatabaseSpi.class, Thread.currentThread().getContextClassLoader());
databaseSpis.forEach(DatabaseSpi::getConnection);
List<CacheSpi> cacheSpis = SpringFactoriesLoader
.loadFactories(CacheSpi.class, Thread.currentThread().getContextClassLoader());
cacheSpis.forEach(CacheSpi::getConnection);
}
}
- 运行结果
说明: Spring中的SPI,类似于Java中SPI的设计理念,并且在其基础上进行了扩展,服务提供接口不再要求必须放到META-INF/services/
目录下,Spring中是通过配置spring.factories
方式实现SPI机制的。通过该机制,我们也可以在不修改Spring源码的前提下,根据我们的需求,扩展Spring框架。
四、总结
两种实现SPI的方式非常类似,我们可以根据需求自行选择,二者最明显的区别在于:
- Java中的SPI,每一个服务提供接口只能对应一个配置文件,配置文件中存放当前接口的所有实现类全路径,多个服务提供接口就必须对应多个配置文件,所有配置都必须放在
META-INF/services/
目录下。 - Spring中的SPI,只需要
spring.factories
一个配置文件,其中通过key-value的方式配置多个接口及其实现类,以接口全路径作为key,实现类全路径作为value,多个实现类直接用英文逗号隔开即可。