SPI详解

什么是SPI

SPI 的全程是Service Provider Interface,是Java提供的一套用来被第三方实现或扩展的接口

SPI有什么作用

SPI能为在不改变源码的情况下,实现对接口的扩展,在java中,提供了一套标准的JDBC,但是并没有提供具体的实现,同时,也并不知道应该通过那个实现类创建对应的对象,我们便没有办法通过Driver的实现类创建Driver,那么我们应该怎么办呢,在JDK中,便使用SPI机制去加载第三方实现的接口,以此来通过Driver的实现类创建Driver。

JDK中的SPI

我们来看一下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

我们来看一下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.ServiceA
public 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的形式,在加载扩展点的时候,便可以通过扩展点的名称,直接获取扩展点,以此来实现动态加载。

Dubbo SPI的实现原理

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 holder = this.getOrCreateHolder(name);        Object instance = holder.get();        // 无对应的扩展点,通过双重检查锁获取单利的扩展实例        if (instance == null) {            synchronized(holder) {                instance = holder.get();                if (instance == null) {                    // 创建扩展点                    instance = this.createExtension(name, wrap);                    holder.set(instance);                }            }        }        return instance;    }}private Holder getOrCreateHolder(String name) {    Holder holder = (Holder)this.cachedInstances.get(name);    if (holder == null) {        this.cachedInstances.putIfAbsent(name, new Holder());        holder = (Holder)this.cachedInstances.get(name);    }    return holder;}private Map> getExtensionClasses() {    Map> classes = (Map)this.cachedClasses.get();    // 双重检查锁    if (classes == null) {        synchronized(this.cachedClasses) {            classes = (Map)this.cachedClasses.get();            if (classes == null) {                classes = this.loadExtensionClasses();                this.cachedClasses.set(classes);            }        }    }    return classes;}// 扫描所有的resources目录,加载对应文件夹下的扩展点,private Map> loadExtensionClasses() {    this.cacheDefaultExtensionName();    Map> extensionClasses = new HashMap();    LoadingStrategy[] var2 = strategies;    int var3 = var2.length;    for(int var4 = 0; var4 < var3; ++var4) {        LoadingStrategy strategy = var2[var4];        this.loadDirectory(extensionClasses, strategy.directory(), this.type.getName(), strategy.preferExtensionClassLoader(), strategy.overridden(), strategy.excludedPackages());        this.loadDirectory(extensionClasses, strategy.directory(), this.type.getName().replace("org.apache", "com.alibaba"), strategy.preferExtensionClassLoader(), strategy.overridden(), strategy.excludedPackages());    }    return extensionClasses;}

通过name获取对应的扩展点的时候,同样,首先会从缓存中获取,如果缓存中没有,则加载对应的resources目录下的的文件,并将响应的key,value结果缓存起来,如果找不到对应name的扩展点,则抛出异常

SPI的简单实现

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对应的目录,加载对应的文件,一次来加载对应的扩展点

详解   SPI
发表评论
留言与评论(共有 0 条评论) “”
   
验证码:

相关文章

推荐文章