JAVA SPI 是怎么实现的?
SPI 是什么?
SPI(Service Provider Interface) ,是 JDK 内置的一种提供发现机制。SPI 是一种动态替换发现的机制。
JAVA SPI 实现
- 定义一组接口,接口有多种实现
public interface IShout { void shout(); } public class Cat implements IShout { @Override public void shout() { System.out.println("miao miao"); } } public class Dog implements IShout { @Override public void shout() { System.out.println("wang wang"); } }
- 在 src/main/resources/ 下建立 /META-INF/services 目录, 新增一个以接口命名的文件 (org.foo.demo.IShout文件),内容是要应用的实现类(这里是org.foo.demo.animal.Dog和org.foo.demo.animal.Cat,每行一个类)。
文件位置
- src -main -resources - META-INF - services - org.foo.demo.IShout
文件内容
org.foo.demo.animal.Dog org.foo.demo.animal.Cat
- ServiceLoaader 加载配置文件中指定的实现
public class SPIMain { public static void main(String[] args) { ServiceLoader<IShout> shouts = ServiceLoader.load(IShout.class); for (IShout s : shouts) { s.shout(); } } }
输出:
wang wang miao miao
Java SPI 实现原理
- 应用程序调用 ServiceLoader.load 方法创建一个 ServiceLoader,并实例化类中的成员变量。
- load(Classloader 类型,类加载器)
- acc(AccessControlContext 类型,访问控制器)
- providers(LinkedHashMap<String,S>)类型,用于缓存加载成功的类
- lookupIterators (实现迭代器功能)
private ServiceLoader(Class<S> svc, ClassLoader cl) { service = Objects.requireNonNull(svc, "Service interface cannot be null"); loader = (cl == null) ? ClassLoader.getSystemClassLoader() : cl; acc = (System.getSecurityManager() != null) ? AccessController.getContext() : null; reload(); } public void reload() { providers.clear(); lookupIterator = new LazyIterator(service, loader); }
- 应用程序通过迭代器接口 读取META-INF/services/下的配置文件,然后实例化:
1).读取配置文件
if (configs == null) { try { String fullName = PREFIX + service.getName(); if (loader == null) configs = ClassLoader.getSystemResources(fullName); else configs = loader.getResources(fullName); } catch (IOException x) { fail(service, "Error locating configuration files", x); } }
2). 通过反射方法Class.forName()加载类对象,并用instance()方法将类实例化。
3). 把实例化后的类缓存到providers对象中
JAVA SPI 应用场景
数据库DriverManager、Spring、ConfigurableBeanFactory等都用到了SPI机制。
在JDBC4.0之前,连接数据库的时候,通常会用Class.forName("com.mysql.jdbc.Driver")这句先加载数据库相关的驱动,然后再进行获取连接等的操作。而JDBC4.0之后不需要Class.forName来加载驱动,直接获取连接即可,这里使用了Java的SPI扩展机制来实现。
- 在mysql-connector-java-5.1.45.jar中,META-INF/services目录下会有一个名字为java.sql.Driver的文件:
com.mysql.jdbc.Driver com.mysql.fabric.jdbc.FabricMySQLDriver
- Mysql DriverManager实现
DriverManager中有一个静态代码块如下:
static { loadInitialDrivers(); println("JDBC DriverManager initialized"); }
loadInitialDrivers方法,loadInitialDrivers用法用到了上文提到的spi工具类ServiceLoader:
public void run() { ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class); Iterator<Driver> driversIterator = loadedDrivers.iterator(); ... }
总结
Java的SPI机制就是为某个接口寻找到相关的服务实现