欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

浅析Java SPI 与 dubbo SPI

程序员文章站 2022-06-16 10:10:44
java原生spi面向接口编程+策略模式实现建立接口robotpublic interface robot { /** * 测试方法1 */ void sayhello()...

java原生spi

面向接口编程+策略模式

实现

建立接口

robot

public interface robot {
    /**
     * 测试方法1
     */
    void sayhello();
}

多个实现类实现接口

robota

public class robota implements robot {
    public robota() {
        system.out.println("happy robota is loaded");
    }
    @override
    public void sayhello() {
        system.out.println("i am a very very happy robot ");
    }
    public void saybye(){}
}

robotb

public class robotb implements robot {
    public robotb() {
        system.out.println("sb robotb is loaded");
    }
    @override
    public void sayhello() {
        system.out.println("i am a da sha bi ");
    }
    public void saybye(){}
}

配置实现类与接口

meta-inf/services目录下建立一个以接口全限定名为名字的文件,里面的内容是实现类的全限定名

原理

通过serviceloader与配置文件中的全限定名加载所有实现类,根据迭代器获取具体的某一个类

我们通过对下面一段代码的分析来说明

serviceloader<robot> serviceloader=serviceloader.load(robot.class);
serviceloader.foreach(robot::sayhello);

load(robot.class)这个方法的目的只是为了设置类加载器为线程上下文加载器,我们当然可以不这么做,直接调用load(class service,classloader loader)方法

public static <s> serviceloader<s> load(class<s> service) {
    classloader cl = thread.currentthread().getcontextclassloader();
    return serviceloader.load(service, cl);
}

这个load方法其实也没有做什么实质的事,仅仅是实例化了一个serviceload对象返回罢了

public static <s> serviceloader<s> load(class<s> service,
                                        classloader loader)
{
    return new serviceloader<>(service, loader);
}

那是不是构造方法做了最核心的事呢?

private serviceloader(class<s> svc, classloader cl) {
    service = objects.requirenonnull(svc, "service interface cannot be null");
    loader = (cl == null) ? classloader.getsystemclassloader() : cl;
    acc = (system.getsecuritymanager() != null) ? accesscontroller.getcontext() : null;
    reload();
}
public void reload() {
    //这里的provider是一个对于已实例化对象的缓存,为map类型
        providers.clear();
        lookupiterator = new lazyiterator(service, loader);
    }

没有,这里仅仅只是检验了参数和权限这样一些准备操作.然后实例化了一个lazyiterator

这是lazyiterator的构造函数

private lazyiterator(class<s> service, classloader loader) {
    this.service = service;
    this.loader = loader;
}

然后....,没了,serviceloader<robot> serviceloader=serviceloader.load(robot.class);执行完毕了,到这里,并没有实例化我们所需要的robot对象,而仅仅只是返回了一个serviceloader对象

这时候如果我们去看serviceloader的对象方法是这样的

浅析Java SPI 与 dubbo SPI

有用的只有这三个方法,reload上面已经提到过,只是重新实例化一个对象而已.

而另外两个iterator()是个迭代器,foreach也只是用于迭代的语法糖罢了.如果我们debug的话,会发现foreach的核心依旧会变成iterator(),好了,接下来重点看iterator()

public iterator<s> iterator() {
    return new iterator<s>() {

        iterator<map.entry<string,s>> knownproviders
            = providers.entryset().iterator();

        public boolean hasnext() {
            if (knownproviders.hasnext())
                return true;
            return lookupiterator.hasnext();
        }

        public s next() {
            if (knownproviders.hasnext())
                return knownproviders.next().getvalue();
            return lookupiterator.next();
        }

        public void remove() {
            throw new unsupportedoperationexception();
        }

    };

这个方法实际上是返回了一个iterator对象.而通过这个iterator,我们可以遍历获取我们所需要的robot对象.

我们来看其用于获取对象的next方法

 public s next() {
            if (knownproviders.hasnext())
                return knownproviders.next().getvalue();
            return lookupiterator.next();
        }

这个方法是先在缓存里找,缓存里找不到,就需要用最开始的实例化的lookupiterator

再来看看它的next方法

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

这方法的核心是nextservice,我们继续看实现,这个方法比较长,我贴一部分核心

if (!hasnextservice())
    throw new nosuchelementexception();
string cn = nextname;
nextname = null;
class<?> c = null;
try {
    c = class.forname(cn, false, loader);
} catch (classnotfoundexception x) {
    fail(service,
         "provider " + cn + " not found");
}

hasnextservice()判断是否还可以继续迭代,通过class.forname反射获取实例,最后再加入到provider缓存中.于是基本逻辑就完成了.那nextname哪来的.是在hasnextservice()中获取的.

依旧只有核心代码

//获取文件
string fullname = prefix + service.getname();
if (loader == null)
    configs = classloader.getsystemresources(fullname);
else
    configs = loader.getresources(fullname);
//解析文件配置
while ((pending == null) || !pending.hasnext()) {
                if (!configs.hasmoreelements()) {
                    return false;
                }
                pending = parse(service, configs.nextelement());
            }
            nextname = pending.next();

根据前缀(即meta-inf/services)和接口的全限定名去找到对应的配置文件.然后加载里面的配置,获取具体实现类的名字.

dubbo增强spi

实现

建立接口

与原生spi不同,dubbo需要加入@spi注解

robot

@spi
public interface robot {
    /**
     * 测试方法1
     */
    void sayhello();
}

多个实现类实现接口

robota

public class robota implements robot {
    public robota() {
        system.out.println("happy robota is loaded");
    }
    @override
    public void sayhello() {
        system.out.println("i am a very very happy robot ");
    }
    public void saybye(){}
}

robotb

public class robotb implements robot {
    public robotb() {
        system.out.println("sb robotb is loaded");
    }
    @override
    public void sayhello() {
        system.out.println("i am a da sha bi ");
    }
    public void saybye(){}
}

配置实现类与接口

meta-inf/dubbo目录下建立一个以接口全限定名为名字的文件,里面的内容是自定义名字与类的全限定名的键值对,举个例子

robota = cn.testlove.double_dubbo.inter.impl.robota
robotb=cn.testlove.double_dubbo.inter.impl.robotb

原理

我们通过对下列代码的调用来进行分析

extensionloader<robot> extensionloader= extensionloader.getextensionloader(robot.class);
robot robotb = extensionloader.getextension("robotb");

第一句代码没什么好说的,只是获取一个robotextensionloader对象并且缓存在map中,下次如果是同样的接口可以直接从map中获取

extensionloader<t> loader = (extensionloader<t>) extension_loaders.get(type);
if (loader == null) {
    extension_loaders.putifabsent(type, new extensionloader<t>(type));
    loader = (extensionloader<t>) extension_loaders.get(type);
}

再来看第二句代码

//从缓存中找
final holder<object> holder = getorcreateholder(name);
object instance = holder.get();
//双重检查
if (instance == null) {
    synchronized (holder) {
        instance = holder.get();
        if (instance == null) {
            instance = createextension(name);
            holder.set(instance);
        }
    }
}

首先从缓存里找,找不到再创建一个新的对象。

再看createextension(name)方法

class<?> clazz = getextensionclasses().get(name);

t instance = (t) extension_instances.get(clazz);
if (instance == null) {
    extension_instances.putifabsent(clazz, clazz.newinstance());
    instance = (t) extension_instances.get(clazz);
}
injectextension(instance);
set<class<?>> wrapperclasses = cachedwrapperclasses;
if (collectionutils.isnotempty(wrapperclasses)) {
    for (class<?> wrapperclass : wrapperclasses) {
        instance = injectextension((t) wrapperclass.getconstructor(type).newinstance(instance));
    }
}
initextension(instance);
return instance;

注意对于class<?> clazz = getextensionclasses().get(name);这一句的理解,这一句是获取配置文件中所有类的class实例,而不是获取所有扩展类的实例。

接下来的流程其实也就简单了从extension_instances缓存中获取instance实例,如果没有,就借助class对象实例化一个,再放入缓存中

接着用这个instance去实例化一个包装类然后返回.自此,一个我们需要的对象产生了.

最后我们看看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;

这里的classes就是用来存各个扩展类class的map缓存,如果不存在的话,会调用loadextensionclasses();去加载,剩下的就是找到对应路径下的配置文件,获取全限定名了

上文我在分析dubbo spi时,多次提到map,缓存二词,我们可以具体有以下这些.其实看名字就大概知道作用了

private final concurrentmap<class<?>, string> cachednames = new concurrenthashmap<>();
private final holder<map<string, class<?>>> cachedclasses = new holder<>()    
private final map<string, object> cachedactivates = new concurrenthashmap<>();
private final concurrentmap<string, holder<object>> cachedinstances = new concurrenthashmap<>();
private final holder<object> cachedadaptiveinstance = new holder<>();
private volatile class<?> cachedadaptiveclass = null;
private string cacheddefaultname;

对比原生的java spi,dubbo的无疑更灵活,可以按需去加载某个类,也可以很便捷的通过自定义的名字去获取类.而且dubbo还支持setter注入.这点以后再讲.

最后提一个问题,java原生的spi只有在用iterator遍历到的时候才会实例化对象,那能不能在遇到自己想要的实现对象时就停止遍历,避免不必要的资源消耗呢?

补充:下面看下dubbo spi 和 java spi 区别?

jdk spi

jdk 标准的 spi 会一次性加载所有的扩展实现,如果有的扩展吃实话很耗时,但

也没用上,很浪费资源。

所以只希望加载某个的实现,就不现实了

dubbo spi

1,对 dubbo 进行扩展,不需要改动 dubbo 的源码

2,延迟加载,可以一次只加载自己想要加载的扩展实现。

3,增加了对扩展点 ioc 和 aop 的支持,一个扩展点可以直接 setter 注入其它扩展点。

3,dubbo 的扩展机制能很好的支持第三方 ioc 容器,默认支持 spring bean。

以上就是java spi 与 dubbo spi的详细内容,更多关于java spi 与 dubbo spi的资料请关注其它相关文章!

相关标签: Java SPI dubbo