SPI服务发现机制
SPI是Java JDK内部提供的一种服务发现机制。
-
SPI->Service Provider Interface,服务提供接口,是Java JDK内置的一种服务发现机制
-
通过在ClassPath路径下的META-INF/services文件夹查找文件,自动加载文件里所定义的类
[⚠️注意事项]: 面向对象的设计里,一般推荐模块之间基于接口编程,模块之间不对实现类进行编码。如果涉及实现类就会违反可插拔的原则,针对于模块装配,Java SPI提供了为某个接口寻找服务的实现机制。
SPI规范
- 使用约定: [1].编写服务提供接口,可以是抽象接口和函数接口,JDK1.8之后推荐使用函数接口
[2].在jar包的META-INF/services/目录里创建一个以服务接口命名的文件。其实就是实现该服务接口的具体实现类。
1提供一个目录: 2META-INF/services/ 3放到ClassPath下面
[3].当外部程序装配这个模块的时候,就能通过该Jar包META-INF/services/配置文件找到具体的实现类名,并装载实例化,完成模块注入。
1目录下放置一个配置文件: 2文件名是需要拓展的接口全限定名称 3文件内部为要实现的接口实现类 4文件必须为UTF-8编码
[4].寻找服务接口实现,不用在代码中提供,而是利用JDK提供服务查找工具类:java.util.ServiceLoader类来加载使用:
1ServiceLoader.load(xxx.class) 2ServiceLoader<XXXInterface> loads = ServiceLoader.load(xxx.class)
SPI源码分析
[1].ServiceLoader源码: 
1package java.util; 2import java.io.BufferedReader; 3import java.io.IOException; 4import java.io.InputStream; 5import java.io.InputStreamReader; 6import java.net.URL; 7import java.security.AccessController; 8import java.security.AccessControlContext; 9import java.security.PrivilegedAction; 10import java.util.ArrayList; 11import java.util.Enumeration; 12import java.util.Iterator; 13import java.util.List; 14import java.util.NoSuchElementException; 15 16public final class ServiceLoader<S> implements Iterable<S> 17{ 18 //[1].初始化定义全局配置文件路径Path 19 private static final String PREFIX = "META-INF/services/"; 20 //[2].初始化定义加载的服务类或接口 21 private final Class<S> service; 22 //[3].初始化定义类加载器 23 private final ClassLoader loader; 24 //[4].初始化定义访问控制上下文 25 private final AccessControlContext acc; 26 //[5].初始化定义加载服务类的缓存集合 27 private LinkedHashMap<String,S> providers = new LinkedHashMap<>(); 28 //[6].初始化定义私有内部LazyIterator类,真正加载服务类的实现类 29 private LazyIterator lookupIterator; 30 31 //私有化有参构造-> ServiceLoader(Class<S> svc, ClassLoader cl) 32 private ServiceLoader(Class<S> svc, ClassLoader cl) { //[1].实例化服务接口->Class<S> 33 service = Objects.requireNonNull(svc, "Service interface cannot be null"); 34 //[2].实例化类加载器->ClassLoader 35 loader = (cl == null) ? ClassLoader.getSystemClassLoader() : cl; 36 //[3].实例化访问控制上下文->AccessControlContext 37 acc = (System.getSecurityManager() != null) ? AccessController.getContext() : null; 38 //[4].回调函数->reload 39 reload(); 40 } 41 42 public void reload() { 43 //[1].清空缓存实例集合 44 providers.clear(); 45 //[2].实例化私有内部LazyIterator类->LazyIterator 46 lookupIterator = new LazyIterator(service, loader); 47 } 48 49 public static <S> ServiceLoader<S> load(Class<S> service,ClassLoader loader) 50 { 51 return new ServiceLoader<>(service, loader); 52 } 53 54 public static <S> ServiceLoader<S> load(Class<S> service) { 55 ClassLoader cl = Thread.currentThread().getContextClassLoader(); 56 return ServiceLoader.load(service, cl); 57 } 58 59 }
2.LazyIterator源码: 
1 private class LazyIterator implements Iterator<S> { 2 3 Class<S> service; 4 ClassLoader loader; 5 Enumeration<URL> configs = null; 6 Iterator<String> pending = null; 7 String nextName = null; 8 9 private LazyIterator(Class<S> service, ClassLoader loader) { 10 this.service = service; 11 this.loader = loader; 12 } 13 14 private boolean hasNextService() { 15 if (nextName != null) { 16 return true; 17 } 18 if (configs == null) { 19 try { 20 String fullName = PREFIX + service.getName(); 21 if (loader == null) configs = ClassLoader.getSystemResources(fullName); 22 else configs = loader.getResources(fullName); 23 } catch (IOException x) { 24 fail(service, "Error locating configuration files", x); 25 } 26 } 27 while ((pending == null) || !pending.hasNext()) { 28 if (!configs.hasMoreElements()) { 29 return false; 30 } 31 pending = parse(service, configs.nextElement()); 32 } 33 nextName = pending.next(); 34 return true; 35 } 36 37 private S nextService() { 38 if (!hasNextService()) throw new NoSuchElementException(); 39 String cn = nextName; 40 nextName = null; 41 Class<?> c = null; 42 try { 43 c = Class.forName(cn, false, loader); 44 } catch (ClassNotFoundException x) { 45 fail(service, "Provider " + cn + " not found"); 46 } 47 if (!service.isAssignableFrom(c)) { 48 fail(service, "Provider " + cn + " not a subtype"); 49 } 50 try { 51 S p = service.cast(c.newInstance()); 52 providers.put(cn, p); 53 return p; 54 } catch (Throwable x) { 55 fail(service, "Provider " + cn + " could not be instantiated", x); 56 } 57 throw new Error(); // This cannot happen 58 } 59 60 public boolean hasNext() { 61 if (acc == null) { 62 return hasNextService(); 63 } else { 64 PrivilegedAction<Boolean> action = 65 new PrivilegedAction<Boolean>() { 66 public Boolean run() { 67 return hasNextService(); 68 } 69 }; 70 return AccessController.doPrivileged(action, acc); 71 } 72 } 73 74 public S next() { 75 if (acc == null) { 76 return nextService(); 77 } else { 78 PrivilegedAction<S> action = 79 new PrivilegedAction<S>() { 80 public S run() { 81 return nextService(); 82 } 83 }; 84 return AccessController.doPrivileged(action, acc); 85 } 86 } 87 88 public void remove() { 89 throw new UnsupportedOperationException(); 90 } 91 }
使用举例
[1].Dubbo SPI 机制:
META-INF/dubbo.internal/xxx=接口全限定名
Dubbo 并未使用 Java SPI,而是重新实现了一套功能更强的 SPI 机制。 Dubbo SPI 的相关逻辑被封装在了 ExtensionLoader 类中,通过 ExtensionLoader,我们可以加载指定的实现类。Dubbo SPI 所需的配置文件需放置在 META-INF/dubbo 路径下。 与Java SPI 实现类配置不同,Dubbo SPI 是通过键值对的方式进行配置,这样我们可以按需加载指定的实现类。另外,在测试 Dubbo SPI 时,需要在 Robot 接口上标注 @SPI 注解。 [2].Cache SPI 机制:
META-INF/service/javax.cache.spi.CachingProvider=xxx
[3]Spring SPI 机制:
META-INF/services/org.apache.commons.logging.LogFactory=xxx
[4].SpringBoot SPI机制:
META-INF/spring.factories/org.springframework.boot.autoconfigure.EnableAutoConfiguration=xxx
在springboot的自动装配过程中,最终会加载META-INF/spring.factories文件,而加载的过程是由SpringFactoriesLoader加载的。从CLASSPATH下的每个Jar包中搜寻所有META-INF/spring.factories配置文件,然后将解析properties文件,找到指定名称的配置后返回 源码:
1public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories"; 2// spring.factories文件的格式为:key=value1,value2,value3 3// 从所有的jar包中找到META-INF/spring.factories文件 4// 然后从文件中解析出key=factoryClass类名称的所有value值 5public static List<String> loadFactoryNames(Class<?> factoryClass, ClassLoader classLoader) { 6 String factoryClassName = factoryClass.getName(); 7 // 取得资源文件的URL 8 Enumeration<URL> urls = (classLoader != null ? classLoader.getResources(FACTORIES_RESOURCE_LOCATION) : ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION)); 9 List<String> result = new ArrayList<String>(); 10 // 遍历所有的URL 11 while (urls.hasMoreElements()) { 12 URL url = urls.nextElement(); 13 // 根据资源文件URL解析properties文件,得到对应的一组@Configuration类 14 Properties properties = PropertiesLoaderUtils.loadProperties(new UrlResource(url)); 15 String factoryClassNames = properties.getProperty(factoryClassName); 16 // 组装数据,并返回 17 result.addAll(Arrays.asList(StringUtils.commaDelimitedListToStringArray(factoryClassNames))); 18 } 19 return result; 20}
[5].自定义序列化实现SPI:META-INF/services/xxx=接口全限定名
参考学习Java SPI 和Dubbo SPI机制源码,自己动手实现序列化工具类等
版权声明:本文为博主原创文章,遵循相关版权协议,如若转载或者分享请附上原文出处链接和链接来源。