SPI 的全程是Service Provider Interface,是Java提供的一套用来被第三方实现或扩展的接口
SPI能为在不改变源码的情况下,实现对接口的扩展,在java中,提供了一套标准的JDBC,但是并没有提供具体的实现,同时,也并不知道应该通过那个实现类创建对应的对象,我们便没有办法通过Driver的实现类创建Driver,那么我们应该怎么办呢,在JDK中,便使用SPI机制去加载第三方实现的接口,以此来通过Driver的实现类创建Driver。
我们来看一下JDK中的SPI如何使用
//工程A,在工程A中定义接口,并打成jar包public interface Service { String process();}// 工程B,在工程B中引入工程A的jar包,并实现Service public class ServiceA implements Service { @Override public String process() { return "ServiceA process"; }}//在resources目录像创建META-INF/services/,并以Service的全类名,创建文件// org.example.spi.Serviceorg.example.spi.impl.ServiceA// 工程C,引入工程B的jar包public class ServiceManager { public static void main(String[] args) { ServiceLoader serviceLoader = ServiceLoader.load(Service.class); Iterator iter = serviceLoader.iterator(); while (iter.hasNext()) { Service service = iter.next(); String process = service.process(); System.out.println(process); } }}// 执行结果ServiceA process 我们看到,JDK的SPI机制并不需要通过Service的实现类,便能创建Service,通过这种形式,我们便可以将接口和实现分离,以此来实现对功能的动态扩展
我们来看一下dubbo中的SPI
// 在工程A中定义接口,并使用SPI注解@SPIpublic interface Service { String process();}// 工程B,在工程B中引入工程A的jar包,并实现Service public class ServiceA implements Service { @Override public String process() { return "ServiceA process"; }}//在resources目录像创建META-INF/dubbo/,并以Service的全类名,创建文件// org.example.spi.ServiceserviceA=org.example.spi.impl.ServiceApublic class App { public static void main(String[] args) { ExtensionLoader extensionLoader = ExtensionLoader.getExtensionLoader(Service.class); Service serviceA = extensionLoader.getExtension("serviceA"); String process = serviceA.process(); System.out.println(process); }} 通过上面的代码,我们可以看出Dubbo的SPI机制和JDK中SPI机制的区别,在Dubbo中,可扩展的接口必须增加SPI注解,同时,在resources目录下定义配置文件的时候,采用的是key,value的形式,在加载扩展点的时候,便可以通过扩展点的名称,直接获取扩展点,以此来实现动态加载。
public static ExtensionLoader getExtensionLoader(Class type) { if (type == null) { throw new IllegalArgumentException("Extension type == null"); } else if (!type.isInterface()) { throw new IllegalArgumentException("Extension type (" + type + ") is not an interface!"); } else if (!withExtensionAnnotation(type)) { throw new IllegalArgumentException("Extension type (" + type + ") is not an extension, because it is NOT annotated with @" + SPI.class.getSimpleName() + "!"); } else { // 获取对应的ExtensionLoader ExtensionLoader loader = (ExtensionLoader)EXTENSION_LOADERS.get(type); if (loader == null) { // 没有则通过构造函数创建一个 EXTENSION_LOADERS.putIfAbsent(type, new ExtensionLoader(type)); loader = (ExtensionLoader)EXTENSION_LOADERS.get(type); } return loader; }} 在构建ExtensionLoader的时候,会首先从缓存中获取,如果缓存中没有,通过构造函数创建一个ExtensionLoader,也就是会,在Dubbo中,一个Class对应着一个ExtensionLoader。
public T getExtension(String name, boolean wrap) { if (StringUtils.isEmpty(name)) { throw new IllegalArgumentException("Extension name == null"); } else if ("true".equals(name)) { return this.getDefaultExtension(); } else { // 通过名称获取对应的扩展点的包装类 Holder通过name获取对应的扩展点的时候,同样,首先会从缓存中获取,如果缓存中没有,则加载对应的resources目录下的的文件,并将响应的key,value结果缓存起来,如果找不到对应name的扩展点,则抛出异常
public class SpiLoader { private static final String resources = "META-INF/spi/"; private static final Map, SpiLoader> loaderCacheMap = new ConcurrentHashMap<>(); private final Map> classCacheMap = new ConcurrentHashMap<>(); private final Map instanceCacheMap = new ConcurrentHashMap<>(); private Class clazz; public SpiLoader(Class clazz) { this.clazz = clazz; } public static SpiLoader getSpiLoader(Class clazz) { SpiLoader spiLoader = loaderCacheMap.get(clazz); if (spiLoader == null) { synchronized (clazz) { spiLoader = loaderCacheMap.get(clazz); if (spiLoader == null) { spiLoader = new SpiLoader(clazz); loaderCacheMap.put(clazz, spiLoader); } } } return spiLoader; } public T getExtension(String name) { Object instance = instanceCacheMap.get(name); if (instance == null) { synchronized (clazz) { instance = instanceCacheMap.get(name); if (instance == null) { instance = loadExtension(name); instanceCacheMap.put(name, instance); } } } return (T) instance; } public T loadExtension(String name) { Class<?> clazz = loadClass(name); try { return (T) clazz.newInstance(); } catch (Exception e) { e.printStackTrace(); } return null; } public Class<?> loadClass(String name) { Class<?> clazz = classCacheMap.get(name); if (clazz == null) { synchronized (this.clazz) { clazz = classCacheMap.get(name); if (clazz == null) { clazz = doLoadClass(name); } } } if (clazz == null) { throw new RuntimeException("找不到对应的扩展点"); } return clazz; } public Class<?> doLoadClass(String name) { Map> map = new HashMap<>(); String filePath = resources + clazz.getName(); ClassLoader classLoader = this.getClass().getClassLoader(); try { Enumeration urls = classLoader.getResources(filePath); while (urls.hasMoreElements()) { URL resourceURL = urls.nextElement(); this.loadResource(map, classLoader, resourceURL); } classCacheMap.putAll(map); } catch (Exception e) { e.printStackTrace(); } return classCacheMap.get(name); } private void loadResource(Map> map, ClassLoader classLoader, URL resourceURL) throws IOException { InputStream inputStream = resourceURL.openStream(); Properties properties = new Properties(); properties.load(inputStream); properties.forEach((name, clazz) -> { try { map.put((String) name, Class.forName((String) clazz, true, classLoader)); } catch (ClassNotFoundException e) { e.printStackTrace(); } }); }} 使用
public class Main { public static void main(String[] args) { SpiLoader spiLoader = SpiLoader.getSpiLoader(Service.class); Service serviceA = spiLoader.getExtension("serviceA"); String process = serviceA.process(); System.out.println(process); Service serviceB = spiLoader.getExtension("serviceB"); System.out.println(serviceB.process()); }} 通过这一个类,我们模仿Dubbo简单的实现了SPI的功能,当然,并没有dubbo的SPI那么强大,像dubbo中的自动注入,自动包装,默认Extension我们都没有实现
我们可以简单的来分析一下这个类,其核心就是通过缓存+双重检查锁,获取对应的SpiLoader,Class,以及Instance,当找不到对应的实例时,便通过扫描SPI对应的目录,加载对应的文件,一次来加载对应的扩展点
| 留言与评论(共有 0 条评论) “” |