概念 spi 全称(Service Provider Interface),是JDK内置的一种服务发现机制, 不同的厂商可以通过扩展的形式来动态替换需要实现的方法
约定
在META-INF/services/目录中创建以Service接口路径命名的文本文件, 文件中包含实现接口实现类的路径,多个实现类以换行符分割
使用ServiceLoader.load 动态加载Service接口的实现类
如SPI的实现类为jar,则需要将其放在当前程序的classpath下
应用 在实际开发过程中我们可能会用到apacheHttp和okHttp的一些框架,我们可以让用户自主选择用一个他们比较熟悉的http框架 我们首先来创建一个spi项目
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 27 28 29 30 31 32 33 . ├── pom.xml ├── spi.iml ├── src │ ├── main │ │ ├── java │ │ │ └── com │ │ │ └── tookbra │ │ │ └── spi │ │ │ ├── ApacheHttpProxy.java │ │ │ ├── HttpProxy.java │ │ │ ├── Main.java │ │ │ └── OkHttpProxy.java │ │ └── resources │ │ └── META-INF │ │ └── services │ │ └── com.tookbra.spi.HttpProxy │ └── test │ └── java └── target ├── classes │ ├── META-INF │ │ └── services │ │ └── com.tookbra.spi.HttpProxy │ └── com │ └── tookbra │ └── spi │ ├── ApacheHttpProxy.class │ ├── HttpProxy.class │ ├── Main.class │ └── OkHttpProxy.class └── generated-sources └── annotations
1.创建接口 com.tookbra.spi.HttpProxy
1 2 3 4 5 6 7 8 9 10 11 package com.tookbra.spi;public interface HttpProxy { void proxy () ; }
2.创建对应的实现类 com.tookbra.spi.ApacheHttpProxy
1 2 3 4 5 6 7 8 9 10 11 12 package com.tookbra.spi;public class ApacheHttpProxy implements HttpProxy { public void proxy () { System.out.println("apache proxy" ); } }
com.tookbra.spi.OkHttpProxy
1 2 3 4 5 6 7 8 9 10 11 12 package com.tookbra.spi;public class OkHttpProxy implements HttpProxy { public void proxy () { System.out.println("okhttp proxy" ); } }
3.执行使用实现类的文件 META-INF/services/com.tookbra.spi.HttpProxy
1 2 com.tookbra.spi.ApacheHttpProxy com.tookbra.spi.OkHttpProxy
4.运行
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 package com.tookbra.spi;import java.util.ServiceLoader;public class Main { public static void main (String[] args) { ServiceLoader<HttpProxy> serviceLoader = ServiceLoader.load(HttpProxy.class ) ; serviceLoader.forEach(s -> { s.proxy(); }); } }
5.输出
1 2 apache proxy okhttp proxy
解析 先看第一行代码,主要创建了服务加载对象以及初始化类加载器、加载的服务类或接口、以及内部的迭代器
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 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 ServiceLoader<HttpProxy> serviceLoader = ServiceLoader.load(HttpProxy.class ) ; public final class ServiceLoader <S > implements Iterable <S > { 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; ... ... public static <S> ServiceLoader<S> load (Class<S> service) { ClassLoader cl = Thread.currentThread().getContextClassLoader(); return ServiceLoader.load(service, cl); } public static <S> ServiceLoader<S> load (Class<S> service, ClassLoader loader) { return new ServiceLoader<>(service, loader); } 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); } ... }
ServiceLoader实现了Iterable接口的iterator方法,会先从缓存map去找,没有数据再从内部lookupIterator找
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 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 public Iterator<S> iterator () { return new Iterator<S>() { Iterator<Map.Entry<String,S>> knownProviders = providers.entrySet().iterator(); public boolean hasNext () { if (knownProviders.hasNext()) return true ; return lookupIterator.hasNext(); } public S next () { if (knownProviders.hasNext()) return knownProviders.next().getValue(); return lookupIterator.next(); } public void remove () { throw new UnsupportedOperationException(); } }; } private class LazyIterator implements Iterator <S > { Class<S> service; ClassLoader loader; Enumeration<URL> configs = null ; Iterator<String> pending = null ; String nextName = null ; private LazyIterator (Class<S> service, ClassLoader loader) { this .service = service; this .loader = loader; } private boolean hasNextService () { if (nextName != null ) { return true ; } 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); } } while ((pending == null ) || !pending.hasNext()) { if (!configs.hasMoreElements()) { return false ; } pending = parse(service, configs.nextElement()); } nextName = pending.next(); return true ; } private S nextService () { if (!hasNextService()) 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); } throw new Error(); } public boolean hasNext () { if (acc == null ) { return hasNextService(); } else { PrivilegedAction<Boolean> action = new PrivilegedAction<Boolean>() { public Boolean run () { return hasNextService(); } }; return AccessController.doPrivileged(action, acc); } } public S next () { if (acc == null ) { return nextService(); } else { PrivilegedAction<S> action = new PrivilegedAction<S>() { public S run () { return nextService(); } }; return AccessController.doPrivileged(action, acc); } } public void remove () { throw new UnsupportedOperationException(); } }
应用于
缺陷 “”” 需要遍历所有的实现,并实例化,然后我们在循环中才能找到我们需要的实现。 配置文件中只是简单的列出了所有的扩展实现,而没有给他们命名。导致在程序中很难去准确的引用它们。 扩展如果依赖其他的扩展,做不到自动注入和装配 不提供类似于Spring的IOC和AOP功能 扩展很难和其他的框架集成,比如扩展里面依赖了一个Spring bean,原生的Java SPI不支持