浅析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<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
的对象方法是这样的
有用的只有这三个方法,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");
第一句代码没什么好说的,只是获取一个robot
的extensionloader
对象并且缓存在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的资料请关注其它相关文章!
上一篇: Android版本和目标sdk
下一篇: 如何提升网店排名,淘宝提升排名方法