Dubbo-Java SPI与Dubbo SPI

什么是SPI

SPI全称Service Provider Interface,在面向的对象的设计里,不同模块之间推崇面向接口编程,不建议在模块中对实现类进行硬编码。一旦代码里涉及具体的实现类,就违反了可拔插的原则,如果需要替换一种实现,就需要修改代码。SPI使得程序能在ClassPath路径下的META-INF/services文件夹查找接口的实现类,自动加载文件里所定义的实现类。

使用场景

  • 数据库驱动java.sql.Driver,Mysql,Oracle都有自己的驱动实现jar包,只需引入不同的jar包即可加载不同的数据库驱动,例如mysql-connector-java中,其META-INF/services下制定了mysql驱动的实现类
    在这里插入图片描述

  • Dubbo中针对Java SPI做了一层封装,Dubbo SPI更加灵活、高效。如Dubbo SPI使用在根据URL中指定的协议(dubbo、thrift、redis)来动态指定协议,根据URL中指定loadBalance算法来指定特定的负载均衡实现

下面以实例来说明JAVA SPI使用及Dubbo SPI原理

Java SPI

假设定义一个接口ISpiDemoService如下

public interface ISpiDemoService {
    void sayHello();
}

Demo

定义两个实现类

JavaSpiImp1

public class JavaSpiImp1 implements ISpiDemoService {
    @Override
    public void sayHello() {
        System.out.println("java spi implemention 111");
    }
}

JavaSpiImp2

public class JavaSpiImp2 implements ISpiDemoService {
    @Override
    public void sayHello() {
        System.out.println("java spi implemention 222");
    }
}

在META-INF/services下新建文件(名称为接口全限定名),文件内容为接口实现类全限定名
在这里插入图片描述
调用java.util.ServiceLoader#load(java.lang.Class)加载实现类,并测试输出
在这里插入图片描述

缺点

默认加载了所有实现类(然后通过迭代器访问provider实现类),实际上程序了不一定能用到所有的实现,也不能方便的获取某一个实现类。如果能实现按需加载能减少一定程度的资源浪费
在这里插入图片描述

Dubbo SPI

Java SPI 实现类配置不同,Dubbo SPI 是通过键值对的方式进行配置,这样我们可以按需加载指定的实现类

Demo

ISpiDemoService接口上加上@SPI注解
在这里插入图片描述

配置文件中加入key-value格式的配置,key自定义,value为实现类的全路径
在这里插入图片描述
通过ExtensionLoader.getExtensionLoader(ISpiDemoService.class)得到特定接口的类加载器,再通过extensionLoader.getExtension(key)获取特定的实现类
在这里插入图片描述

Dubbo SPI优点

  1. 能够实现按需加载,JDK SPI仅仅通过接口类名获取所有实现,在通过迭代器获取指定实现,而ExtensionLoader则通过接口类名和key值获取一个实现
  2. 支持AOP(将实现类包装在Wrapper中,Wrapper中实现公共增强逻辑)
  3. 支持IOC(能够通过set方法注入其他扩展点)

按需加载,上面的Demo例子里已经体现过了,下面针对AOP、IOC特性分析Dubbo是如何实现的,先了解下Dubbo中类加载的过程

ExtensionLoader.getExtension

首先从ExtensionLoader.getExtension入手看下实现类的加载过程,dubbo中会对SPI中加载的Class及Class的实例进行缓存,其中cachedInstances就是实例的缓存

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) {
                	//调用createExtension创建拓展对象
                    instance = createExtension(name);
                    holder.set(instance);
                }
            }
        }
        return (T) instance;
    }

逻辑比较简单、缓存里找不到对应拓展实现类,就调用createExtension创建一个,下面进入到了createExtension方法

//缓存了 Class及对应的实例
private static final ConcurrentMap<Class<?>, Object> EXTENSION_INSTANCES = new ConcurrentHashMap<Class<?>, Object>();
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) {
            	//反射创建Class对应实例instance
                EXTENSION_INSTANCES.putIfAbsent(clazz, clazz.newInstance());
                instance = (T) EXTENSION_INSTANCES.get(clazz);
            }
            //向实例instance中注入其依赖的对象
            injectExtension(instance);
            Set<Class<?>> wrapperClasses = cachedWrapperClasses;
            //遍历wrapper,向wrapper包装类中注入依赖的对象(将实例本身注入到包装类中)
            if (wrapperClasses != null && !wrapperClasses.isEmpty()) {
                for (Class<?> wrapperClass : wrapperClasses) {
                    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);
        }
    }

看到这里基本扩展类的加载流程就清晰了,分为几个关键步骤

  1. getExtensionClasses()先从resource目录下读取指定位置的配置文件中的拓展类的定义,转化为Class,代码如下:

同样的,针对Class也做了缓存,先从缓存中获取Class。loadDirectory方法从指定位置"META-INF/services/",
“META-INF/dubbo/”,"META-INF/dubbo/internal/"中加载拓展类配置

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

    /**
     * synchronized in getExtensionClasses
     * */
    private Map<String, Class<?>> loadExtensionClasses() {
        cacheDefaultExtensionName();

        Map<String, Class<?>> extensionClasses = new HashMap<>();
        loadDirectory(extensionClasses, DUBBO_INTERNAL_DIRECTORY, type.getName());
        loadDirectory(extensionClasses, DUBBO_INTERNAL_DIRECTORY, type.getName().replace("org.apache", "com.alibaba"));
        loadDirectory(extensionClasses, DUBBO_DIRECTORY, type.getName());
        loadDirectory(extensionClasses, DUBBO_DIRECTORY, type.getName().replace("org.apache", "com.alibaba"));
        loadDirectory(extensionClasses, SERVICES_DIRECTORY, type.getName());
        loadDirectory(extensionClasses, SERVICES_DIRECTORY, type.getName().replace("org.apache", "com.alibaba"));
        return extensionClasses;
    }
  1. 获取到实现类Class后,判断缓存中是否有Class对应的实例,如果没有,则反射生成一个实例
  2. 调用injectExtension注入依赖
  3. 往Wrapper中注入依赖

injectExtension方法是实现IOC依赖注入的关键,往Wrapper中注入依赖,通过Wrapper包装实例,从而在Wrapper的方法中进行方法增强是实现AOP的关键,下面分别分析

IOC

看下injectExtension的实现,其核心思想是遍历其Set方法,从Set方法中取出属性的变量名,从ExtensionFactory中获取依赖对象的实例,再反射调用set方法注入依赖

private T injectExtension(T instance) {

        if (objectFactory == null) {
            return instance;
        }

        try {
        	//遍历所有方法
            for (Method method : instance.getClass().getMethods()) {
            	//不是set方法 continue
                if (!isSetter(method)) {
                    continue;
                }
                //如果方法包含DisableInject注解,不进行注入
                if (method.getAnnotation(DisableInject.class) != null) {
                    continue;
                }
                Class<?> pt = method.getParameterTypes()[0];
                //判断set方法中的类型参数是否是原始类型,如果是原始类型,不进行注入
                if (ReflectUtils.isPrimitives(pt)) {
                    continue;
                }
                try {
                	//获取注入的属性名,比如 setName 方法对应属性名 name
                    String property = getSetterProperty(method);
                    //从objectFactory获取依赖的对象
                    Object object = objectFactory.getExtension(pt, property);
                    if (object != null) {
                    	//反射调用set方法进行注入
                        method.invoke(instance, object);
                    }
                } catch (Exception e) {
                    logger.error("Failed to inject via method " + method.getName()
                            + " of interface " + type.getName() + ": " + e.getMessage(), e);
                }

            }
        } catch (Exception e) {
            logger.error(e.getMessage(), e);
        }
        return instance;
    }

AOP

实现AOP的关键是ExtensionLoader#createExtension实际返回的是Wrapper类(存在对应Wrapper类的情况下)

Set<Class<?>> wrapperClasses = cachedWrapperClasses;
 if (CollectionUtils.isNotEmpty(wrapperClasses)) {
 	//遍历Wrapper类型的Class
     for (Class<?> wrapperClass : wrapperClasses) {
     	//首先反射调用Wrapper的构造方法,创建一个Wrapper类,往Wrapper类中注入依赖
         instance = injectExtension((T) wrapperClass.getConstructor(type).newInstance(instance));
     }
 }

ProtocolListenerWrapper为例,其内部包含Protocol实例,其export,refer方法最终调用的还是Protocol的export,refer方法,但在ProtocolListenerWrapper的export,refer中增强了Protocol的export,refer方法,做了一些额外的判定、封装处理

在这里插入图片描述

最后附上本文中的Demo地址,有兴趣的可以跑一下

https://github.com/hosaos/JavaLearningDemo

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值