浅析Java SPI 与 dubbo SPI

网友投稿 223 2023-01-11

浅析Java SPI 与 dubbo SPI

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 serviceLoader=ServiceLoader.load(Robot.class);

serviceLoader.forEach(Robot::sayHello);

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

public static ServiceLoader load(Class service) {

ClassLoader cl = Thread.currentThread().getContextClassLoader();

return ServiceLoader.load(service, cl);

}

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

public static ServiceLoader load(Class service,

ClassLoader loader)

{

return new ServiceLoader<>(service, loader);

}

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

private ServiceLoader(Class 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 service, ClassLoader loader) {

this.service = service;

this.loader = loader;

}

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

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

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

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

public Iterator iterator() {

return new Iterator() {

Iterator> 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 action = new PrivilegedAction() {

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 extensionLoader= ExtensionLoader.getExtensionLoader(Robot.class);

Robot robotB = extensionLoader.getExtension("robotB");

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

ExtensionLoader loader = (ExtensionLoader) EXTENSION_LOADERS.get(type);

if (loader == null) {

EXTENSION_LOADERS.putIfAbsent(type, new ExtensionLoader(type));

loader = (ExtensionLoader) EXTENSION_LOADERS.get(type);

}

再来看第二句代码

//从缓存中找

final Holder 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> 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> classes = cachedClasses.get();

if (classes == null) {

synchronized (cachedClasses) {

classeqeIPTlNNjs = cachedClasses.get();

if (classes == null) {

classes = loadExtensionClasses();

cachedClasses.set(classes);

}

}

}

return classes;

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

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

private final ConcurrentMap, String> cachedNames = new ConcurrentHashMap<>();

private final Holder>> cachedClasses = new Holder<>()

private final Map cachedActivates = new ConcurrentHashMap<>();

private final ConcurrentMap> cachedInstances = new ConcurrentHashMap<>();

private final Holder 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的资料请关注我们其它相关文章!

版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。

上一篇:邮件发送免费api接口(国内免费邮件api)
下一篇:广州海关快递物流查询单号(广州市进口海关邮件查询)
相关文章

 发表评论

暂时没有评论,来抢沙发吧~