dubbo中SPI接口的定义如下:

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
public @interface SPI { /**
* 缺省扩展点名。
*/
String value() default ""; }

  dubbo默认的情况下,会依次从下面几个文件中读取扩展点。1.META-INF/dubbo/internal/   //dubbo内部实现的各种扩展都放在了这个目录了。2.META-INF/dubbo/。3.META-INF/services/。只有打了@SPI注解的接口类dubbo才会去查找扩展点实现。

  我们以Protocol为例,Protocol接口上打了SPI注解,默认的扩展点名称为dubbo

@SPI("dubbo")
public interface Protocol { int getDefaultPort(); @Adaptive
<T> Exporter<T> export(Invoker<T> invoker) throws RpcException; @Adaptive
<T> Invoker<T> refer(Class<T> type, URL url) throws RpcException; void destroy(); }

  dubbo中内置了各种协议,如DubboProtocol,HttpProtocol,HessianProtocol等等。Dubbo默认rpc模块默认protocol实现DubboProtocol,key为dubbo

ExtensionLoader类

1.ExtensionLoder.getExtensionLoader(Class<T> type)方法

  每个定义的SPI接口,都会创建一个ExtensionLoader实例,存储在ConcurrentMap<Class<?>,ExtensionLoader<?>> EXTENSION_LOADERS这个map对象中

2.ExtensionLoader使用loadExtensionClasses方法读取扩展点中的实现类

  loadExtensionClasses先读取SPI注解的value值,如果value有值,就把这个值作为默认扩展实现的key。然后再以此读取META-INF/dubbo/internal/,META-INF/dubbo/,META-INF/services/下对应的文件。

3.我们以Protocal为例,  loadFile逐行读取com.alibaba.dubbo.rpc.Protocol文件中的内容,每行内容以key/value形式存储。先判断实现类上是否打上了@Adaptive注解,如果打上了该注解,将此类作为Protocol协议的设配类缓存起来,读取下一行。如果实现类上没有打上@Adaptive注解,判断实现类是否存在参数为该接口的构造器,有的话作为包装类存储在该ExtensionLoader的Set<Class<?>> cachedWrapperClasses;集合中,这里用到了装饰器模式。如果该类既不是设配类,也不是wrapper对象,那就是扩展点的具体实现对象,查找实现类上是否打了@Activate注解,有缓存到变量cachedActivates的map中将实现类缓存到cachedClasses中,以便于使用时获取。如ProtocolFilterWrapper的实现如下:

public class ProtocolFilterWrapper implements Protocol {

    private final Protocol protocol;

    public ProtocolFilterWrapper(Protocol protocol) {
if (protocol == null) {
throw new IllegalArgumentException("protocol == null");
}
this.protocol = protocol;
}
..........
}

4.获取或则创建设配对象getAdaptiveExtension

  如果cachedAdaptiveClass有值,说明有且仅有一个实现类打了@Adaptive, 实例化这个对象返回。如果cachedAdaptiveClass为空, 创建设配类字节码。

  为什么要创建设配类,一个接口多种实现,SPI机制也是如此,这是策略模式,但是我们在代码执行过程中选择哪种具体的策略呢。Dubbo采用统一数据模式com.alibaba.dubbo.common.URL(它是dubbo定义的数据模型不是jdk的类),它会穿插于系统的整个执行过程,URL中定义的协议类型字段protocol,会根据具体业务设置不同的协议。url.getProtocol()值可以是dubbo也是可以webservice, 可以是zookeeper也可以是redis。

  设配类的作用是根据url.getProtocol()的值extName,去ExtensionLoader. getExtension( extName)选取具体的扩展点实现。

  有上述的分析可知,能够使用javasist生成设配类的条件:

    1)接口方法中必须至少有一个方法打上了@Adaptive注解

    2)打上了@Adaptive注解的方法参数必须有URL类型参数或者有参数中存在getURL()方法

  createAdaptiveExtensionClass方法源码如下:

private String createAdaptiveExtensionClassCode() {
StringBuilder codeBuidler = new StringBuilder();
Method[] methods = type.getMethods();
boolean hasAdaptiveAnnotation = false;
for (Method m : methods) {
if (m.isAnnotationPresent(Adaptive.class)) {
hasAdaptiveAnnotation = true;
break;
}
}
// 完全没有Adaptive方法,则不需要生成Adaptive类
if (!hasAdaptiveAnnotation)
throw new IllegalStateException("No adaptive method on extension " + type.getName() + ", refuse to create the adaptive class!"); codeBuidler.append("package " + type.getPackage().getName() + ";");
codeBuidler.append("\nimport " + ExtensionLoader.class.getName() + ";");
codeBuidler.append("\npublic class " + type.getSimpleName() + "$Adaptive" + " implements " + type.getCanonicalName() + " {"); for (Method method : methods) {
Class<?> rt = method.getReturnType();
Class<?>[] pts = method.getParameterTypes();
Class<?>[] ets = method.getExceptionTypes(); Adaptive adaptiveAnnotation = method.getAnnotation(Adaptive.class);
StringBuilder code = new StringBuilder();
if (adaptiveAnnotation == null) {
code.append("throw new UnsupportedOperationException(\"method ")
.append(method.toString()).append(" of interface ")
.append(type.getName()).append(" is not adaptive method!\");");
} else {
int urlTypeIndex = -;
for (int i = ; i < pts.length; ++i) {
if (pts[i].equals(URL.class)) {
urlTypeIndex = i;
break;
}
}
// 有类型为URL的参数
if (urlTypeIndex != -) {
// Null Point check
String s = String.format("\nif (arg%d == null) throw new IllegalArgumentException(\"url == null\");",
urlTypeIndex);
code.append(s); s = String.format("\n%s url = arg%d;", URL.class.getName(), urlTypeIndex);
code.append(s);
}
// 参数没有URL类型
else {
String attribMethod = null; // 找到参数的URL属性
LBL_PTS:
for (int i = ; i < pts.length; ++i) {
Method[] ms = pts[i].getMethods();
for (Method m : ms) {
String name = m.getName();
if ((name.startsWith("get") || name.length() > )
&& Modifier.isPublic(m.getModifiers())
&& !Modifier.isStatic(m.getModifiers())
&& m.getParameterTypes().length ==
&& m.getReturnType() == URL.class) {
urlTypeIndex = i;
attribMethod = name;
break LBL_PTS;
}
}
}
if (attribMethod == null) {
throw new IllegalStateException("fail to create adative class for interface " + type.getName()
+ ": not found url parameter or url attribute in parameters of method " + method.getName());
} // Null point check
String s = String.format("\nif (arg%d == null) throw new IllegalArgumentException(\"%s argument == null\");",
urlTypeIndex, pts[urlTypeIndex].getName());
code.append(s);
s = String.format("\nif (arg%d.%s() == null) throw new IllegalArgumentException(\"%s argument %s() == null\");",
urlTypeIndex, attribMethod, pts[urlTypeIndex].getName(), attribMethod);
code.append(s); s = String.format("%s url = arg%d.%s();", URL.class.getName(), urlTypeIndex, attribMethod);
code.append(s);
} String[] value = adaptiveAnnotation.value();
// 没有设置Key,则使用“扩展点接口名的点分隔 作为Key
if (value.length == ) {
char[] charArray = type.getSimpleName().toCharArray();
StringBuilder sb = new StringBuilder();
for (int i = ; i < charArray.length; i++) {
if (Character.isUpperCase(charArray[i])) {
if (i != ) {
sb.append(".");
}
sb.append(Character.toLowerCase(charArray[i]));
} else {
sb.append(charArray[i]);
}
}
value = new String[]{sb.toString()};
} boolean hasInvocation = false;
for (int i = ; i < pts.length; ++i) {
if (pts[i].getName().equals("com.alibaba.dubbo.rpc.Invocation")) {
// Null Point check
String s = String.format("\nif (arg%d == null) throw new IllegalArgumentException(\"invocation == null\");", i);
code.append(s);
s = String.format("\nString methodName = arg%d.getMethodName();", i);
code.append(s);
hasInvocation = true;
break;
}
} String defaultExtName = cachedDefaultName;
String getNameCode = null;
for (int i = value.length - ; i >= ; --i) {
if (i == value.length - ) {
if (null != defaultExtName) {
if (!"protocol".equals(value[i]))
if (hasInvocation)
getNameCode = String.format("url.getMethodParameter(methodName, \"%s\", \"%s\")", value[i], defaultExtName);
else
getNameCode = String.format("url.getParameter(\"%s\", \"%s\")", value[i], defaultExtName);
else
getNameCode = String.format("( url.getProtocol() == null ? \"%s\" : url.getProtocol() )", defaultExtName);
} else {
if (!"protocol".equals(value[i]))
if (hasInvocation)
getNameCode = String.format("url.getMethodParameter(methodName, \"%s\", \"%s\")", value[i], defaultExtName);
else
getNameCode = String.format("url.getParameter(\"%s\")", value[i]);
else
getNameCode = "url.getProtocol()";
}
} else {
if (!"protocol".equals(value[i]))
if (hasInvocation)
getNameCode = String.format("url.getMethodParameter(methodName, \"%s\", \"%s\")", value[i], defaultExtName);
else
getNameCode = String.format("url.getParameter(\"%s\", %s)", value[i], getNameCode);
else
getNameCode = String.format("url.getProtocol() == null ? (%s) : url.getProtocol()", getNameCode);
}
}
code.append("\nString extName = ").append(getNameCode).append(";");
// check extName == null?
String s = String.format("\nif(extName == null) " +
"throw new IllegalStateException(\"Fail to get extension(%s) name from url(\" + url.toString() + \") use keys(%s)\");",
type.getName(), Arrays.toString(value));
code.append(s); s = String.format("\n%s extension = (%<s)%s.getExtensionLoader(%s.class).getExtension(extName);",
type.getName(), ExtensionLoader.class.getSimpleName(), type.getName());
code.append(s); // return statement
if (!rt.equals(void.class)) {
code.append("\nreturn ");
} s = String.format("extension.%s(", method.getName());
code.append(s);
for (int i = ; i < pts.length; i++) {
if (i != )
code.append(", ");
code.append("arg").append(i);
}
code.append(");");
} codeBuidler.append("\npublic " + rt.getCanonicalName() + " " + method.getName() + "(");
for (int i = ; i < pts.length; i++) {
if (i > ) {
codeBuidler.append(", ");
}
codeBuidler.append(pts[i].getCanonicalName());
codeBuidler.append(" ");
codeBuidler.append("arg" + i);
}
codeBuidler.append(")");
if (ets.length > ) {
codeBuidler.append(" throws ");
for (int i = ; i < ets.length; i++) {
if (i > ) {
codeBuidler.append(", ");
}
codeBuidler.append(ets[i].getCanonicalName());
}
}
codeBuidler.append(" {");
codeBuidler.append(code.toString());
codeBuidler.append("\n}");
}
codeBuidler.append("\n}");
if (logger.isDebugEnabled()) {
logger.debug(codeBuidler.toString());
}
return codeBuidler.toString();
}

5. 通过createAdaptiveExtensionClassCode()生成的java源代码,要被java虚拟机加载执行必须得编译成字节码,dubbo提供两种方式去执行代码的编译1)利用JDK工具类编译2)利用javassit根据源代码生成字节码。

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

在此顺便介绍下@Adaptive注解打在实现类上跟打在接口方法上的区别:

  如果有打在接口方法上,调ExtensionLoader.getAdaptiveExtension()获取设配类,会先通过前面的过程生成java的源代码,在通过编译器编译成class加载。但是Compiler的实现策略选择也是通过ExtensionLoader.getAdaptiveExtension(),如果也通过编译器编译成class文件那岂不是要死循环下去了吗?

  ExtensionLoader.getAdaptiveExtension(),对于有实现类上去打了注解@Adaptive的dubbo spi扩展机制,它获取设配类不在通过前面过程生成设配类java源代码,而是在读取扩展文件的时候遇到实现类打了注解@Adaptive就把这个类作为设配类缓存在ExtensionLoader中,调用是直接返回

6.  自动Wrap上扩展点的Wrap类

  Dubbo是如何自动的给扩展点wrap上装饰对象的呢?

  在ExtensionLoader.loadFile加载扩展点配置文件的时候对扩展点类有接口类型为参数的构造器就是包转对象,缓存到集合中去。

  在调ExtensionLoader的createExtension(name)根据扩展点key创建扩展的时候, 先实例化扩展点的实现, 在判断时候有此扩展时候有包装类缓存,有的话利用包转器增强这个扩展点实现的功能。具体实现如下:

  

private T createExtension(String name) {
Class<?> clazz = getExtensionClasses().get(name);
if (clazz == null) {
throw findException(name);
}
try {
T instance = (T) EXTENSION_INSTANCES.get(clazz);
if (instance == null) {
EXTENSION_INSTANCES.putIfAbsent(clazz, (T) clazz.newInstance());
instance = (T) EXTENSION_INSTANCES.get(clazz);
}
injectExtension(instance);
Set<Class<?>> wrapperClasses = cachedWrapperClasses;
if (wrapperClasses != null && wrapperClasses.size() > ) {
for (Class<?> wrapperClass : wrapperClasses) {
instance = injectExtension((T) wrapperClass.getConstructor(type).newInstance(instance));
}
}
return instance;
} catch (Throwable t) {
throw new IllegalStateException("Extension instance(name: " + name + ", class: " +
type + ") could not be instantiated: " + t.getMessage(), t);
}
}

7.ioc是spring的三大基础功能之一, dubbo的ExtensionLoader在加载扩展实现的时候内部实现了个简单的ioc机制来实现对扩展实现所依赖的参数的注入,         dubbo对扩展实现中公有的set方法且入参个数为一个的方法,尝试从对象工厂ObjectFactory获取值注入到扩展点实现中去。

private T injectExtension(T instance) {
try {
if (objectFactory != null) {
for (Method method : instance.getClass().getMethods()) {
if (method.getName().startsWith("set")
&& method.getParameterTypes().length ==
&& Modifier.isPublic(method.getModifiers())) {
Class<?> pt = method.getParameterTypes()[];
try {
String property = method.getName().length() > ? method.getName().substring(, ).toLowerCase() + method.getName().substring() : "";
Object object = objectFactory.getExtension(pt, property);
if (object != null) {
method.invoke(instance, object);
}
} catch (Exception e) {
logger.error("fail to inject via method " + method.getName()
+ " of interface " + type.getName() + ": " + e.getMessage(), e);
}
}
}
}
} catch (Exception e) {
logger.error(e.getMessage(), e);
}
return instance;
}

  下面我们来看看ObjectFactory是如何根据类型和名字来获取对象的,ObjectFactory也是基于dubbo的spi扩展机制的。它跟Compiler接口一样设配类注解@Adaptive是打在类AdaptiveExtensionFactory上的不是通过javassist编译生成的。

  AdaptiveExtensionFactory持有所有ExtensionFactory对象的集合,dubbo内部默认实现的对象工厂是SpiExtensionFactory和SpringExtensionFactory,他们经过TreeMap排好序的查找顺序是优先先从SpiExtensionFactory获取,如果返回空在从SpringExtensionFactory获取。

  SpiExtensionFactory工厂获取要被注入的对象,就是要获取dubbo spi扩展的实现,所以传入的参数类型必须是接口类型并且接口上打上了@SPI注解,返回的是一个设配类对象

public <T> T getExtension(Class<T> type, String name) {
if (type.isInterface() && type.isAnnotationPresent(SPI.class)) {
ExtensionLoader<T> loader = ExtensionLoader.getExtensionLoader(type);
if (loader.getSupportedExtensions().size() > ) {
return loader.getAdaptiveExtension();
}
}
return null;
}

  SpringExtensionFactory,Dubbo利用spring的扩展机制跟spring做了很好的融合。在发布或者去引用一个服务的时候,会把spring的容器添加到SpringExtensionFactory工厂集合中去, 当SpiExtensionFactory没有获取到对象的时候会遍历SpringExtensionFactory中的spring容器来获取要注入的对象

public <T> T getExtension(Class<T> type, String name) {
for (ApplicationContext context : contexts) {
if (context.containsBean(name)) {
Object bean = context.getBean(name);
if (type.isInstance(bean)) {
return (T) bean;
}
}
}
return null;
}

  ExtensionLoader整体活动图如下:

  

Dubbo实现原理之基于SPI思想实现Dubbo内核的更多相关文章

  1. 2. Dubbo原理解析-Dubbo内核实现之基于SPI思想Dubbo内核实现(转)

    转载自  斩秋的专栏  http://blog.csdn.net/quhongwei_zhanqiu/article/details/41577159 SPI接口定义 定义了@SPI注解 public ...

  2. 【DUBBO】 Dubbo原理解析-Dubbo内核实现之基于SPI思想Dubbo内核实现

    转载:http://blog.csdn.net/quhongwei_zhanqiu/article/details/41577235 SPI接口定义 定义了@SPI注解 public @interfa ...

  3. dubbo实现原理之动态编译

    Dubbo为了实现基于spi思想的扩展特性,特别是能够灵活添加额外功能,对于扩展或则策略选择的设配类能够动态生成.对于一些需求已知的类如Protocal,它们的设配类代码dubbo可以直接的提供,但是 ...

  4. dubbo源码分析3——SPI机制中的ExtensionLoader类的objectFactory属性分析

    ExtensionLoader类是整个SPI的核心类,每个SPI都会对应一个ExtensionLoader类实例,这个类的构造方法如下: private ExtensionLoader(Class&l ...

  5. dubbo源码分析5——SPI机制_AdaptiveExtension的原理和作用

    private T createAdaptiveExtension() { try { return injectExtension((T) getAdaptiveExtensionClass().n ...

  6. dubbo 的 spi 思想是什么?

    面试题 dubbo 的 spi 思想是什么? 面试官心理分析 继续深入问呗,前面一些基础性的东西问完了,确定你应该都 ok,了解 dubbo 的一些基本东西,那么问个稍微难一点点的问题,就是 spi, ...

  7. 4.dubbo 的 spi 思想是什么?

    作者:中华石杉 面试题 dubbo 的 spi 思想是什么? 面试官心理分析 继续深入问呗,前面一些基础性的东西问完了,确定你应该都 ok,了解 dubbo 的一些基本东西,那么问个稍微难一点点的问题 ...

  8. dubbo源码分析之基于SPI的强大扩展

    https://blog.csdn.net/luoyang_java/article/details/86609045 Dubbo采用微内核+插件体系,使得设计优雅,扩展性强.那所谓的微内核+插件体系 ...

  9. 分布式的几件小事(五)dubbo的spi思想是什么

    1.什么是SPI机制 SPI 全称为 Service Provider Interface,是一种服务发现机制. SPI 的本质是将接口实现类的全限定名配置在文件中,并由服务加载器读取配置文件,加载实 ...

随机推荐

  1. kbmmw 5.06.00 beta 发布

    原生.高效.可扩展.跨平台通信库来了. we are happy to announce v5.06.00 BETA of our popular middleware for Delphi and  ...

  2. 特级教师总结的教育之33条(ZZ)

    一位班主任发给家长的一则短信:“无论成绩好坏,请想想:每个孩子都是种子,只不过每个人的花期不同.有的花,一开始就灿烂绽放:有的花,需要漫长的等待.不要看着别人怒放了,自己的那棵还没有动静就着急,相信是 ...

  3. 2019.01.14 bzoj2752: [HAOI2012]高速公路(线段树)

    传送门 线段树菜题. 题意简述:给一条nnn个点的链,链有边权,支持区间修改边权,查询在一段区间内随机选择不同的起点和终点路径的期望总边权和. 思路:考虑每条边的贡献. 考虑对于一段区间[l,r][l ...

  4. AttributeError: type object 'testClass' has no attribute 'testMothod'

    点击"Unittest for test_post_API.testClass"按钮,点击”Edit configuration...“,弹出对话框Run/Debug config ...

  5. 利用url传多个参数

    刚开始接触jsp,比较偏向于用button标签的onclick方法进行页面的跳转.但是关于页面跳转的各种问题真是叫人头大,以下记录,仅仅为自己以后查看. Qone 用url传参的时候遇到中文怎么办 编 ...

  6. BZOJ 4765 普通计算姬 (分块 + BIT)

    4765: 普通计算姬 Time Limit: 30 Sec  Memory Limit: 256 MBSubmit: 1547  Solved: 329[Submit][Status][Discus ...

  7. php,微信公众号,获取用户地理位置 定位 经纬度

    <?php //php插件下载地址: https://files.cnblogs.com/files/fan-bk/jssdk_php.rar //建立一个php文件 require_once ...

  8. poj 2299 Ultra-QuickSort(归并排序,树状数组,线段树)

    Description In this problem, you have to analyze a particular sorting algorithm. The algorithm proce ...

  9. MongoDB、redis、memcached

    mongodb和memcached不是一个范畴内的东西. mongodb     是文档型的非关系型数据库,其优势在于查询功能比较强大,能存储海量数据. memcached,redis mongodb ...

  10. C#-.Net Framework和.Net Compact Framework-摘

    .Net Compact Framework 一种独立于硬件的环境,用于在资源受限制的计算设备上运行程序.它继承了公共语言运行时的完整 .NET Framework 结构,支持 .NET Framew ...