日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

Dubbo实现原理之基于SPI思想实现Dubbo内核

發布時間:2025/3/21 编程问答 16 豆豆
生活随笔 收集整理的這篇文章主要介紹了 Dubbo实现原理之基于SPI思想实现Dubbo内核 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

  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(512);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 = -1;for (int i = 0; i < pts.length; ++i) {if (pts[i].equals(URL.class)) {urlTypeIndex = i;break;}}// 有類型為URL的參數if (urlTypeIndex != -1) {// Null Point checkString 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 = 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) {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 checkString 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,則使用“擴展點接口名的點分隔 作為Keyif (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()};}boolean hasInvocation = false;for (int i = 0; i < pts.length; ++i) {if (pts[i].getName().equals("com.alibaba.dubbo.rpc.Invocation")) {// Null Point checkString 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 - 1; i >= 0; --i) {if (i == value.length - 1) {if (null != defaultExtName) {if (!"protocol".equals(value[i]))if (hasInvocation)getNameCode = String.format("url.getMethodParameter(methodName, \"%s\", \"%s\")", value[i], defaultExtName);elsegetNameCode = String.format("url.getParameter(\"%s\", \"%s\")", value[i], defaultExtName);elsegetNameCode = 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);elsegetNameCode = String.format("url.getParameter(\"%s\")", value[i]);elsegetNameCode = "url.getProtocol()";}} else {if (!"protocol".equals(value[i]))if (hasInvocation)getNameCode = String.format("url.getMethodParameter(methodName, \"%s\", \"%s\")", value[i], defaultExtName);elsegetNameCode = String.format("url.getParameter(\"%s\", %s)", value[i], getNameCode);elsegetNameCode = 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 statementif (!rt.equals(void.class)) {code.append("\nreturn ");}s = String.format("extension.%s(", method.getName());code.append(s);for (int i = 0; i < pts.length; i++) {if (i != 0)code.append(", ");code.append("arg").append(i);}code.append(");");}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(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() > 0) {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 == 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);}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() > 0) {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整體活動圖如下:

?

  

轉載于:https://www.cnblogs.com/senlinyang/p/8612883.html

總結

以上是生活随笔為你收集整理的Dubbo实现原理之基于SPI思想实现Dubbo内核的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。

主站蜘蛛池模板: freesex性hd公交车上 | 欧美福利在线观看 | 男人都懂的网站 | 国产成人精品网 | 都市乱淫| 欧美日本免费 | 精品国产一区二区三区久久 | 中文字幕 自拍 | 国内外免费激情视频 | 欧美日韩一区二区三区免费 | 亚洲最大网 | 久久精彩免费视频 | 美女露出粉嫩尿囗让男人桶 | 波多野结衣一本一道 | 日韩欧美一二三 | 黄色一级片免费播放 | 亚洲三级国产 | 麻豆传媒映画官网 | 亚洲一区二区伦理 | 国产粉嫩av | 特黄视频在线观看 | 日韩经典一区 | 久久久91精品国产一区二区三区 | 波多野结衣网站 | 清冷学长被爆c躁到高潮失禁 | 亚洲精品成人无码熟妇在线 | 一起操在线观看 | 老湿机69福利 | 免费三片在线观看网站v888 | 国产91综合一区在线观看 | 国产精品无码电影在线观看 | 污视频网站免费 | 欧美99久久精品乱码影视 | 日韩欧美不卡视频 | 日韩成人黄色片 | 日本亚洲高清 | 国产五十路 | 亚洲一级中文字幕 | 女人喂男人奶水做爰视频 | 亚洲av色一区二区三区精品 | 99久久久无码国产精品6 | 伊人操 | 亚洲国产成人精品91久久久 | 午夜黄色在线观看 | 色偷偷人人澡人人爽人人模 | 久久亚洲综合国产精品99麻豆精品福利 | 激情网站视频 | 伊久久| 亚洲人免费视频 | 伊人88| 无码日韩精品视频 | 六月丁香综合网 | 西西午夜 | 久久伊人一区二区 | 午夜tv影院| 精品视频一区二区 | 亚洲一区二区三区免费在线观看 | 亚洲日本成人在线观看 | 日韩欧美视频 | 污污的视频在线观看 | а√天堂资源在线 | jzjzz成人免费视频 | 亚洲av无码国产精品久久不卡 | 日韩欧美卡一卡二 | 91中文字幕| 国产一区日韩精品 | 国产乱码精品一区二三区蜜臂 | 综合伊人av | 精彩视频一区二区三区 | 一级特黄aa大片免费播放 | 日批在线播放 | 精品国产99 | 免费荫蒂添的好舒服视频 | 日韩欧美中文字幕一区 | 国产免费资源 | 人人草人人干 | 天天草天天操 | ww久久 | brazzers精品成人一区 | 99九九视频 | 国产91精品高潮白浆喷水 | 51成人| 91精品久久久久久粉嫩 | 欧洲精品久久久 | 曰本不卡视频 | 香蕉小视频 | 久久久久精 | 熟妇毛片 | 免费看又黄又无码的网站 | 亚洲av成人精品毛片 | 亚洲第一福利视频 | 成人在线观看www | 91爱爱.com | 精品动漫一区二区 | 冈本视频在线观看 | 亚洲播放器 | 精品夜夜澡人妻无码av | 91国产视频在线播放 | 91免费视频 |