SPI机制简介
SPI(Service Provider Interface),是从JDK6开始引入的,一种基于ClassLoader来发现并加载服务的机制。
一个标准的SPI,由3个组件构成,分别是:
- Service:是一个公开的接口或者抽象类,定义了一个抽象的功能模块;
- Service Provider:是Service接口的实现子类;
- ServiceLoader:是SPI机制的核心组件,负责在运行时发现并加载Service Provider。
SPI运行流程
SPI运行流程如下图所示,
ServiceLoader类
ServiceLoader是SPI机制的核心组件,负责在运行时发现并加载Service Provider。该类提供了load方法,用于在程序运行过程中去加载第三方提供的Service接口实现类,得到接口实例;后续过程中,只需要通过接口实例去执行对应的操作即可。
假设,我们有这样一个InternetService 接口,用来提供网络连接服务。
/*** 网络连接服务接口SPI-Service*/
public interface InternetService {void connectInternet();
}
然后为其提供接口实现子类,
package cn.mobile;import spi.InternetService;public class BeijingChinaMobileMobile implements InternetService {@Overridepublic void connectInternet() {System.out.println("connect internet By [Beijing China Mobile]");}
}
这样写在单体项目中自然是可以的,但是,如果我们要让别人也能在项目中使用这个接口提供的网络连接服务,就有点难受了。好在SPI机制就是用来做服务发现和加载工作的,我们可以将其改造成符合SPI标准的一套通用工具。
service服务定义
接口就是在定义标准,而这个标准需要交由第三方进行实现。
1. 创建maven项目,
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"><parent><artifactId>simple_spi_example</artifactId><groupId>org.example</groupId><version>1.0-SNAPSHOT</version></parent><modelVersion>4.0.0</modelVersion><artifactId>simple-api</artifactId><properties><maven.compiler.source>8</maven.compiler.source><maven.compiler.target>8</maven.compiler.target></properties></project>
2.定义接口标准,
package spi;/*** 网络连接服务接口SPI-Service*/
public interface InternetService {void connectInternet();
}
service provider服务的第三方实现
service provider是Service接口的实现子类。以下我们提供两个第三方实现,分别命名为A、B。
第三方A实现
1. 创建Maven项目,引入接口标准依赖,
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"><parent><artifactId>simple_spi_example</artifactId><groupId>org.example</groupId><version>1.0-SNAPSHOT</version></parent><modelVersion>4.0.0</modelVersion><artifactId>simple-spi-mobile</artifactId><properties><maven.compiler.source>8</maven.compiler.source><maven.compiler.target>8</maven.compiler.target></properties><dependencies><dependency><artifactId>simple-api</artifactId><groupId>org.example</groupId><version>1.0-SNAPSHOT</version></dependency></dependencies></project>
2. 实现SPI接口,
package cn.mobile;import spi.InternetService;public class ChinaMobile implements InternetService {@Overridepublic void connectInternet() {System.out.println("connect internet By [China Mobile]");}
}
package cn.mobile;import spi.InternetService;public class BeijingChinaMobileMobile implements InternetService {@Overridepublic void connectInternet() {System.out.println("connect internet By [Beijing China Mobile]");}
}
3.提供service元数据,
在resources目录下,新建资源文件META-INF\services\spi.InternetService,文件名:spi.InternetService(父接口的全路径名称),
cn.mobile.ChinaMobile
cn.mobile.BeijingChinaMobileMobile
第三方B实现
1. 创建Maven项目,引入接口标准依赖,
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"><parent><artifactId>simple_spi_example</artifactId><groupId>org.example</groupId><version>1.0-SNAPSHOT</version></parent><modelVersion>4.0.0</modelVersion><artifactId>simple-spi-unicom</artifactId><properties><maven.compiler.source>8</maven.compiler.source><maven.compiler.target>8</maven.compiler.target></properties><dependencies><dependency><artifactId>simple-api</artifactId><groupId>org.example</groupId><version>1.0-SNAPSHOT</version></dependency></dependencies>
</project>
2. 实现SPI接口,
package cn.unicom;import spi.InternetService;public class ChinaUniCom implements InternetService {@Overridepublic void connectInternet() {System.out.println("connect internet By [ChinaUniCom]");}
}
3.提供service元数据,
在resources目录下,新建资源文件META-INF\services\spi.InternetService,文件名:spi.InternetService(父接口的全路径名称),
cn.unicom.ChinaUniCom
ServiceLoader服务发现和服务加载
1. 在主项目中引入service服务的第三方实现相关依赖,
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"><parent><artifactId>simple_spi_example</artifactId><groupId>org.example</groupId><version>1.0-SNAPSHOT</version></parent><modelVersion>4.0.0</modelVersion><artifactId>smaple-company</artifactId><properties><maven.compiler.source>8</maven.compiler.source><maven.compiler.target>8</maven.compiler.target></properties><dependencies><dependency><artifactId>simple-api</artifactId><groupId>org.example</groupId><version>1.0-SNAPSHOT</version></dependency><dependency><groupId>org.example</groupId><artifactId>simple-spi-unicom</artifactId><version>1.0-SNAPSHOT</version></dependency><dependency><groupId>org.example</groupId><artifactId>simple-spi-mobile</artifactId><version>1.0-SNAPSHOT</version></dependency></dependencies></project>
2. 通过ServiceLoader发现并加载服务
package com.company;import spi.InternetService;import java.util.ServiceLoader;public class Application {public static void main(String[] args) {ServiceLoader<InternetService> load = ServiceLoader.load(InternetService.class);for (InternetService provider : load) {provider.connectInternet();}}
}
3.执行结果如下
ServiceLoader源码分析
分析,
// ServiceLoader实现了Iterable接口,可以遍历所有的服务实现者
public final class ServiceLoader<S> implements Iterable<S>
{// 查找配置文件的目录private static final String PREFIX = "META-INF/services/";// 表示要被加载的服务的类或接口private final Class<S> service;// 这个ClassLoader用来定位,加载,实例化服务提供者private final ClassLoader loader;// 访问控制上下文private final AccessControlContext acc;// 缓存已经被实例化的服务提供者,按照实例化的顺序存储private LinkedHashMap<String,S> providers = new LinkedHashMap<>();// 迭代器private LazyIterator lookupIterator;
}
// 服务提供者查找的迭代器
public Iterator<S> iterator() {return new Iterator<S>() {Iterator<Map.Entry<String,S>> knownProviders= providers.entrySet().iterator();// hasNext方法public boolean hasNext() {if (knownProviders.hasNext())return true;return lookupIterator.hasNext();}// next方法public S next() {if (knownProviders.hasNext())return knownProviders.next().getValue();return lookupIterator.next();}};
}
// 服务提供者查找的迭代器
private class LazyIterator implements Iterator<S> {// 服务提供者接口Class<S> service;// 类加载器ClassLoader loader;// 保存实现类的urlEnumeration<URL> configs = null;// 保存实现类的全名Iterator<String> pending = null;// 迭代器中下一个实现类的全名String nextName = null;public boolean hasNext() {if (nextName != null) {return true;}if (configs == null) {try {String fullName = PREFIX + service.getName();if (loader == null)configs = ClassLoader.getSystemResources(fullName);elseconfigs = loader.getResources(fullName);} catch (IOException x) {fail(service, "Error locating configuration files", x);}}while ((pending == null) || !pending.hasNext()) {if (!configs.hasMoreElements()) {return false;}pending = parse(service, configs.nextElement());}nextName = pending.next();return true;}public S next() {if (!hasNext()) {throw new NoSuchElementException();}String cn = nextName;nextName = null;Class<?> c = null;try {c = Class.forName(cn, false, loader);} catch (ClassNotFoundException x) {fail(service,"Provider " + cn + " not found");}if (!service.isAssignableFrom(c)) {fail(service, "Provider " + cn + " not a subtype");}try {S p = service.cast(c.newInstance());providers.put(cn, p);return p;} catch (Throwable x) {fail(service, "Provider " + cn + " could not be instantiated: " + x, x);}throw new Error(); // This cannot happen}
}
SPI应用场景举例
在JDBC4.0之前,连接数据库的时候,通常会用
Class.forName("com.mysql.jdbc.Driver")
这句先加载数据库相关的驱动,然后再进行获取连接等的操作。而JDBC4.0之后不需要Class.forName
来加载驱动,直接获取连接即可,这里使用了Java的SPI扩展机制来实现。
注册数据库连接驱动就是一个典型的例子,以PostGreSQL数据库连接驱动为例,我们知道:java中定义了接口java.sql.Driver,但是并没有提供具体的实现,具体的实现都是由不同厂商来提供的,所以我们实际开发时,需要先去找到对应的数据库连接驱动,把驱动加载到应用中,然后才能去执行数据库的种种操作。
查看postgresql依赖jar包,会发现在META-INFO下的services路径下,也提供了java.sql.Driver驱动类的实现子类信息,
文件内容如下,
org.postgresql.Driver
这样,就可以基于SPI机制,动态加载第三方提供的Driver数据库连接驱动,实现数据库相关的操作。