解析:Java SPI到Dubbo SPI

1,752 阅读7分钟

SPI概述

SPI全称Service Provider Interface(服务提供者接口),是Java提供的一套用来被第三方实现或者扩展的API,它可以用来启用框架扩展和替换组件

SPI使用场景

  • SPI在Java核心库中被广泛使用,如JDBC、JNDI、JCE体系、Java文件系统、JAXP等。SPI允许第三方为这些接口提供实现

  • spring-boot-starter,spring中各种功能的集成,只需要引入boot-starter包,即可引入复杂的插件。同样是利用了SPI机制

  • 插件开发,比如idea插件可以在resources/META-INF/plugin.xml中自定义扩展;Eclipse插件遍历plugins文件夹中的目录,扫描每个插件的清单文件MANIFEST.MF等都是基于SPI思想

  • Dubbo中通过SPI加载扩展

    ......

Java SPI

假设我们有一个加密的接口,需要动态的选择加密算法,那么只需要声明一个加密接口,并提供各自算法的实现,具体的实现并不在程序中确定,而是由程序之外的配置确定

Java SPI 示例

1、定义接口:

public interface EncrypitionService{

    void encrypt();

}

2、定义RSA加密的实现:

@Service
public class RsaEncryptServiceImpl implements EncrypitionService {

    @Override
    public void encrypt() {
        LogTools.info("RSA encrypt!");
    }
}

3、定义MD5加密的实现:

@Service
public class Md5EncryptServiceImpl implements EncrypitionService {

    @Override
    public void encrypt() {
        LogTools.info("MD5 encrypt!");
    }
}

4、新建src\main\resources\META-INF\services目录,在目录下新建以EncrypitionService全限定名命名的文件com.xxx.service.EncrypitionService,内容为EncrypitionService接口的实现类:

com.xxx.service.impl.Md5EncryptServiceImpl
com.xxx.service.impl.RsaEncryptServiceImpl

5、加密算法调用:

public class EncryptMain {
    public static void main(String[] args) {
        ServiceLoader<EncrypitionService> encrypts = ServiceLoader.load(EncrypitionService.class);
        for (EncrypitionService service:encrypts){
            service.encrypt();
        }
    }
}

6、执行EncryptMain

img

在EncryptMain中,通过构建ServiceLoader实例,遍历ServiceLoader实例来达到了执行META-INF\目录下配置的加密实现的调用。EncrypitionService的具体实现可以由文件com.xxx.service.EncrypitionService中配置的实现类来决定

源码分析

下面分析ServiceLoader的核心代码,ServiceLoader实现了Iterable接口,在程序中遍历返回的ServiceLoader实例时,会使用创建的迭代器,ServiceLoader的实际加载过程就交给了LazyIterator来做。

服务提供者懒加载的迭代器

private class LazyIteratorimplements Iterator<S>{

        Class<S> service;
        ClassLoader loader;
        // 配置文件资源的URL
        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 {
                    //资源名称为META-INF/services + '类的全限定类名'
                    String fullName = PREFIX + service.getName();
                    if (loader == null)
                        configs = ClassLoader.getSystemResources(fullName);
                    else
                        // 从ClassPath加载资源
                        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 {
                // 反射构造Class<S>实例
                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 {
                //通过Class#newInstance()进行实例化,并且强制转化为对应的类型的实例
                S p = service.cast(c.newInstance());
                //添加缓存,Key为实现类的全限定名,Value为实现类的实例
                providers.put(cn, p);
                return p;
            } catch (Throwable x) {
                fail(service,
                     "Provider " + cn + " could not be instantiated",
                     x);
            }
            throw new Error();          // This cannot happen
        }

        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();
        }

    }

ServiceLoader的原理其实不难理解,根据给定的参数(接口)定位到该接口与实现类的映射配置文件的路径,然后读取该配置文件,获取到该接口的子类限定名,再通过反射去构造实例。

Dubbo SPI使用

Dubbo并未使用 Java 原生的 SPI 机制,而是对其进行了增强,使其能够更好的满足需求。在 Dubbo 中,SPI 是一个非常重要的模块。Dubbo通过SPI加载了所有的组件

Dubbo SPI示例

1、定义接口:Dubbo SPI 通过键值对的方式进行配置,可以按需加载指定的实现类,需要在接口上标注 @SPI 注解

@SPI
public interface EncrypitionService{

    void encrypt();

}

2、 在META-INF/dubbo下新增com.alibaba.dubbo.rpc.service.EncrypitionService文件

RsaEncryptServiceImpl= com.xxx.service.impl.RsaEncryptServiceImpl

5、通过ExtensionLoader获取RsaEncryptServiceImpl实现

public class main {

    public static void main(String[] args) {
        EncrypitionService service= ExtensionLoader.getExtensionLoader(EncrypitionService.class).getExtension("RsaEncryptServiceImpl");
        service.encrypt();
    }
}

img

原码解析

从上面的例子分析ExtensionLoader的工作过程,ExtensionLoader的入口分为getExtension、getActivateExtension、getAdaptiveExtension,下面对getExtension的实现原理做分析

    @SuppressWarnings("unchecked")
    public T getExtension(String name) {
        if (name == null || name.length() == 0)
            throw new IllegalArgumentException("Extension name == null");
        if ("true".equals(name)) {
            // 自适应扩展类
            return getDefaultExtension();
        }
        // 检查缓存中是否存在所需数据 没有则创建并缓存
        Holder<Object> holder = cachedInstances.get(name);
        if (holder == null) {
            cachedInstances.putIfAbsent(name, new Holder<Object>());
            holder = cachedInstances.get(name);
        }
        Object instance = holder.get();
        // 双重校验锁
        if (instance == null) {
            synchronized (holder) {
                instance = holder.get();
                if (instance == null) {
                    // 根据名称创建扩展实现对象
                    instance = createExtension(name);
                    holder.set(instance);
                }
            }
        }
        return (T) instance;
    }

开始创建扩展类对象

  1. 通过 getExtensionClasses 获取所有的拓展类
  2. 通过反射创建拓展对象
  3. 向拓展对象中注入依赖
  4. 将拓展对象包裹在相应的 Wrapper 对象中
    private T createExtension(String name) {
        // 尝试从缓存获取Class,没有则调用创建
        Class<?> clazz = getExtensionClasses().get(name);
        if (clazz == null) {
            throw findException(name);
        }
        try {
            T instance = (T) EXTENSION_INSTANCES.get(clazz);
            if (instance == null) {
                // 通过反射创建出实例
                EXTENSION_INSTANCES.putIfAbsent(clazz, (T) clazz.newInstance());
                instance = (T) EXTENSION_INSTANCES.get(clazz);
            }
            // IOC实现 注入依赖
            injectExtension(instance);
            // 从配置文件中获取的wrapper对象
            Set<Class<?>> wrapperClasses = cachedWrapperClasses;
            if (wrapperClasses != null && wrapperClasses.size() > 0) {
                for (Class<?> wrapperClass : wrapperClasses) {
                // AOP实现
                // 将当前 instance 作为参数传给 Wrapper 的构造方法,并通过反射创建 Wrapper 实例。
                // 然后向 Wrapper 实例中注入依赖,最后将 Wrapper 实例再次赋值给 instance 变量
                    instance = injectExtension((T) wrapperClass.getConstructor(type).newInstance(instance));
                }
            }
            return instance;
        } catch (Throwable t) {
            throw new IllegalStateException("Extension instance(name: " + name + ", class: " +
                    type + ")  could not be instantiated: " + t.getMessage(), t);
        }
    }

Dubbo IOC:通过反射调用setter方法设置依赖(下例注释来自于dubbo官网)

private T injectExtension(T instance) {
    try {
        if (objectFactory != null) {
            // 遍历目标类的所有方法
            for (Method method : instance.getClass().getMethods()) {
                // 检测方法是否以 set 开头,且方法仅有一个参数,且方法访问级别为 public
                if (method.getName().startsWith("set")
                    && method.getParameterTypes().length == 1
                    && Modifier.isPublic(method.getModifiers())) {
                    // 获取 setter 方法参数类型
                    Class<?> pt = method.getParameterTypes()[0];
                    try {
                        // 获取属性名,比如 setName 方法对应属性名 name
                        String property = method.getName().length() > 3 ? 
                            method.getName().substring(3, 4).toLowerCase() + 
                                method.getName().substring(4) : "";
                        // 从 ObjectFactory 中获取依赖对象
                        Object object = objectFactory.getExtension(pt, property);
                        if (object != null) {
                            // 通过反射调用 setter 方法设置依赖
                            method.invoke(instance, object);
                        }
                    } catch (Exception e) {
                        logger.error("fail to inject via method...");
                    }
                }
            }
        }
    } catch (Exception e) {
        logger.error(e.getMessage(), e);
    }
    return instance;
}

加载扩展类配置信息

private Map<String, Class<?>> getExtensionClasses() {
        // 加载扩展类Class
        Map<String, Class<?>> classes = cachedClasses.get();
        if (classes == null) {
            synchronized (cachedClasses) {
                classes = cachedClasses.get();
                if (classes == null) {
                    classes = loadExtensionClasses();
                    cachedClasses.set(classes);
                }
            }
        }
        return classes;
    }

实例化扩展类

   private Map<String, Class<?>> loadExtensionClasses() {
        // 检查是否有SPI注解,如果有则获取value,并作为默认实现名,放入缓存
        final SPI defaultAnnotation = type.getAnnotation(SPI.class);
        if (defaultAnnotation != null) {
            String value = defaultAnnotation.value();
            if (value != null && (value = value.trim()).length() > 0) {
                String[] names = NAME_SEPARATOR.split(value);
                if (names.length > 1) {
                    throw new IllegalStateException("more than 1 default extension name on extension " + type.getName()
                            + ": " + Arrays.toString(names));
                }
                if (names.length == 1) cachedDefaultName = names[0];
            }
        }

        Map<String, Class<?>> extensionClasses = new HashMap<String, Class<?>>();
        // 到指定目录查找并解析配置文件
        loadFile(extensionClasses, DUBBO_INTERNAL_DIRECTORY);
        loadFile(extensionClasses, DUBBO_DIRECTORY);
        loadFile(extensionClasses, SERVICES_DIRECTORY);
        return extensionClasses;
    }

查找扩展类实现,注入扩展类实例

private void loadFile(Map<String, Class<?>> extensionClasses, String dir) {
        String fileName = dir + type.getName();
        try {
            Enumeration<java.net.URL> urls;
            ClassLoader classLoader = findClassLoader();
            // 获取配置文件
            if (classLoader != null) {
                urls = classLoader.getResources(fileName);
            } else {
                urls = ClassLoader.getSystemResources(fileName);
            }
            // 遍历urls,解析字符串,得到扩展实现类,并缓存
            if (urls != null) {
                while (urls.hasMoreElements()) {
                   ......
                        // 自适应
                        if (clazz.isAnnotationPresent(Adaptive.class)) {
                            if (cachedAdaptiveClass == null) {
                                cachedAdaptiveClass = clazz;
                            } else if (!cachedAdaptiveClass.equals(clazz)) {
                                throw new IllegalStateException("More than 1 adaptive class found: "
                                        + cachedAdaptiveClass.getClass().getName()
                                        + ", " + clazz.getClass().getName());
                            }
                        } else {
                            try {
                                clazz.getConstructor(type);
                                Set<Class<?>> wrappers = cachedWrapperClasses;
                                if (wrappers == null) {
                                    cachedWrapperClasses = new ConcurrentHashSet<Class<?>>();
                                    wrappers = cachedWrapperClasses;
                                }
                                wrappers.add(clazz);
                            } catch (NoSuchMethodException e)
                    ......


                } // end of while urls
            }
        } catch (Throwable t) {
            logger.error("Exception when load extension class(interface: " +
                    type + ", description file: " + fileName + ").", t);
        }
    }

可以看到getExtension根据SPI配置加载了所有扩展类并缓存起来,并只初始化了传入名称的对应的扩展类实例,并且实现了对扩展类属性进行依赖注入的功能即IOC,同时也提供了利用装饰者模式实现的AOP功能,本文因为篇幅有限对Dubbo中IOC、AOP的实现暂不做过多扩展,后续再更新

总结

SPI的思想在各大框架中经常会被使用,它通过服务发现的机制把装配的控制权移交到程序之外,实现了程序扩展可插拔。对比Java SPI和Dubbo SPI,Java SPI内部的实现使用迭代器,在使用时需要遍历服务提供者使用if判断才能获取指定提供者,而Dubbo SPI可直接获取指定名称的扩展,也可通过注解声明默认扩展;Java SPI不支持依赖注入,如果依赖了其他扩展,做不到自动注入,而Duubo SPI则增加了对IOC、AOP的支持。