博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
dubbo扩展的实现原理11
阅读量:6861 次
发布时间:2019-06-26

本文共 6026 字,大约阅读时间需要 20 分钟。

hot3.png

1       概要介绍及使用方法

1.1    Java SPI

关于 java spi 的介绍可以参见下面这个帖子

 

Dubbo的扩展点加载从JDK标准的SPI(Service Provider Interface)扩展点发现机制加强而来。

Dubbo改进了JDK标准的SPI的以下问题:

  • JDK标准的SPI会一次性实例化扩展点所有实现,如果有扩展实现初始化很耗时,但如果没用上也加载,会很浪费资源
  • 如果扩展点加载失败,连扩展点的名称都拿不到了。比如:JDK标准的ScriptEngine,通过getName();获取脚本类型的名称, 但如果RubyScriptEngine因为所依赖的jruby.jar不存在,导致RubyScriptEngine类加载失败,这个失败原因被吃掉 了,和ruby对应不起来,当用户执行ruby脚本时,会报不支持ruby,而不是真正失败的原因
  • 增加了对扩展点IoCAOP的支持,一个扩展点可以直接setter注入其它扩展点

 

1.2    扩展使用方法:

在扩展类的jar包内,放置扩展点配置文件:META-INF/dubbo/接口全限定名,内容为:配置名=扩展实现类全限定名,多个实现类用换行符分隔。

示例:

在协议的实现jar包内放置文本文件:META-INF/dubbo/com.alibaba.dubbo.rpc.Protocol,内容为:

xxx=com.alibaba.xxx.XxxProtocol

实现类内容:

package com.alibaba.xxx;import com.alibaba.dubbo.rpc.Protocol;public class XxxProtocol implemenets Protocol {    // ...}

 

加载扩展实现, dubbo中都是通过ExtensionLoader实现的, 代码如:

ExtensionLoader.getExtensionLoader(Container.class).getExtension("registry");

2       ExtensionLoader代码分析

我们以方法getExtensionLoader作为入口来分析加载的实现:

 public static
ExtensionLoader
getExtensionLoader(Class
type){ if(type==null) throw new IllegalArgumentException("Extensiontype == null"); if(!type.isInterface()){ throw new IllegalArgumentException("Extension type(" + type + ") is not interface!"); } if(!withExtensionAnnotation(type)){ thrownewIllegalArgumentException("Extensiontype("+type+ ") is not extension, because WITHOUT@" + SPI.class.getSimpleName() + " Annotation!"); } ExtensionLoader
loader=(ExtensionLoader
)EXTENSION_LOADERS.get(type); if(loader==null){ EXTENSION_LOADERS.putIfAbsent(type,newExtensionLoader
(type)); loader=(ExtensionLoader
)EXTENSION_LOADERS.get(type); } return loader;}

 

 

 

此方法根据扩展类型, 得到一个扩展加载器(ExtensionLoader)

1, 通过方法开始部分的几个校验可以知道:

·         扩展类泛型类型不能为空

·         必须是一个接口.

·         此接口必须打上@SPI注解

2, 先从EXTENSION_LOADERS缓存里取, 取不到会实例化一个ExtensionLoader, 并且缓存起来;

ExtensionLoader的构造器中, 会通过AdaptiveExtension方式得到一个ExtensionFactory; AdaptiveExtensionExtensionFactory后面会再讲到.

3,得到ExtensionLoader之后, 再来看看getExtension(String name)方法

逻辑比较简单,

·         如果name 传一个 "true", 表示取默认扩展; getDefaultExtension(), 实现逻辑见4.

·         先从缓存cachedInstances里取, 取不到通过createExtension方法创建, 完了再放缓存.

 

4, 获取默认扩展的实现逻辑.getDefaultExtension

5, createExtension方法:

 1) 通过getExtensionClasses方法, 取得所有的扩展类缓存, 并从中取得name对应的class. getExtensionClasses实现逻辑见6.

·         根据类型, EXTENSION_INSTANCES获得缓存的实例.

·         如果不存在, 则通过class.newInstance创建实例, 并缓存.

·         通过injectExtension方法, 给这个实例注入各种属性.injectExtension实现逻辑, 详见7.

·         如果待扩展的T类型,Wrapper包装器类(构造函数有T入参),实例化,并且injectExtension:

Set
> wrapperClasses = cachedWrapperClasses; if(wrapperClasses != null && wrapperClasses.size() > 0){ for(Class
wrapperClass : wrapperClasses){ instance = injectExtension((T)wrapperClass.getConstructor(type).newInstance(instance)); }}return instance;

 

可以到看最后返回的是最后一个包装器类;之前的包装器类以及最开始最纯粹的扩展类实例, 只是调用了一下构造器方法, 然后传递给后面的包装器类; 这样就能在各级包装器类中添加各种扩展属性级扩展方法(装饰器模式).

 

包装类在loadFile加载扩展类的时候, 加载到cachedWrapperClasses缓存的.(loadFile实现逻辑详见8)

6.getExtensionClasses 获取所有的扩展类, 实现逻辑

    1) cachedClasses缓存里取, 如果不存在, 通过loadExtensionClasses方法加载.

    2) loadExtensionClasses实现逻辑:

        a.通过getExtensionLoader(Class<T>type)初始传进来的type类型上的SPI注解的value, 解析得到一个cachedDefaultName(这个就是默认扩展了.

        b.通过loadFile方法(实现逻辑见8), 加载DUBBO_INTERNAL_DIRECTORY,DUBBO_DIRECTORY,SERVICES_DIRECTORY三个目录下的扩展类汇总到extensionClasses缓存, 最后一起返回.

7.injectExtension方法,给实例注入属性的实现逻辑.

通过反射, 找到只有一个入参的set public方法, 得到参数类型.

通过ExtensionFactory得到这个属性的扩展实例, 如果存在的话, 就注入.

8, loadFile 通过配置文件加载扩展类的实现逻辑:

1)获得指定扩展配置文件名, :META-INF/dubbo/internal/com.alibaba.dubbo.common.compiler.Compiler

2)通过类加载器得到配置文件资源Url, 在类路径下可能会找到多个.

3)遍历, 读取配置文件中的每一行, 并解析, 配置行形如:javassist=com.alibaba.dubbo.common.compiler.support.JavassistCompiler

4)通过class.forName将等号后面的类, 转为类类型.

5)校验:

a.必须是T接口的实现类 type.isAssignableFrom(clazz)

b. 如果这个类有@Adaptive注解. 并且缓存到cachedAdaptiveClass, 如果cachedAdaptiveClass已经有了, 但不是当前解析得到的class, 说明该接口有多个@Adaptive注解扩展类, 报错.关于@Adaptive后面会再分析.

c.如果不是@Adaptive扩展类, 尝试获得该类带有该类类型为入参的构造函数

clazz.getConstructor(type);

存在, 则认为是包装器类, 加入到包装器缓存.

d.如果不是包装器类,

如果配置行, 仅有类名, 没有=号及等号左边的内容, 看看这个类上有没有@Extension注解, 并且注解的值与类名

如果还是没有如果扩展类的类名是接口类类名结尾.

clazz.getSimpleName().length()>type.getSimpleName().length()

&&clazz.getSimpleName().endsWith(type.getSimpleName()

那么 扩展名就是去掉接口名之后, 前半部分.

否则报错.

这个扩展名, 允许逗号隔开, 配置多

从扩展类上获取@Activate注解, 如果有, 存入cachedActivates缓存, 如果扩展名有多个, 只以第一个作为缓存key.

遍历每一个扩展名, 放入缓存, 名称扩展类对应扩展名.同时放入extensionClasses缓存,扩展名对应扩展类.

 

 

ExtensionLoader 还可加载Activate getActivateExtension()

 

Adaptive实例,直到扩展点方法执行时才决定调用是一个扩展点实现。

 

扩展点方法调用会有URL参数(或是参数有URL成员)

这样依赖的扩展点也可以从URL拿到配置信息,所有的扩展点自己定好配置的Key后,配置信息从URL上从最外层传入。

 

Adaptive实例的逻辑是固定,指定提取的URLKey,即可以代理真正的实现类上,可以动态生成。

 

DubboExtensionLoader的扩展点类开对应的Adaptive实现是在加载扩展点里动态生成。指定提取的URLKey通过@Adaptive注解在接口方法上提供。

下面是DubboTransporter扩展点的代码:

public interface Transporter {    @Adaptive({"server", "transport"})    Server bind(URL url, ChannelHandler handler) throws RemotingException;    @Adaptive({"client", "transport"})    Client connect(URL url, ChannelHandler handler) throws RemotingException;}

 

对于bind方法表示,Adaptive实现先查找"server"key,如果该Key没有值则找"transport"key值,来决定代理到哪个实际扩展点。

 

调用扩展如下面的代码:

Protocol refprotocol= ExtensionLoader.getExtensionLoader(Protocol.class).getAdaptiveExtension();

 

下面是getAdaptiveExtension()方法的实现逻辑:

 

1, cachedAdaptiveInstance缓存获取Adaptive类实例, 如果不存在, 调用createAdaptiveExtension()方法创建, 并缓存

2, createAdaptiveExtension方法, 通过getAdaptiveExtensionClass()得到Adaptive类并实例化, 并且通过injectExtension方法注入各属性.

 

3, getAdaptiveExtensionClass()实现逻辑: 从缓存cachedAdaptiveClass中获取, 取不到调用createAdaptiveExtensionClass方法创建

 

4, 通过createAdaptiveExtensionClassCode方法生成@Adaptive类字节码, 并且通过Compiler类编译得到, Compiler也是通过ExtensionLoader getAdaptiveExtension得到的.

这里不会递归调用栈溢出吗?

由于在getExtensionClasses()中已经找了cachedAdaptiveClass(8.5.b), 所以如果cachedAdaptiveClass仍然为空, 会去createAdaptiveExtensionClass() , 这样就会调用堆栈溢出. 这样就不难理解为啥8.5.b, 如果找到多个@Adaptive注解的类会报异常.

 

Compiler有一个实现类加了@Adaptive注解, 所以不会再去createAdaptiveExtensionClass.

 

AdaptiveCompiler

 

@Adaptive

public class AdaptiveCompilerimplementsCompiler

 

转载于:https://my.oschina.net/u/661116/blog/702458

你可能感兴趣的文章
我的友情链接
查看>>
我的友情链接
查看>>
我的友情链接
查看>>
linux笔记--磁盘基础管理
查看>>
KVM系列笔记(2)
查看>>
PHP中一个错误的一生
查看>>
『C#基础』多线程笔记「一」基本说明
查看>>
perl对于encode的用法
查看>>
StrongOD快捷键说明及其例子
查看>>
我的友情链接
查看>>
CVSACL 账号权限发生冲突时的权限判断方法
查看>>
忘记 ROOT 密码怎么办?
查看>>
android单元测试框架中的类
查看>>
C#遍历动态对象属性
查看>>
Mathematica中的尾递归优化
查看>>
Nginx+fastcgi+c语言+jQuery等技术实现设备端web登录
查看>>
QT学习资料
查看>>
Service Mesh:什么是Sidecar模式
查看>>
关于如何将安全意识带入企业的思考
查看>>
反射 注解的解析
查看>>