服务治理中间件 Dubbo 原理解析(Dubbo内核实现)读书笔记
2018-08-23 16:05
465 查看
[code]首先感谢一个人,斩秋,很幸运能看到他整理的《服务治理中间件Dubbo原理分析》一文,接下来尝试对这本书的阅读; 一.SPI(service provider interface)传统机制,斩秋典型案例举例如下: JDK 实现 spi 服务查找: ServiceLoader ServiceLoader 会遍历所有 jar 查找 META-INF/services/com.example.Spi 文件 public interface Spi { boolean isSupport(String name); String sayHello(); } 在A 厂商提供的 jar 包中的 META-INF/services/com.example.Spi 文件内容为: com.a.example.SpiAImpl #厂商 A 的 spi 实现全路径类名 public class SpiAImpl implements Spi { public boolean isSupport(String name) { return "SPIA".equalsIgnoreCase(name.trim()); } public String syaHello() { return “hello 我是厂商 A”; } } 在B 厂商提供的 jar 包中的 META-INF/services/com.example.Spi 文件内容为: com.b.example.SpiBImpl #厂商 B 的 spi 实现全路径类名 public class SpiBImpl implements Spi { public boolean isSupport(String name) {return"SPIB".equalsIgnoreCase(name.trim());} public String syaHello() { return “hello 我是厂商 B”; } } 一个接口多种实现,就如策略模式一样提供了策略的实现,但是没有提供策略的选择, 使用方可以根据 isSupport 方法根据业务传入厂商名来选择具体的厂商。 public class SpiFactory { //读取配置获取所有实现 private static ServiceLoader spiLoader =ServiceLoader.load(Spi.class); //根据名字选取对应实现 public static Spi getSpi(String name) { for (Spi spi : spiLoader) { if (spi.isSupport(name) ) { return spi; } } return null; } } 二:基于 SPI 思想 Dubbo 内核实现 1.dubbo中应用举例 public @interface SPI { String value() default ""; //指定默认的扩展点 } 会依次从这几个文件中读取扩展点 META-INF/dubbo/internal/ //dubbo 内部实现的各种扩展都放在了这个目录 META-INF/dubbo/;META-INF/services/ @SPI("dubbo") 接口上打上 SPI 注解,默认扩展点名字为 dubbo public interface Protocol { } dubbo 中 内 置 实 现 了 各 种 协 议 如 : DubboProtocol InjvmProtocol HessianProtocol WebServiceProtocol 等等 2.具体Dubbo中spi加载机制 先熟悉这个类 ExtensionLoader 类 1.ExtensionLoader.getExtensionLoader(Protocol.class)每个定义的 spi 的接口 都会构建一个ExtensionLoader实例,存储在ConcurrentMap<Class<?>, ExtensionLoader<?>> EXTENSION_LOADERS 这个map 对象中 2. loadExtensionClasses 读取扩展点中的实现类 a) 先读取 SPI 注解的 value 值,有值作为默认扩展实现的 key b) 依次读取路径的文件 META-INF/dubbo/internal/ com.alibaba.dubbo.rpc.Protocol META-INF/dubbo/ com.alibaba.dubbo.rpc.Protocol META-INF/services/ com.alibaba.dubbo.rpc.Protocol 3. loadFile 逐行读取 com.alibaba.dubbo.rpc.Protocol 文件中的内容,每行 内容以 key/value 形式存储的。 a) 判断类实现(如: DubboProtocol)上有木有打上@Adaptive 注解,如果 打上了注解,将此类作为 Protocol协议的设配类缓存起来,读取下一行; 否则适配类通过 javasisit 修改字节码生成,关于设配类功能作用后续介绍 b) 如果类实现没有打上@Adaptive, 判断实现类是否存在入参为接口的构 造器(就是 DubbboProtocol 类是否还有入参为 Protocol 的构造器),有 的话作为包装类缓存到此 ExtensionLoader 的 Set<Class<?>>集合中, 这个其实是个装饰模式 c) 如果即不是设配对象也不是 wrapped 的对象,那就是扩展点的具体实现 对象,查找实现类上有没有打上 @Activate 注解 ,有缓存到变量 cachedActivates 的 map 中将实现类缓存到 cachedClasses 中,以便于使用时获取 4. 获取或者创建设配对象 getAdaptiveExtension a) 如 果 cachedAdaptiveClass有值,说明有且仅有一个实现类打了 @Adaptive, 实例化这个对象返回 b) 如果 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()方法 三、下面给出 createAdaptiveExtensionClassCode()方法生成 javasist 用来生成 Protocol 适配类后的代码 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!"); } // 接口中 export 方法打上@Adaptive 注册 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"); //参数类中要有 URL 属性 if (arg0.getUrl() == null)throw new IllegalArgumentException("com.alibaba.dubbo.rpc.Invoker argument getUrl() == null"); //从入参获取统一数据模型 URL com.alibaba.dubbo.common.URL url = arg0.getUrl(); String extName = (url.getProtocol() == null ? "dubbo" : url.getProtocol()); //从统一数据模型 URL 获取协议,协议名就是 spi 扩展点实现类的 key if (extName == null) throw new IllegalStateException( "Fail to get extension(com.alibaba.dubbo.rpc.Protocol) name from url(" + url.toString() + ") use keys([protocol])"); //利用 dubbo 服务查找机制根据名称找到具体的扩展点实现 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); } // 接口中 refer 方法打上@Adaptive 注册 public com.alibaba.dubbo.rpc.Invoker refer(java.lang.Class arg0, com.alibaba.dubbo.common.URL arg1) throws { //统一数据模型 URL 不能为空 if (arg1 == null) throw new IllegalArgumentException("url == null"); com.alibaba.dubbo.common.URL url = arg1; //从统一数据模型 URL 获取协议,协议名就是 spi 扩展点实现类的 key String extName = (url.getProtocol() == null ? "dubbo" :url.getProtocol()); if (extName == null) throw new IllegalStateException("Failtogetextension(com.a libaba.dubbo.rpc.Protocol) name from url("+ url.toString() + ") use keys([protocol])"); //利用 dubbo 服务查找机制根据名称找到具体的扩展点实现 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); } } 5. 通过 createAdaptiveExtensionClassCode()生成如上的 java 源码代码,要 被 java 虚拟机加载执行必须得编译成字节码, dubbo 提供两种方式去执行代 码的编译 1)利用 JDK 工具类编译 2)利用 javassit 根据源代码生成字节码。 1) 生成 Adaptive 代码 code 2) 利用 dubbo 的 spi 扩展机制获取 compiler 的设配类 3) 编译生成的 adaptive 代码 在此顺便介绍下@Adaptive 注解打在实现类上跟打在接口方法上的区别 1) 如果有打在接口方法上,调 ExtensionLoader.getAdaptiveExtension() 获取设配类,会先通过前面的过程生成java的源代码,在通过编译器编 译成class加载 。但是Compiler的实现策略选择也是通过 ExtensionLoader.getAdaptiveExtension(),如果也通过编译器编译成 class 文件那岂不是要死循环下去了吗? 2) ExtensionLoader.getAdaptiveExtension(),对于有实现类上去打了注解 @Adaptive 的 dubbo spi 扩展机制,它获取设配类不在通过前面过程生成设配类 java 源代 码,而是在读取扩展文件的时候遇到实现类打了注解@Adaptive 就把这个类作为设配类缓存在 ExtensionLoader 中,调用是直接返回 6. 自动 Wrap 上扩展点的 Wrap 类 这是一种装饰模式的实现,在 jdk 的输入输出流实现中有很多这种设计,在 于增强扩展点功能。这里我们拿对于 Protocol 接口的扩展点实现作为实例讲 解:Protocol 继承关系 ProtocolFilterWrapper,ProtocolListenerWrapper 这个两个类是装饰对象用来增强其他扩展点实现的功能。 ProtocolFilterWrapper 功能主要是在 refer 引用远程服务的中透 明的设置一系列的过滤器链用来记录日志,处理超时,权限控制等等功能;ProtocolListenerWrapper 在 provider 的 exporter,unporter 服 务 和consumer 的 refer 服务, destory 调用时添加监听器, dubbo 提供了扩展但是没有默认实现哪些监听器。 Dubbo 是如何自动的给扩展点 wrap 上装饰对象的呢? 1) 在 ExtensionLoader.loadFile 加载扩展点配置文件的时候,对扩展点类有接口类型为参数的构造器就是包转对象,缓存到集合中去 2) 在调ExtensionLoader的createExtension(name)根据扩展点 key 创建扩展的时候,先实例化扩展点的实现, 在判断时候有此扩展时候有包装类缓存,有的话利用包转器增强这个扩展点实现的功能。 7. IOC大家所熟知的ioc是spring的三大基础功能之一,dubbo 的ExtensionLoader 在加载扩展实现的时候内部实现了个简单的 ioc 机制来实现对扩展实现所依赖的参数的注入, dubbo对扩展实现中公有的set方法且入参个数为一个的方法,尝试从对象工厂ObjectFactory获取值注入到扩展点实现中去 下面我们来看看 ObjectFactory 是如何根据类型和名字来获取对象的, ObjectFactory 也是基于dubbo的spi扩展机制的它跟Compiler接口一样设配类注解@Adaptive是打在类AdaptiveExtensionFactory 上的不是通过 javassist 编译生成的。AdaptiveExtensionFactory 持有所有 ExtensionFactory 对象的集合, dubbo内部默认实现的对象工厂是 SpiExtensionFactory 和SpringExtensionFactory,他们经过 TreeMap 排好序的查找顺序是优先先从SpiExtensionFactory 获取,如果返回空在从 SpringExtensionFactory 获取 1) SpiExtensionFactory 工厂获取要被注入的对象,就是要获取dubbo spi 扩展的实现,所以传入的参数类型必须是接口类型并且接口上打上了 @SPI 注解,返回的是一个设配类对象。 2) SpringExtensionFactory, Dubbo 利用 spring 的扩展机制跟 spring 做了 很好的融合。在发布或者去引用一个服务的时候,会把 spring 的容器添 加到 SpringExtensionFactory 工厂集合中去, 当 SpiExtensionFactory 没有获取到对象的时候会遍历 SpringExtensionFactory 中的 spring 容 器来获取要注入的对象 三: 动态编译 我们运行的 java 代码,一般都是编译之后的字节码。 Dubbo 为了实现基于 spi 思想的扩展特性,特别是能够灵活添加额外功能,对于扩展或者说是策略的选择 这个叫做控制类也好设配类也好的类要能够动态生成。当然对应已知需求如 Protocol, ProxyFactory 他们的策略选择的设配类代码 dubbo 直接提供也无妨, 但是 dubbo 作为一个高扩展性的框架,使得用户能够添加自己的需求,根据配置 动态生成自己的设配类代码,这样就需要在运行的时候去编译加载这个设配类的 代码。下面我们就是来了解下 Dubbo 的动态编译。 编译接口定义 @SPI("javassist") public interface Compiler { Class<?> compile(String code, ClassLoader classLoader); } SPI 注解表示如果没有配置,dubbo 默认选用 javassist 编译源代码 接口方法 compile 第一个入参 code,就是 java 的源代码 接口方法 compile 第二个入参 classLoader,按理是类加载器用来加载编译后的 字节码,其实没用到,都是根据当前线程或者调用方的 classLoader加载的 @Adaptive public class AdaptiveCompiler implements Compiler { private static volatile String DEFAULT_COMPILER;public Class<?> compile(String code, ClassLoader classLoader) { Compiler compiler; ExtensionLoader<Compiler> loader = ExtensionLoader.getExtensionLoader(Compiler.class); String name = DEFAULT_COMPILER; // copy reference if (name != null && name.length() > 0) { compiler = loader.getExtension(name); } else { compiler = loader.getDefaultExtension(); } return compiler.compile(code, classLoader); } } AdaptiveCompiler 是 Compiler 的设配类, 它有类注解@Adaptive 表示这个 Compile r 的设配类不是动态编译生成的。 AdaptiveCompiler 作用就是策略的选 择,根据条件选择何种编译策略来编译动态生成的源代码。 AbstractCompiler 为编译的抽象类,抽象出公用逻辑,这里它主要是利用正则 匹配出源代码中的包名和类名后先在 jvm 中 Class.forName 看下是否存在,如果 存在反回,不存在在执行编译与加载。 关于 JavassistCompiler 和 JdkCompiler 执行 doCompile 的过程都是利用 Javassit 和 Jdk 提供的相关 api 或者扩展接口实现的。 如果系统配置中没有给 AdaptiveCompiler 设置哪种 compiler, 那么获取默认 compiler, 默认策略根据打在接口 Compiler 上的@SPI 值为 javassist, 所以 dubbo 默认利用 javassist 生成 SPI 机制的设配用来根据统一数据模型 URL 中获 取协议来选择使用何种策略
阅读更多
相关文章推荐
- 服务治理中间件 Dubbo 原理解析(代理)读书笔记
- 服务治理中间件 Dubbo 原理解析(注册中心)读书笔记
- 服务治理中间件 Dubbo 原理解析(集群容错)读书笔记
- 【DUBBO】 Dubbo原理解析-Dubbo内核实现之基于SPI思想Dubbo内核实现
- 2. Dubbo原理解析-Dubbo内核实现之基于SPI思想Dubbo内核实现
- 3. Dubbo原理解析-Dubbo内核实现之动态编译
- 【DUBBO】Dubbo原理解析-Dubbo内核实现之SPI简单介绍
- 3. Dubbo原理解析-Dubbo内核实现之动态编译
- 1. Dubbo原理解析-Dubbo内核实现之SPI简单介绍
- Dubbo原理解析-Dubbo内核实现之基于SPI思想Dubbo内核实现
- Dubbo 原理解析-Dubbo 内核实现之 SPI 简单介绍
- 1. Dubbo原理解析-Dubbo内核实现之SPI简单介绍
- Dubbo原理解析-Dubbo内核实现之SPI简单介绍
- 1. Dubbo原理解析-Dubbo内核实现之SPI简单介绍
- Dubbo原理解析-Dubbo内核实现之基于SPI思想Dubbo内核实现
- 2. Dubbo原理解析-Dubbo内核实现之基于SPI思想Dubbo内核实现
- Dubbo原理解析-Dubbo内核实现之动态编译
- 12. Dubbo原理解析-注册中心之基于dubbo协议的简单注册中心实现
- 20. Dubbo原理解析-通信层之引用服务
- 分布式服务框架dubbo原理解析 转