SPI
定义
spi(service provider interface)服务提供接口,它是通过约定一种接口实现的规约,来完成接口服务的扩展实现。
它是JDK内置的一种服务提供发现机制,目前市面上有很多框架都是用它来做服务的扩展发现,大家耳熟能详的如jdbc、日志框架都有用到,简单来说,它是一种动态替换发现的机制,举个例子,如果我们定义了一个规范,需要第三方厂商去实现,那么对于我们应用来说,只需要继承对应的厂商插件,既可以完成对应的实现机制。形成一种可插拔式的扩展手段
JDK实现SPI
JDK使用ServiceLoader实现spi的核心功能,
实现原理为
1、从MEDA_INF/services 目录中获取所有的类的配置文件
2、文件名必须是类的全路径包含包名
3、文件中输入所有的实现类
4、文件的格式必须是utf-8
java实现的spi有如下缺点
(1)spi会加载所有的扩展点,不管是否用到,这种记载方式,造成加载不灵活,浪费资源失败,很难进行定位问题
(2)如果spi加载
Dubbo实现SPI
dubbo修正了这样的缺点,并进行了改进,
dubbo实现中,大量使用了这种功能,所以想学习dubbo源码。必须学好spi,同时在dubbo的实现中也大量使用了缓存,就是内存缓存,在获取所有的对象之前都会先判断是否有相应的缓存,从这个方面可以看出,dubbo是基础单例模式实现的
解析的两种方式
Protocol protocol = ExtensionLoader. getExtensionLoader(Protocol.class). getExtension("myProtocol");
Protocol protocol = ExtensionLoader.getExtensionLoader(Protocol.class). getAdaptiveExtension();
getAdaptiveExtension源码解析
首先针对第二个进行源码分析,看一下它是如何获取一个动态扩展点的实例的
测试以Protocol为例
执行分为两个部分,首先是调用了 ExtensionLoader.getExtensionLoader(Protocol.class),那我们先从这里出发,一步步揭开它的神秘面纱
getExtensionLoader解析
//判断是否为空
if (type == null)
throw new IllegalArgumentException("Extension type == null");
//判断该类型是否接口
if(!type.isInterface()) {
throw new IllegalArgumentException("Extension type(" + type + ") is not interface!");
}
//判断是否有SPI扩展点的备注@SPI
if(!withExtensionAnnotation(type)) {
throw new IllegalArgumentException("Extension type(" + type +
") is not extension, because WITHOUT @" + SPI.class.getSimpleName() + " Annotation!");
}
//首先从缓存中查询是否已经有该类型的扩展器,如果有则取出
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);
}
return loader;
继续看下 创建一个新的扩展器,它的构造方法里做了些什么
//保存Protocol.class对象 this.type = type; //构建一个objectFactory对象,字面意思就是一个对象工厂 //它的定义是 private final ExtensionFactory objectFactory; objectFactory = (type == ExtensionFactory.class ? null : ExtensionLoader.getExtensionLoader(ExtensionFactory.class). getAdaptiveExtension());
暂时在这里不进行展开,在讲完上面的Protocol扩展器之后,在回看此方法,应该就能看懂了
书接上文,讲到就此构建了扩展器对象,这时候ExtensionLoader. getExtensionLoader(Protocol.class)代表的就是ExtensionLoader的实例对象了。
那么接下来我们继续解析后面的内容getAdaptiveExtension(),它的意思是获取一个可适配的扩展点,它最终会获取一个符合条件自适应的扩展对象实例,它是怎么做到的呢,且看源码
//不变的是它还是先从缓存中读取该实例
Object instance = cachedAdaptiveInstance.get();
//对象为空
if (instance == null) {
//且没有创建适配对象的错误存在
if(createAdaptiveInstanceError == null) {
//采用了双重检查锁 去重新创建新的适配对象,这里是进行了同步操作
synchronized (cachedAdaptiveInstance) {
instance = cachedAdaptiveInstance.get();
if (instance == null) {
try {
//上面又对缓存做了判断,是否存在该对象,
//如果没有则走下面 新建流程
instance = createAdaptiveExtension();
cachedAdaptiveInstance.set(instance);
} catch (Throwable t) {
createAdaptiveInstanceError = t;
throw new IllegalStateException("fail to create adaptive instance: " + t.toString(), t);
}
}
}
}
else {
throw new IllegalStateException("fail to create adaptive instance: " + createAdaptiveInstanceError.toString(), createAdaptiveInstanceError);
}
}
return (T) instance;
下面看下具体的创建过程 看方法createAdaptiveExtension
这里面一句话 做了很多的事情,通过嵌套方法调用,首先通过getAdaptiveExtensionClass()获取一个适配的class对象,接着对这个对象进行构建一个实例的操作,最后通过injectExtension方法,对该实例中所有set开头的方法进行依赖注入操作。
//可以实现扩展点的注入 return injectExtension((T) getAdaptiveExtensionClass().newInstance());
那么继续往下挖吧 >>getAdaptiveExtensionClass,就是你了,不要躲闪,你已经发光了
//获取一个扩展class对象
getExtensionClasses();
//TODO 判断是否缓存中存在该适配class对象,如果有则直接发回
if (cachedAdaptiveClass != null) {
return cachedAdaptiveClass; //AdaptiveCompiler
}
//这里就是创建一个新的适配器对象了
return cachedAdaptiveClass = createAdaptiveExtensionClass();
都到这份上了 继续吧,迫不及待的想去征服它,,,嘎嘎getExtensionClasses 我来了
createAdaptiveExtensionClass
//不出所料的 还是从缓存从获取
Map<String, Class<?>> classes = cachedClasses.get();
if (classes == null) {
synchronized (cachedClasses) {
classes = cachedClasses.get();
if (classes == null) {
//同样经历了双重检查锁之后,没有得到想要的结果,然后愤然自立更生,加载属于自己的扩展点,
//该我表现的时刻终于来临了,come on
classes = loadExtensionClasses();
cachedClasses.set(classes);
}
}
}
return classes;
一条路走到黑,看它还有什么幺蛾子,loadExtensionClasses 来吧 让我在来看你一眼,好像某歌词,
蛾子来了,看来还是比较大啊,比较之前,代码已经增多不少,简单一看,发现了3个load操作,那么跟进吧
loadExtensionClasses
//type->Protocol.class
//得到SPI的注解
final SPI defaultAnnotation = type.getAnnotation(SPI.class);
if(defaultAnnotation != null) { //如果不等于空.
String value = defaultAnnotation.value();
if(value != null && (value = value.trim()).length() > 0) {
String[] names = NAME_SEPARATOR.split(value);
if(names.length > 1) {
throw new IllegalStateException("more than 1 default extension name on extension " + type.getName()
+ ": " + Arrays.toString(names));
}
if(names.length == 1) cachedDefaultName = names[0];
}
}
Map<String, Class<?>> extensionClasses = new HashMap<String, Class<?>>();
loadFile(extensionClasses, DUBBO_INTERNAL_DIRECTORY);
loadFile(extensionClasses, DUBBO_DIRECTORY);
loadFile(extensionClasses, SERVICES_DIRECTORY);
return extensionClasses;
本来不想把源码都贴进来的,后来发现,如果以小片段来分解 会有种 只有点没有面的尴尬,还是把代码都整理进来,这样就不用针对源码在对一遍了,因为亲,你看的都是原封不动的源码啊,
里面写的非常亲民,发现好多都是我们日常中很常见的,我初看这个源码的时候,错愕了一阵,这就是源码么,怎么没有其它框架的特质呢(ps:看不懂,懵圈的感觉没有出现),很是诧异,看来国产的还是走的爱国路线啊,必须赞一个,
代码的核心就是读取配置文件中所有的扩展点类,首先判断该扩展点类上是否有@Adaptive的注册,因为有这种注解的类,就是命中注定的不平凡呐,一注解注一生啊!它的伟大作用就是指定了它作为扩展器对象,无他,就此一个,其次就是判断该类中是否存在一个 clazz.getConstructor(type)这样的构造方法,如果存在则放入缓存,这个是wrapper类型的类,对具体的扩展对象 进行包装操作,之后进行一个扩展的功能处理,那么不是这两种呢,还是存到一个缓存中以备后用啊,在异常里我们看到了其他扩展点的身影,备胎的滋味,不好受啊,,,
走到这一步已经获取到需要的东东了,扩展点class已经确定了,或者有,或者需要创建动态可适配的,都有了定论
loadFile
String fileName = dir + type.getName();
try {
Enumeration<java.net.URL> urls;
ClassLoader classLoader = findClassLoader();
if (classLoader != null) {
urls = classLoader.getResources(fileName);
} else {
urls = ClassLoader.getSystemResources(fileName);
}
if (urls != null) {
while (urls.hasMoreElements()) {
java.net.URL url = urls.nextElement();
try {
BufferedReader reader = new BufferedReader(new InputStreamReader(url.openStream(), "utf-8"));
try {
String line = null;
while ((line = reader.readLine()) != null) {
final int ci = line.indexOf('#');
if (ci >= 0) line = line.substring(0, ci);
line = line.trim();
if (line.length() > 0) {
try {
String name = null;
int i = line.indexOf('=');
if (i > 0) {//文件采用name=value方式,通过i进行分割
name = line.substring(0, i).trim();
line = line.substring(i + 1).trim();
}
if (line.length() > 0) {
Class<?> clazz = Class.forName(line, true, classLoader);
//加载对应的实现类,并且判断实现类必须是当前的加载的扩展点的实现
if (! type.isAssignableFrom(clazz)) {
throw new IllegalStateException("Error when load extension class(interface: " +
type + ", class line: " + clazz.getName() + "), class "
+ clazz.getName() + "is not subtype of interface.");
}
//判断是否有自定义适配类,如果有,则在前面讲过的获取适配类的时候,直接返回当前的自定义适配类,不需要再动态创建
if (clazz.isAnnotationPresent(Adaptive.class)) {
if(cachedAdaptiveClass == null) {
cachedAdaptiveClass = clazz;
} else if (! cachedAdaptiveClass.equals(clazz)) {
throw new IllegalStateException("More than 1 adaptive class found: "
+ cachedAdaptiveClass.getClass().getName()
+ ", " + clazz.getClass().getName());
}
} else {
try {
//如果没有Adaptive注解,则判断当前类是否带有参数是type类型的构造函数,如果有,则认为是
//wrapper类。这个wrapper实际上就是对扩展类进行装饰.
//可以在dubbo-rpc-api/internal下找到Protocol文件,发现Protocol配置了3个装饰
//分别是,filter/listener/mock. 所以Protocol这个实例来说,会增加对应的装饰器
clazz.getConstructor(type);//
//得到带有public DubboProtocol(Protocol protocol)的扩展点。进行包装
Set<Class<?>> wrappers = cachedWrapperClasses;
if (wrappers == null) {
cachedWrapperClasses = new ConcurrentHashSet<Class<?>>();
wrappers = cachedWrapperClasses;
}
wrappers.add(clazz);//包装类 ProtocolFilterWrapper(ProtocolListenerWrapper(Protocol))
} catch (NoSuchMethodException e) {
clazz.getConstructor();
if (name == null || name.length() == 0) {
name = findAnnotationName(clazz);
if (name == null || name.length() == 0) {
if (clazz.getSimpleName().length() > type.getSimpleName().length()
&& clazz.getSimpleName().endsWith(type.getSimpleName())) {
name = clazz.getSimpleName().substring(0, clazz.getSimpleName().length() - type.getSimpleName().length()).toLowerCase();
} else {
throw new IllegalStateException("No such extension name for the class " + clazz.getName() + " in the config " + url);
}
}
}
String[] names = NAME_SEPARATOR.split(name);
if (names != null && names.length > 0) {
Activate activate = clazz.getAnnotation(Activate.class);
if (activate != null) {
cachedActivates.put(names[0], activate);
}
for (String n : names) {
if (! cachedNames.containsKey(clazz)) {
cachedNames.put(clazz, n);
}
Class<?> c = extensionClasses.get(n);
if (c == null) {
extensionClasses.put(n, clazz);
} else if (c != clazz) {
throw new IllegalStateException("Duplicate extension " + type.getName() + " name " + n + " on " + c.getName() + " and " + clazz.getName());
}
}
}
}
}
}
} catch (Throwable t) {
IllegalStateException e = new IllegalStateException("Failed to load extension class(interface: " + type + ", class line: " + line + ") in " + url + ", cause: " + t.getMessage(), t);
exceptions.put(line, e);
}
}
} // end of while read lines
} finally {
reader.close();
}
} catch (Throwable t) {
logger.error("Exception when load extension class(interface: " +
type + ", class file: " + url + ") in " + url, t);
}
} // end of while urls
}
} catch (Throwable t) {
logger.error("Exception when load extension class(interface: " +
type + ", description file: " + fileName + ").", t);
}
接下来回到初始的调用点getExtensionClasses方法我们已经走完流程了,那么继续往下走,查看缓存中是否存在适配点,如果没有则需要进行动态生成一个了,有的话不用说了 直接返回,如果没有则走该方法,进行动态生成一个适配class
createAdaptiveExtensionClass
getExtensionClasses();
//TODO 不一定?
if (cachedAdaptiveClass != null) {
return cachedAdaptiveClass; //AdaptiveCompiler
}
return cachedAdaptiveClass = createAdaptiveExtensionClass();
来来来,让我们看一下,通过下面的代码生成并且完成了扩展点class
createAdaptiveExtensionClass
//生成字节码代码 String code = createAdaptiveExtensionClassCode(); //获得类加载器 ClassLoader classLoader = findClassLoader(); com.alibaba.dubbo.common.compiler.Compiler compiler = ExtensionLoader.getExtensionLoader(com.alibaba.dubbo.common.compiler.Compiler.class).getAdaptiveExtension(); //动态编译字节码 return compiler.compile(code, classLoader);
上面生成的类的源码有必要展示一下,这样可以明白它到底是如何实现的适配的
public class Protocol$Adaptive implements
com.alibaba.dubbo.rpc.Protocol {
public void destroy() {
throw new UnsupportedOperationException("method
public abstract void com.alibaba.dubbo.rpc.Protocol.destroy()
of interface com.alibaba.dubbo.rpc.Protocol is not adaptive
method!");
}
public int getDefaultPort() {
throw new UnsupportedOperationException("method
public abstract int
com.alibaba.dubbo.rpc.Protocol.getDefaultPort() of interface
com.alibaba.dubbo.rpc.Protocol is not adaptive method!");
}
public com.alibaba.dubbo.rpc.Invoker refer(java.lang.Class
arg0, com.alibaba.dubbo.common.URL arg1) throws
com.alibaba.dubbo.rpc.RpcException {
if (arg1 == null) throw new
IllegalArgumentException("url == null");
com.alibaba.dubbo.common.URL url = arg1;
String extName = (url.getProtocol() == null ?
"dubbo" : url.getProtocol());
if (extName == null)
throw new IllegalStateException("Fail to get
extension(com.alibaba.dubbo.rpc.Protocol) name from url(" +
url.toString() + ") use keys([protocol])");
com.alibaba.dubbo.rpc.Protocol extension =
(com.alibaba.dubbo.rpc.Protocol)
ExtensionLoader. getExtensionLoader (com.alibaba.dubbo.rpc.P
rotocol.class).getExtension(extName);
return extension.refer(arg0, arg1);
}
public com.alibaba.dubbo.rpc.Exporter
export(com.alibaba.dubbo.rpc.Invoker arg0) throws
com.alibaba.dubbo.rpc.RpcException {
if (arg0 == null) throw new
IllegalArgumentException("com.alibaba.dubbo.rpc.Invoker
argument == null");
if (arg0.getUrl() == null)
throw new
IllegalArgumentException("com.alibaba.dubbo.rpc.Invoker
argument getUrl() == null");
com.alibaba.dubbo.common.URL url = arg0.getUrl();
String extName = (url.getProtocol() == null ?
"dubbo" : url.getProtocol());
if (extName == null)
throw new IllegalStateException("Fail to get
extension(com.alibaba.dubbo.rpc.Protocol) name from url(" +
url.toString() + ") use keys([protocol])");
com.alibaba.dubbo.rpc.Protocol extension =
(com.alibaba.dubbo.rpc.Protocol)
ExtensionLoader. getExtensionLoader (com.alibaba.dubbo.rpc.P
rotocol.class).getExtension(extName);
return extension.export(arg0);
}
}
createAdaptiveExtensionClassCode方法具体的创建过程就不贴代码了,并不影响对SPI的理解,到这一步就创建完成了一个适配点
PS 之前的objectFactory 我们现在可以讲一下了,通过上文你应该能够自己去试着理解了,它同样是创建了一个适配点对象,在配置文件中我们发现它的实现类AdaptiveExtensionFactory类上面有注解@Adaptive 于是乎,聪明的你应该能想到是怎么回事了,对,创建的就是这个对象,上面已经交代清楚了,如果还是不清楚,请回看,看仔细 看明白 ,
getExtension("myProtocol")源码解析
上面讲解的是获取动态的适配器扩展点,这个就是获取指定的扩展点,参数里面传递着具体的扩展点名称,好吧,我们开始吧
开始还是一通判断上面已经有讲解,不在重复了
getExtension("myProtocol")
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) {
//关键点 创建指定的扩展点
instance = createExtension(name);
holder.set(instance);
}
}
}
return (T) instance;
这里面的的第一句看到了熟悉的方法的身影getExtensionClasses,它不就是上面讲的去加载扩展类的么,这里亦不在重复,接下来就是创建该类型的对象了,通过反射,都知道的云云,,,
然后还是有injectExtension依赖注入这一块,就是注入有set方法的,在往下有一个重点就是注入wrapper对象进行扩展点的包装,形成a>b>dubbo这种形式,最后把该扩展点对象进行返回,就完成了指定扩展点对象的获取
createExtension
SPI机制解析

553

被折叠的 条评论
为什么被折叠?



