1、什么是SPI?
SPI全称为Service Provider Interface,是一种服务发现机制。它通过在ClassPath路径下的META-INF/services文件夹查找文件,自动加载文件里所定义的类。这一机制为很多框架扩展提供了可能,比如现在的Dubbo、JDBC中都使用到了SPI机制。为什么要学SPI,因为在SpringBoot的自动装配中其实有使用到SPI机制,所以掌握了这部分对于SpringBoot的学习还是很有帮助的。
总结:SPI就是我定义接口,你去实现这些接口,然后在某个文件里告诉我实现是什么即可。
2、案例
我们Java在进行数据库连接时使用的JDBC,数据库的种类比较多,我们常用的就是MySQL和Oracle,如果没有一个访问数据库的统一接口,那么连接不同的数据库就需要写不同的程序,这样对程序员来说比较繁琐,而JDBC就提供了一些接口,定义了操作数据库的抽象行为,由各个数据库厂商自己实现。
2.1 程序是怎么知道这些接口是由哪些Java类实现的呢?
基于这个点,SPI定义了一个规范,实现者要在META-INF/services下创建接口名的文件,然后在文件中是接口的具体实现类。
2.2 案例介绍
先定义接口项目
然后创建一个扩展的实现,先导入上面接口项目的依赖
1 2 3 4 5
| <dependency> <groupId>com.liyyao</groupId> <artifactId>MyDataBase</artifactId> <version>1.0-SNAPSHOT</version> </dependency>
|
然后创建接口的实现
1 2 3 4 5 6 7 8 9 10
|
public class MySqlConnection implements Connection { @Override public String getUrl() { System.out.println("mysql..."); return null; } }
|
然后在resources目录下创建META-INF/services目录,然后在目录中创建一个文件,名称必须是定义的接口的全类路径名称。然后在文件中写上接口的实现类的全类路径名称。
同样的再创建一个扩展的实现
然后创建一个测试的项目,导入扩展的项目,然后进行测试,测试代码如下
1 2 3 4 5 6 7 8
| public static void main(String[] args) { ServiceLoader<Connection> load = ServiceLoader.load(Connection.class); Iterator<Connection> iterator = load.iterator(); while (iterator.hasNext()) { Connection con = iterator.next(); con.getUrl(); } }
|
根据不同的导入,执行的逻辑会有不同
3、源码查看
3.1、ServiceLoader
首先来看下ServiceLoader的类结构
1 2 3 4 5 6 7 8 9 10 11 12
| private static final String PREFIX = "META-INF/services/";
private final Class<S> service;
private final ClassLoader loader;
private final AccessControlContext acc;
private LinkedHashMap<String,S> providers = new LinkedHashMap<>();
private LazyIterator lookupIterator;
|
3.2、load
load方法创建了一些属性,重要的是实例化了内部类,LazyIterator。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| public final class ServiceLoader<S> implements Iterable<S> { 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); } }
|
查找实现类和创建实现类的过程,都在LazyIterator完成。当我们调用iterator.hasNext和iterator.next方法时,实际上调用的都是LazyIterator的相应方法。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| private class LazyIterator implements Iterator<S> { Class<S> service; ClassLoader loader; Enumeration<URL> configs = null; Iterator<String> pending = null; String nextName = null;
private boolean hasNextService() { if (nextName != null) { return true; } if (configs == null) { String fullName = PREFIX + service.getName(); configs = loader.getResources(fullName); } while ((pending == null) || !pending.hasNext()) { pending = parse(service, configs.nextElement()); } nextName = pending.next(); return true; } }
|
创建实例对象,当然,调用next方法时,实际调用到的是lookupIterator.nextService。它通过反射的方式,创建实现类的实例并返回。
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| private class LazyIterator implements Iterator<S> { private S nextService() { String cn = nextName; nextName = null; Class<?> c = Class.forName(cn, false, loader); S p = service.cast(c.newInstance()); providers.put(cn, p); return p; } }
|
看到这儿应该很清楚了,获取到类的实例,然后就可以做事情了。