Dubbo源码分析——扩展点机制
来源:互联网 发布:crossgear 知乎 编辑:程序博客网 时间:2024/06/07 08:37
1、概述
dubbo中定义了很多的扩展点,用SPI注解声明的接口就是一个扩展点。扩展点的每一个实现称为extension。dubbo使用ExtensionLoader来加载所有的Extension,ExtensionLoader的作用和spring容器有点像,spring容器负责创建和管理所有配置的bean,而ExtensionLoader负责加载所有的扩展点实现,不仅如此,ExtensionLoader也支持对扩展点进行依赖注入(后面会讲到),还支持扩展点的Wrapper机制。
我们都知道JDK也提供了SPI机制,那么为什么dubbo还要实现一套扩展点机制呢?相比JDK标准的SPI,dubbo的扩展点机制在以下几个方面进行了改进:
- JDK标准的SPI会一次性实例化扩展点所有实现,如果有扩展实现初始化很耗时,但如果没用上也加载,会很浪费资源,而dubbo扩展点机制实现了按需加载,只有在明确请求获取一个SPI的时候才创建相应的扩展点实现。
- JDK标准的SPI如果扩展点加载失败,连扩展点的名称都拿不到了。比如:ScriptEngine,通过getName();获取脚本类型的名称,但如果RubyScriptEngine因为所依赖的jruby.jar不存在,导致RubyScriptEngine类加载失败,这个失败原因被吃掉了,和ruby对应不起来,当用户执行ruby脚本时,会报不支持ruby,而不是真正失败的原因。dubbo扩展点对这种情况会给出明确的失败原因,让排查问题更容易。
- JDK标准的SPI扩展点是独立的,彼此之间没有建立起关联关系。而dubbo扩展点机制增加了对扩展点IoC和AOP的支持,一个扩展点可以直接setter注入其它扩展点。
2、扩展点配置方式
在jar包的META-INF/dubbo/,META-INF/dubbo/internal/和META-INF/services/,这三个目录下放置SPI接口全限定名作为为文件名的文件,文件的内容为:配置名=扩展实现类全限定名,多个扩展点实现用换行分隔。举个例子,Protocol扩展点的配置文件位于dubbo-rpc这个jar包的META-INF/dubbo/internal/目录下,文件名是com.alibaba.dubbo.rpc.Protocol,内容如下:
dubbo=com.alibaba.dubbo.rpc.protocol.dubbo.DubboProtocol
当然这个文件是在dubbo自己的jar包下的,我们当然可以自己写一个类实现com.alibaba.dubbo.rpc.Protocol,然后在自己的jar包的/和META-INF/services/或者META-INF/dubbo/目录下放置一个名为com.alibaba.dubbo.rpc.Protocol的文件,把实现类的名字作为文件内容,dubbo也会加载我们的这个扩展点实现。事实上dubbo会扫描所有classpath下的SPI配置文件,将相同文件名的内容进行合并,平等地对待预定义和外部提供的扩展点实现。
需要注意的是,ExtensionLoader会cache扩展点的实现,因此所有扩展点的实现类都必须是线程安全的,作为单例提供服务。
3、扩展点的自动包装
如果一个SPI接口有多个实现,这时候需要在每一个实现的前后都加一段逻辑(比如统计rt),该怎么办呢,要去改每一个实现类的代码吗?如果实现类第三方jar包中的怎么办?关于这点,dubbo作者早就想到了,他们提供了扩展点的Wrapper机制。wrapper其实也是一个扩展点实现,它和被wrapper的扩展点实现一样,都实现了相同的SPI接口,不同的是wrapper有一个拷贝构造函数。举个例子,dubbo-rpc-api这个jar包中配置了如下两个扩展点实现:
filter=com.alibaba.dubbo.rpc.protocol.ProtocolFilterWrapperlistener=com.alibaba.dubbo.rpc.protocol.ProtocolListenerWrapper
那么当获取名为dubbo的这个扩展点实现单例的时候,ExtensionLoader首先会创建DubboProtocol对象,然后用
ProtocolFilterWrapper和ProtocolListenerWrapper对其进行封装,返回封装之后的对象,实际进行调用的时候,调用顺序是这样的:
ProtocolListenerWrapper => ProtocolFilterWrapper => DubboProtocol
dubbo对扩展点实现进行封装的代码如下:
if (wrapperClasses != null && wrapperClasses.size() > 0) { for (Class<?> wrapperClass : wrapperClasses) { instance = injectExtension((T) wrapperClass.getConstructor(type).newInstance(instance)); }}return instance;
dubbo以这种形式实现了扩展点的代理和AOP机制。
4、扩展点的自动注入
前面说dubbo的ExtensionLoader有点类似spring容器,是一个专门管理扩展点的容器,那么依赖注入功能肯定是少不了的,不然如果不同的扩展点有相互依赖的,还得自己拿到一个个扩展点,去手动注入,太麻烦了。dubbo也替我们想到了这一点,它能够对扩展点实现进行依赖注入。其实现原理很简单,代码如下:
private T injectExtension(T instance) { try { if (objectFactory != null) {//ExtensionFactory自己不需要这个逻辑 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) : ""; //FIXME 通过SpiExtensionFactory获取adaptiveExtension add by jileng 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;}
一句话概括就是遍历set方法,获取参数的类型和名字,然后以此在容器中寻找这样的扩展点。
不过具体的寻找依赖的工作是由objectFactory成员完成的,它的声明如下
private final ExtensionFactory objectFactory;
ExtensionFactory也是一个扩展点:
@SPIpublic interface ExtensionFactory { /** * Get extension. * * @param type object type. * @param name object name. * @return object instance. */ <T> T getExtension(Class<T> type, String name);}
那ExtensionFactory是怎么寻找依赖的呢?其实ExtensionFactory有两个扩展点实现SpiExtensionFactory和SpringExtensionFactory。SpiExtensionFactory负责寻找名称是name类型是type的扩展点实现,而SpringExtensionFactory负责在spring容器中寻找名称为name的bean。而AdaptiveExtensionFactory是ExtensionFactory的Adaptive实现,它负责协调其它实现扩展点实现来完成工作。下面会介绍Adaptive扩展点实现的作用。
五、扩展点自适应
上文说了,SpiExtensionFactory会寻找扩展点实现作为依赖注入的对象,但是如果扩展点有多个实现,那么该选哪一个实现呢?换个方式提问,既然有多个扩展点实现,那么在引用扩展点的时候怎么决定用哪一个实现呢?如果写死的话,也就失去了扩展点提供的灵活性。dubbo使用Adaptive扩展点解决这个问题。
下面的代码是SpiExtensionFactory寻找依赖的逻辑:
public class SpiExtensionFactory implements ExtensionFactory { 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) { //返回Adaptive Extension return loader.getAdaptiveExtension(); } } return null; }}
可见ExtensionLoader注入的依赖扩展点是一个Adaptive实例。Adaptive实例也是一个扩展点实现,但是它本身不干活,它的作用是根据URL(Dubbo使用URL对象传递配置信息)动态地决定由哪个具体的扩展点实现来干活,然后把工作代理给真正干活的实现。Adaptive扩展点可以用@Adaptive注解来显式声明,每一个扩展点最多只能有一个显式声明的Adaptive扩展点。如果没有显式声明Adaptive扩展点怎么办呢?答案是动态生成一个!如下面的代码所示:
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);}
其中createAdaptiveExtensionClassCode方法是用来生成源代码的,实现代码比较复杂,但是逻辑却很简单,总结起来就是:为扩展点接口的每一个带有@Adaptive注解的方法生成一个方法体,方法体的逻辑是找到参数中类型是URL的对象或者参数中的URL成员,然后根据方法@Adaptive注解上声明的key,去url对象里面找对应的value,这个value就是真正干活的扩展点实现的名称,然后从ExtensionLoader中获取这个扩展点实现,把工作代理给它。下面是一个例子,便于理解:
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值,来决定代理到哪个实际扩展点。
六、总结
dubbo其实是一个微内核的架构,很多功能都是通过扩展点来实现的,业务方可以方便的添加自己想要的功能,比如打印rpc日志、上报rt监控等。我们自己在写代码尤其是框架性的代码时,可以借鉴下dubbo的思路。
- Dubbo源码分析——扩展点机制
- Dubbo源码分析——扩展点机制
- Dubbo——扩展点加载机制
- Dubbo——扩展点加载机制
- Dubbo源码分析系列-扩展机制的实现
- Dubbo源码分析 ---- 基于SPI的扩展实现机制
- 对于Dubbo的扩展点加载机制的一些想法
- Dubbo/Dubbox的服务暴露(二)-扩展点机制
- 深入dubbo之ExtensionLoader,灵活的扩展点加载机制
- dubbo源码解析(一): 扩展点加载(ExtensionLoader)
- dubbo扩展点
- Dubbo 源码学习笔记 —— SPI的机制体现
- dubbo extension扩展点 源代码
- dubbo源码分析-ExtensionLoader发现机制和Adaptive注解应用
- dubbo源码分析-dubboProtocol
- Dubbo源码分析1
- Dubbo源码分析1
- Dubbo源码分析2
- 反射使用全解析
- 使用IDEA和Mybatis实现CRUD(一)
- nodejs 通过ffmpeg,实现微信jsapi上传mp3录音
- git
- Socket编程 ——UDP 实验报告
- Dubbo源码分析——扩展点机制
- linux连接mysql命令
- PAT--1091. Acute Stroke (30)(三维bfs)
- 13. Error analysis: Look at dev set examples to evaluate ideas 错误分析:查看开发集样本来评估idea(《MACHINE
- 知识库--lifecycle interface (46)
- TCP/IP协议三次握手,四次挥手那些事
- [jQuery知识]jQuery之知识十一-Ajax初级
- 京东右边固定导航
- 单例设计模式