小程序框架kbone与Hybird混合开发
650
2022-09-01
Dubbo 源码分析之 SPI 详解
前言
不得不说 Dubbo的自定义 spi 减轻了扩展者的负担,但减轻负担的代价是使用大量晦涩难懂的代码,调用层次深,使阅读者步步维艰。为了避免后来者再像我当初那样看源码的吃力,特意把我的阅读见解分享下来,希望大家一起学习。
我使用的是 maven 仓库最新的 dubbo2.6.5 的源码,在社区看到要 2.7 版本就要出了。为了更方便的学习,我们可以等这个版本掌握差不多了再看更高的版本。
SPI
究竟什么是 SPI 呢,SPI 的英文名为 Service Provider Interface 顾名思义服务提供者接口,是面向开发者的。使用 SPI 我们可以把接口实现的全限定名放在文件中,通常在 META-INF/services 目录下,文件名为接口名,文件内容为接口的实现类。由服务器加载读取配置文件,加载实现类,这样在运行时动态为接口替换实现类。正因此特性,我们可以很容易的通过 SPI 机制为我们的程序提供拓展功能。
其实 JDK 自带了 SPI 功能。
JDK SPI
接口及其实现类
public interface Echo { void echo();}
public class DubboEcho implements Echo { @Override public void echo() { System.out.println("I am dubbo"); }}
public class JdkEcho implements Echo { @Override public void echo() { System.out.println("I am jdk"); }}
资源文件目录为:META-INF/services/接口全限定名 比如我的:META-INF/services/com.dfire.spi.Echo
com.dfire.spi.DubboEchocom.dfire.spi.JdkEcho
测试类:
public class App { public static void main(String[] args) { ServiceLoader
执行后输出结果为
I am dubboI am jdk
目录结构如下
完成这个例子,大家就能简单了解了 JDK SPI 。如果想要新增接口只需写个实现类配置在文件中即可。那么 Dubbo 为什么没有使用JDK SPI而使用了自定义SPI呢?
首先说我们能看到的缺点
一次性实例化所有的扩展点实现,即使该实现类没有用上,浪费资源
看不到的缺点
如果扩展点加载失败,连扩展点的名称都拿不到了。比如:JDK 标准的ScriptEngine ,通过getName 获取脚本类型的名称,但如果RubyScriptEngine 因为所依赖的jruby.jar 不存在,导致RubyScriptEngine 类加载失败,这个失败原因被吃掉了,和ruby 对应不起来,当用户执行ruby 脚本时,会报不支持ruby,而不是真正失败的原因。
Dubbo SPI
Dubbo SPI除了修复 JDK SPI 的缺点外还增加了AOP和IOC功能。
在提供者的入口类 ServiceConfig 中有这样一行代码
private static final Protocol protocol = ExtensionLoader.getExtensionLoader(Protocol.class).getAdaptiveExtension();
其中 ExtensionLoader 类即是 Dubbo SPI 的实现类。 我们就从这行代码逐步分析。
public static
在前两行首先是进行判空和该类是否为接口继续从缓存中根据接口类型来获得ExtensionLoader 对象,如果为空就创建进入ExtensionLoader 构造方法
private ExtensionLoader(Class> type) { this.type = type; objectFactory = (type == ExtensionFactory.class ? null : ExtensionLoader.getExtensionLoader(ExtensionFactory.class).getAdaptiveExtension()); }
哇,好复杂。 简单分析下 首先判断 type 是否为 ExtensionFactory类,如果是那么 objectFactory 就为null。如果不是就获得 ExtensionFactory 的自适应扩展类。ExtensionLoader.getExtensionLoader(T.class).getAdaptiveExtension() 这行代码其实就是我们刚刚进入分析的入口。下面继续分析 getAdaptiveExtension 方法。
获得所有的扩展类
public T getAdaptiveExtension() { //检测缓存中是否存在自适应扩展类实现 Object instance = cachedAdaptiveInstance.get(); //double check 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; }
此方法,主要是为了获得自适应扩展类。首先从缓存的 cachedAdaptiveInstance 中检测是否已经存在。
private final Holder
cachedAdaptiveInstance 是 Holder 类创建的一个对象查看一下 Holder 类
public class Holder
发现 Holder 类中持有一个 value 对象并且以 volatile 关键字修饰,保证其可见性,指令有序性。
继续往下分析,常见的 double check ,如果不存在就进入 createAdaptiveExtension 方法进行创建。
private T createAdaptiveExtension() { try { return injectExtension((T) getAdaptiveExtensionClass().newInstance()); } catch (Exception e) { throw new IllegalStateException("Can not create adaptive extenstion " + type + ", cause: " + e.getMessage(), e); } }
createAdaptiveExtension 方法主要做了:调用getAdaptiveExtensionClass 方法获得自适应扩展类,通过反射 newInstance 新建一个对象,然后使用 injectExtension 方法进入属性注入操作。
根据调用顺序,首先进入 getAdaptiveExtensionClass 方法
private Class> getAdaptiveExtensionClass() { //加载配置文件的所有的类 getExtensionClasses(); //如果cachedAdaptiveClass已经加载过,直接返回 if (cachedAdaptiveClass != null) { return cachedAdaptiveClass; } //无自适应扩展类,动态创建自适应扩展类 return cachedAdaptiveClass = createAdaptiveExtensionClass(); }
在 getAdaptiveExtensionClass 方法中主要做了两件事,首先调用 getExtensionClasses 方法,该方法会加载所有配置在文件中的类,其中如果有自适应扩展类,那么就会为 cachedAdaptiveClass 赋值。而 createAdaptiveExtensionClass 会在发现没有自定义的自适应扩展类时进行动态生成。
那么首先进入 getExtensionClasses 方法
private Map
同样是 double check ,首先根据缓存 map 判断是否已经加载过,如果未加载过那么进行第一次加载,并设置缓存。那么就进入加载扩展类的方法中 loadExtensionClasses
private Map
该方法大致内容是:
查看该接口类是否具有SPI 注解,有的话则获得注解的值,并赋值给cachedDefaultName 作为自适应扩展实现的默认值去指定的文件路径下解析该接口的实现类的配置信息
在看 loadFile 方法之前首先附上 Dubbo SPI 的一个资源文件 META-INF/dubbo/internal/com.alibaba.dubbo.rpc.Protocol,方便大家理解。
registry=com.alibaba.dubbo.registry.integration.RegistryProtocoldubbo=com.alibaba.dubbo.rpc.protocol.dubbo.DubboProtocolfilter=com.alibaba.dubbo.rpc.protocol.ProtocolFilterWrapperlistener=com.alibaba.dubbo.rpc.protocol.ProtocolListenerWrappermock=com.alibaba.dubbo.rpc.support.MockProtocolinjvm=com.alibaba.dubbo.rpc.protocol.injvm.InjvmProtocolrmi=com.alibaba.dubbo.rpc.protocol.rmi.RmiProtocolhessian=com.alibaba.dubbo.rpc.protocol.hessian.HessianProtocolcom.alibaba.dubbo.rpc.protocol.Java SPI 的区别,Dubbo 是使用 name=class 的 key-value 模式存储的实现类,至于这样有什么好处,我们继续进入 loadFile 方法分析
private void loadFile(Map
代码很长,需要大家仔细去看,具体的注释我已经写的很清楚。 总结一下这段代码的作用
解析在"META-INF/dubbo/internal/","META-INF/dubbo/" ,"META-INF/services/" 等目录下的SPI 资源文件发现实现类具有@Adaptive 注解,即自定义自适应扩展类,则为缓存cachedAdaptiveClass 赋值,很重要,别忘记我们为什么在执行到getAdaptiveExtensionClass 方法时进入getExtensionClasses 方法,就是为了得到自适应扩展类查看实现类是否具有一个参数为接口类的构造方法,有的话则把该实现类放入修饰类缓存中查看实现类是否具有@Activate 注解,存在的话放入缓存将解析SPI 资源文件 以class -> name 的方式 放入缓存map 的cachedNames 中
加载 SPI 资源这一部分算是结束了,那么我们继续回到 getAdaptiveExtensionClass 方法中。
private Class> getAdaptiveExtensionClass() { getExtensionClasses(); if (cachedAdaptiveClass != null) { return cachedAdaptiveClass; } return cachedAdaptiveClass = createAdaptiveExtensionClass(); }
我们通过上面分析知道 getExtensionClasses 方法会在存在 @Adaptive 注解的实现类时为 cachedAdaptiveClass 赋值。在这里如果发现 cachedAdaptiveClass != null 则直接返回,否则进入动态创建自适应扩展类方法 createAdaptiveExtensionClass。
这里才是我们理解 Dubbo SPI 关键,大家要耐心、仔细往下看。
private Class> createAdaptiveExtensionClass() { //动态生成自适应扩展类代码 String code = createAdaptiveExtensionClassCode(); //获得类加载器 ClassLoader classLoader = findClassLoader(); //获得 Compiler 自适应扩展类 com.alibaba.dubbo.common.compiler.Compiler compiler = ExtensionLoader.getExtensionLoader(com.alibaba.dubbo.common.compiler.Compiler.class).getAdaptiveExtension(); //编译代码生成 class return compiler.compile(code, classLoader); }
这个方法内大概内容就是动态生成自适应扩展类代码,使用 Compiler 编译该代码。
我们主要看的在 createAdaptiveExtensionClassCode 方法内,先多嘴一句,动态生成自适应扩展类代码需要保证接口内具有 @Adaptive 注解的方法
由于该方法内容较长,我决定一段一段分析,大家可以对照源码阅读。在下面文章中,如果存在 /* */ 注释则表示省略了一段代码,注释内容为省略代码的主要功能,大家可以注意一下。
[] methods = type.getMethods(); //判断方法是否具有 @Adaptive 注解 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!");
这部分主要是判断接口内是否存在 @Adaptive 注解的方法,如果不存在,那么直接抛异常。
//生成包名 codeBuidler.append("package " + type.getPackage().getName() + ";"); //导入ExtensionLoader codeBuidler.append("\nimport " + ExtensionLoader.class.getName() + ";"); //生成类名(比如: Protocol$Adpative) 并且 实现接口 该接口 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(512); //如果当前方法没有 @Adaptive 注解,则在实现类的该方法体中抛出异常 if (adaptiveAnnotation == null) { code.append("throw new UnsupportedOperationException(\"method ") .append(method.toString()).append(" of interface ") .append(type.getName()).append(" is not adaptive method!\");"); } else { //其它操作 }
这段代码主要是判断当前接口方法是否存在 @Adaptive 注解,如果不存在,则在实现方法中添加抛异常代码
for (Method method : methods) { /* 参数获取 */ if (adaptiveAnnotation == null) { /*不存在 @Adaptive 注解*/ } else { int urlTypeIndex = -1; //判断该方法的参数中是否存在 URL 类型的 有的话进行标记 for (int i = 0; i < pts.length; ++i) { if (pts[i].equals(URL.class)) { urlTypeIndex = i; break; } } // 有类型为URL的参数 if (urlTypeIndex != -1) { // 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 { //... } //... } // 其它 }
这里主要是检测方法的参数中是否具有 URL 类型的参数。 继续看参数没有 URL 类型的时候处理方式
for (Method method : methods) { /* 参数获取 */ if (adaptiveAnnotation == null) { /*不存在 @Adaptive 注解*/ } else { /*获得 URL 参数位置*/ if (urlTypeIndex != -1) { /* 参数有URL*/ } // 参数没有URL类型 else { String attribMethod = null; // 遍历所有参数 从参数类型的所有方法中 查看 get* 方法并且返回类型为 URL LBL_PTS: for (int i = 0; i < pts.length; ++i) { Method[] ms = pts[i].getMethods(); for (Method m : ms) { String name = m.getName(); if ((name.startsWith("get") || name.length() > 3) && Modifier.isPublic(m.getModifiers()) && !Modifier.isStatic(m.getModifiers()) && m.getParameterTypes().length == 0 && m.getReturnType() == URL.class) { //记录 URL 参数的位置 urlTypeIndex = i; //记录返回 URL 类型的方法名 attribMethod = name; break LBL_PTS; } } } //如果为空 则表示所有方法的参数类型中不具有 URL 参数 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()); } // 空指针检测 生成的代码如:if (arg0 == null) throw new IllegalArgumentException("com.alibaba.dubbo.rpc.Invoker argument == null"); String s = String.format("\nif (arg%d == null) throw new IllegalArgumentException(\"%s argument == null\");", urlTypeIndex, pts[urlTypeIndex].getName()); code.append(s); //url参数判空,生成代码如下:if (arg0.getUrl() == null) throw new IllegalArgumentException("com.alibaba.dubbo.rpc.Invoker argument getUrl() == null"); s = String.format("\nif (arg%d.%s() == null) throw new IllegalArgumentException(\"%s argument %s() == null\");", urlTypeIndex, attribMethod, pts[urlTypeIndex].getName(), attribMethod); code.append(s); //获得url参数,生成代码如下:com.alibaba.dubbo.common.URL url = arg0.getUrl(); s = String.format("%s url = arg%d.%s();",URL.class.getName(), urlTypeIndex, attribMethod); code.append(s); } //其它 } // 其它 }
这部分代码主要就是从接口的方法中遍历所有参数,查看参数是否具有 URL 属性。至于为什么 Dubbo 一直在查找URL 参数,是因为我们的所有信息在 Dubbo 中都是以 URL 为总线的,通过 URL 可以获得我们需要的信息。
继续往下分析
for (Method method : methods) { /* 参数获取 */ if (adaptiveAnnotation == null) { /*不存在 @Adaptive 注解*/ } else { /* 获得 URL 参数位置*/ if (urlTypeIndex != -1) { /* 参数有URL*/ } // 参数没有URL类型 else { /*从方法参数类型的方法中查找URL*/ } //获得方法上具有 @Adaptive 注解的值 String[] value = adaptiveAnnotation.value(); // 没有设置value,则使用“扩展点接口名的点分隔 作为value if(value.length == 0) { char[] charArray = type.getSimpleName().toCharArray(); StringBuilder sb = new StringBuilder(128); for (int i = 0; i < charArray.length; i++) { if(Character.isUpperCase(charArray[i])) { if(i != 0) { sb.append("."); } sb.append(Character.toLowerCase(charArray[i])); } else { sb.append(charArray[i]); } } value = new String[] {sb.toString()}; } //其它 } // 其它 }
这部分也很简单,从方法注解上获得注解的值,可能有多个。如果不存在的话,根据接口名获得,遇见大写改为小写如果不是第一个符号还需要加 . 符号。
for (Method method : methods) { /* 参数获取 */ if (adaptiveAnnotation == null) { /* 不存在 @Adaptive 注解 */ } else { //获得 URL 参数位置 if (urlTypeIndex != -1) { /* 参数有URL */ } // 参数没有URL类型 else { /*从方法参数类型的方法中查找URL*/ } /*获得 @Adaptive 注解的值*/ boolean hasInvocation = false; //判断是否具有 Invocation 类型的参数 for (int i = 0; i < pts.length; ++i) { if (pts[i].getName().equals("com.alibaba.dubbo.rpc.Invocation")) { // 空指针检测 生成的代码如:if (arg0 == null) throw new IllegalArgumentException("invocation == null");" String s = String.format("\nif (arg%d == null) throw new IllegalArgumentException(\"invocation == null\");", i); code.append(s); //获得方法名 生成的代码如:String methodName = arg0.getMethodName(); s = String.format("\nString methodName = arg%d.getMethodName();", i); code.append(s); hasInvocation = true; break; } } //cachedDefaultName 还记得上面我们会加载 SPI 资源文件时会设置该值吗?忘了可以回去看看 该值是从接口类的 @Adaptive 注解中获得的 String defaultExtName = cachedDefaultName; //tip:这段代码通过递归方式生成 仔细观察下面的 for 循环语句 String getNameCode = null; // 由于在方法上 @Adaptive 注解的值可能有多个,从后往前遍历 以最左为准,主要是为了兼容老版本扩展名参数。越[老]的扩展名参数越靠右,会被新的覆盖。默认扩展名为defaultExtName for (int i = value.length - 1; i >= 0; --i) { if(i == value.length - 1) { //如果默认扩展名不为空 if(null != defaultExtName) { //至于为什么特判 protocol,是因为 url 可以直接获得 if(!"protocol".equals(value[i])) if (hasInvocation) //存在 Invocation 参数 生成的代码如: url.getMethodParameter(methodName, "loadbalance", "random") getNameCode = String.format("url.getMethodParameter(methodName, \"%s\", \"%s\")", value[i], defaultExtName); else //生成代码如:url.getParameter("proxy", "javassist") getNameCode = String.format("url.getParameter(\"%s\", \"%s\")", value[i], defaultExtName); else //生成代码如:( url.getProtocol() == null ? "dubbo" : url.getProtocol() ) 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); //其它 } // 其它 }
代码有些复杂,建议大家 debug 查看。主要是为了获得 extName 的参数。
for (Method method : methods) { /* 参数获取 */ if (adaptiveAnnotation == null) { /* 不存在 @Adaptive 注解 */ } else { //获得 URL 参数位置 if (urlTypeIndex != -1) { /* 参数有URL */ } // 参数没有URL类型 else { /*从方法参数类型的方法中查找URL*/ } /*获得 @Adaptive 注解的值*/ /* 生成 extName 的代码*/ //根据 extName 从 SPI 中获得实例,具体代码如:com.alibaba.dubbo.rpc.Protocol extension = (com.alibaba.dubbo.rpc.Protocol)ExtensionLoader.getExtensionLoader(com.alibaba.dubbo.rpc.Protocol.class).getExtension(extName); s = String.format("\n%s extension = (%
这部分代码主要是为了生成返回值。
继续看最后一部分了
// public + 返回值全限定名 + 方法名 + ( codeBuidler.append("\npublic " + rt.getCanonicalName() + " " + method.getName() + "("); // 添加参数列表代码 for (int i = 0; i < pts.length; i ++) { if (i > 0) { codeBuidler.append(", "); } codeBuidler.append(pts[i].getCanonicalName()); codeBuidler.append(" "); codeBuidler.append("arg" + i); } codeBuidler.append(")"); // 添加异常抛出代码 if (ets.length > 0) { codeBuidler.append(" throws "); for (int i = 0; i < ets.length; i ++) { if (i > 0) { codeBuidler.append(", "); } codeBuidler.append(pts[i].getCanonicalName()); } } codeBuidler.append(" {"); codeBuidler.append(code.toString()); codeBuidler.append("\n}");
大致就到这里了,动态生成自适应扩展类大概就这么多,简单举个例子。 大家可以看com.alibaba.dubbo.rpc.Protocol接口,该 接口的实现类中不存在 @Adaptive 注解的实现类,所以在使用
Protocol protocol = ExtensionLoader.getExtensionLoader(Protocol.class).getAdaptiveExtension()
时会动态生成,还记得我们的生成的原则吧:
判断方法是否存在@Adaptive 注解– 不存在:自适应类的实现方法中抛异常– 存在:判断方法参数是否存在URL 类的参数
package com.alibaba.dubbo.rpc;import com.alibaba.dubbo.common.URL;import com.alibaba.dubbo.common.extension.Adaptive;import com.alibaba.dubbo.common.extension.SPI;@SPI("dubbo")public interface Protocol { int getDefaultPort(); @Adaptive
通过查看 Protocol 接口我们发现,该类的默认 SPI 扩展名为 dubbo,getDefaultPort 和 destroy 方法无 @Adaptive 注解。export 和 refer 具有 @Adaptive 注解。自动生成的Protocol自适应类如下
package com.alibaba.dubbo.rpc;import com.alibaba.dubbo.common.extension.ExtensionLoader;public class Protocol$Adpative implements com.alibaba.dubbo.rpc.Protocol { //无 @Adaptive 接口 直接抛异常 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!"); } //无 @Adaptive 接口 直接抛异常 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.Exporter export(com.alibaba.dubbo.rpc.Invoker arg0) throws com.alibaba.dubbo.rpc.Invoker { 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.Protocol.class).getExtension(extName); return extension.export(arg0); } public com.alibaba.dubbo.rpc.Invoker refer(java.lang.Class arg0, com.alibaba.dubbo.common.URL arg1) throws java.lang.Class { 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.Protocol.class).getExtension(extName); return extension.refer(arg0, arg1); }}
然后就要回到 createAdaptiveExtension 方法了
private T createAdaptiveExtension() { try { return injectExtension((T) getAdaptiveExtensionClass().newInstance()); } catch (Exception e) { throw new IllegalStateException("Can not create adaptive extenstion " + type + ", cause: " + e.getMessage(), e); } }
getAdaptiveExtensionClass().newInstance() 通过反射创建一个实例就不用说了,就剩下 injectExtension 方法了,这个方法中也就是 dubbo 的 IOC 了主要是为了给该实例注入值
private T injectExtension(T instance) { try { //判断objectFactory是否为null 通过构造方法我们可以知道主要是判断 type 是否等于ExtensionFactory.class if (objectFactory != null) { //遍历所有方法查看是否具有 set 方法并且参数只有一个 for (Method method : instance.getClass().getMethods()) { if (method.getName().startsWith("set") && method.getParameterTypes().length == 1 && Modifier.isPublic(method.getModifiers())) { Class> pt = method.getParameterTypes()[0]; try { //获得变量名 String property = method.getName().length() > 3 ? method.getName().substring(3, 4).toLowerCase() + method.getName().substring(4) : ""; //对象工厂新建值 这边可以继续看我就不往下分析了 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); }
写了很多,需要大家慢慢消化。其实我这里不可能把 SPI 的全部源码分析的,该类中还有一些其它方法,不过核心都是在这片文章里面。希望大家能够由点到面,逐个击破。
最后还想再说一句,有一点需要注意的是在 loadFile 时,如果发现一个类无 @Adaptive 注解并且该类中具有接口类的构造方法,那么则任务该类为包装类,则向 cachedWrapperClasses 变量中缓存可以包装的类。
比如 ProtocolFilterWrapper 和 ProtocolListenerWrapper 均可以包装 Protocol 的实现类。
public class ProtocolFilterWrapper implements Protocol { private final Protocol protocol; public ProtocolFilterWrapper(Protocol protocol){ if (protocol == null) { throw new IllegalArgumentException("protocol == null"); } this.protocol = protocol; }
public ProtocolListenerWrapper(Protocol protocol){ if (protocol == null) { throw new IllegalArgumentException("protocol == null"); } this.protocol = protocol; }
具体的包装实现是在ExtensionLoader类的createExtension方法中
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
版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。
发表评论
暂时没有评论,来抢沙发吧~