为什么枚举要实现接口?
220
2022-10-22
浅析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.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的对象方法是这样的
有用的只有这三个方法,reload上面已经提到过,只是重新实例化一个对象而已.
而另外两个iterator()是个迭代器,foreach也只是用于迭代的语法糖罢了.如果我们debug的话,会发现foreach的核心依旧会变成iterator(),好了,接下来重点看iterator()
public Iterator iterator() {
return new Iterator() {
Iterator
= 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
Robot robotB = extensionLoader.getExtension("robotB");
第一句代码没什么好说的,只是获取一个Robot的ExtensionLoader对象并且缓存在Map中,下次如果是同样的接口可以直接从map中获取
ExtensionLoader
if (loader == null) {
EXTENSION_LOADERS.putIfAbsent(type, new ExtensionLoader
loader = (ExtensionLoader
}
再来看第二句代码
//从缓存中找
final Holder
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
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
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
private final Holder
private final Map
private final ConcurrentMap
private final 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小时内删除侵权内容。
发表评论
暂时没有评论,来抢沙发吧~