探究Dubbo的拓展机制: 下
承接上篇, 本篇博文的主题就是认认真真捋一捋, 看一下 Dubbo是如何实现他的IOC / AOP / 以及Dubbo SPI这个拓展点的
总览:
本篇的话总体上分成两部分进行展开
- 第一点就是 Dubbo在启动过程中加载原生的配置文件中提供的被@SPI标记的实现类:
- 第二就是Dubbo加载程序员后续添加进去的被@SPI标注的接口和实现类, 进而探究 Dubbo的IOC / AOP / 以及Dubbo SPI这个拓展点机制
环境的初始化
入口程序
如下代码是追踪的起点:
我也是看了好多遍才勉强将这个过程整理明白一些, 但是根据以往的经验来说, 过一俩月之后我可能就会淡忘这个流程... 为了让自己一段时间后快速的回忆起来这个流程, 所以我要对自己说下面一段话
Dubbo的拓展点编码实现中, 会反反复复的出现下面这段代码
**
ExtensionLoader extensionLoader = ExtensionLoader.getExtensionLoader(XXX.class); extensionLoader.getExtension("XXX");
**先说这段代码在干什么? 其实上它就是在为 Dubbo原生的SPI接口, 或者是用户提供的SPI接口 结合SPI的配置文件中的配置, 找到这些SPI接口的实现类, 并且这个过程中穿插Dubbo的IOC已经AOP机制
**不得不服气, 这一段代码的实现, 因为这段代码设计不仅仅能加载Dubbo提供的原生的SPI接口, 也能加载使用 用户自定义的SPI , 详细的过程在下文中展开, 妙!!! **
明星类 ExtensionLoader.java
应该得, 隆重的介绍一下这个明星类 ExtensionLoader.java
从名字上看, ExtensionLoader , 见名知意, 拓展点的加载器, 那什么是Dubbo的拓展点呢? 拓展点就是Dubbo允许用户参与到Dubbo环境的初始化这个过程中来, 允许用户定制Dubbo行为, 诸如 Dubbo的 SPI / IOC / AOP (上一篇博文主要的学习内容就是Dubbo的拓展点的使用)
见名知意: ExtensionLoader 拓展点的加载器, 就是使用这个封装类, 我们可以加载Dubbo提供的拓展点, 说白了, 其实加载为SPI接口找到实现类, 以及完成这些实现类之间的 AOP增强 + IOC 依赖注入的过程
此外这个类很有必要看, 为啥呢? 第一点就是说它的设计很巧妙, 代码的抽象和复用能力都很好, 第二点就是说, 我们可以一睹大神们的风采, 如果 实现自己的SPI , 如何实现自己的IOC AOP
入口方法
下面就是入口程序中的第一个方法, getExtensionLoader(Class<T> type)
很简单, 就是根据类型找到对应的 ExtensionLoader, 待会Dubbo就会为我添加进去SPI接口生成这样的 ExtensionLoader : org.apache.dubbo.common.extension.ExtensionLoader[com.changwu.ioc.api.PersonInterface]
当然Dubbo也有自己原生的ExtensionLoader
从我的入口程序来看, 很显然, 我传递进来的 type = PersonInterface
, 方法执行的逻辑如下
- 对type参数进行校验
- 检查缓存中是否存在 PersonInterface的 ExtensionLoader
- 如果有的话, 返回这个现存的ExtensionLoader
- 如果不存在就创建一个新的
public static <T> ExtensionLoader<T> getExtensionLoader(Class<T> type) {
if (type == null) {
throw new IllegalArgumentException("Extension type == null");
}
if (!type.isInterface()) {
throw new IllegalArgumentException("Extension type (" + type + ") is not an interface!");
}
if (!withExtensionAnnotation(type)) {
throw new IllegalArgumentException("Extension type (" + type +
") is not an extension, because it is NOT annotated with @" + SPI.class.getSimpleName() + "!");
}
//todo 这里体现了缓存机制, EXTENSION_LOADERS 其实就是 CurrentHashMap
//todo EXTENSION_LOADERS 是 CurrentHashMap , 每一种interfaceType 都对应一个 ExtensionLoader , 但是这些 ExtensionLoader全部被维护在 这个EXTENSION_LOADER中
ExtensionLoader<T> loader = (ExtensionLoader<T>) EXTENSION_LOADERS.get(type);
if (loader == null) {
EXTENSION_LOADERS.putIfAbsent(type, new ExtensionLoader<T>(type));
loader = (ExtensionLoader<T>) EXTENSION_LOADERS.get(type);
}
return loader;
}
ExtensionLoader蹊跷的构造方法
那我们是第一次进来, 肯定是没有的, 因此我们看他是如何进行new ExtensionLoader<T>(type)
的, 所以跟进看一下它的构造方法
private ExtensionLoader(Class<?> type) {
this.type = type;
// 对于一个接口,比如PersonInterface接口,有两种实现类,一种就是我们自定义的实现类,比如Student,还有一种就是代理类,对于代理类,可以由我们自己实现,也可以让Dubbo帮我们实现,而代理类主要就是依赖注入时使用
// todo ExtensionFactory 是dubbo的拓展机制工厂, 它里面封装了 Dubbo的SPI拓展机制和Spring的拓展机制
// todo ExtensionLoader.getExtensionLoader(ExtensionFactory.class) ===> 获取 自适应的extension
// || || || || || ||
// todo ExtensionLoader.getExtensionLoader(PersonInterface.class) ===> 昌武, 你获取的是: extension("human")
// todo 你看这是不是挺清晰的
objectFactory = (type == ExtensionFactory.class ? null : ExtensionLoader.getExtensionLoader(ExtensionFactory.class).getAdaptiveExtension());
}
在上面的构造方法中, 就有蹊跷了, 逻辑如下
- 将type = PersonInterface 保存起来
- 获取 ExtensionFactory.class 类型的 ExtensionLoader
- 获取 ExtensionFactory.class 类型的 ExtensionLoader的自适应的 Extension
**我所说的有蹊跷的地方: ** 我们本来不是前来创建PersionInterface 的ExtensionLoader吗? 怎么先创建 ExtensionFactory的 ExtensionLoader呢?
(因为在创建ExtensionFactory的 ExtensionLoader的过程中会去加载Dubbo提供的其他的诸如SpiExtensionFactory这一类的实现, 这些默认的实现的作用就是辅助Dubbo再去解析用户提供的SPI实现体系)
下面看看这个 ExtensionFactory.class类
没错! 它被添加上了@SPI的注解, 说明和 我们的PersonInterface一样, 是DubboSPI
那好吧, 既然Dubbo想先完成它的实例化, 就往下看, 我在博文开头就不停的说, Dubbo设计的很好, 这里不就递归调用getExtensionLoader(type= ExtensionFactory.class)了吗? 不出意外的话, 再一次的 进去构造方法, 然后在这个三元判断表达式中发现了 type == ExtensionFactory.class ? null : ExtensionLoader.getE... 前半部分是满足条件的, 然后设置objectFactory=null, **完成 ExtensionFactory的构造, 然后执行getAdaptiveExtension()
**
获取自适应的Extension
这个 getAdaptiveExtension()
同样需要好好的看看, 见名知意, 返回一个自适应的 Extension, 说白了就是返回Dubbo通过字符拼接出来的Extension类
下面看看这个 getAdaptiveExtension()
源码如下:
@SuppressWarnings("unchecked")
public T getAdaptiveExtension() {
Object instance = cachedAdaptiveInstance.get();
if (instance == null) {
if (createAdaptiveInstanceError == null) {
// todo 为了线程安全 , 使用了双重同步锁
synchronized (cachedAdaptiveInstance) {
instance = cachedAdaptiveInstance.get();
if (instance == null) {
try {
// todo 跟进去
instance = createAdaptiveExtension();
cachedAdaptiveInstance.set(instance);
} catch (Throwable t) {
createAdaptiveInstanceError = t;
throw new IllegalStateException("Failed to create adaptive instance: " + t.toString(), t);
}
}
}
} else {
throw new IllegalStateException("Failed to create adaptive instance: " + createAdaptiveInstanceError.toString(), createAdaptiveInstanceError);
}
}
return (T) instance;
}
着重跟进 instance = createAdaptiveExtension();
方法, 源码如下: 主要逻辑如下:
- 获取出 AdaptiveExtensionClass
- 启动过程中, 第一次获取到的是 Dubbo提供的默认实现类, 叫 AdaptiveExtensionFactory
- 第二次获取到的是 Dubbo为用户提供的SPI接口动态生成的实现类
- 实例化AdaptiveExtensionClass
- 对实例化的AdaptiveExtensionClass 进行依赖注入的操作
加载SPI配置文件, 获取所有的ExtensionClass
ExtensionClass 可以直白的理解成 SPI 接口的实现类, 或者是wrapper类
上面的代码中想要获取一个 AdaptiveExtensionClass()
那么问题来了, 从哪里获取呢? 跟进getAdaptiveExtensionClass()
没错就在下面的
private Class<?> getAdaptiveExtensionClass() {
// todo 加载配置文件
getExtensionClasses();
// todo 在前一步加载配置文件时, 加载到了 AdaptiveExtensionFactory, 这里返回的就是 CachedAdaptiveClass
if (cachedAdaptiveClass != null) {
return cachedAdaptiveClass;
}
// 如果没有手动实现接口的代理类,那么Dubbo就会自动给你实现一个
return cachedAdaptiveClass = createAdaptiveExtensionClass();
}
往下跟进 getExtensionClasses();
下面的函数中维护着一个 cachedClasses
它是一个Map , key=String value= Class ; 说白了, 存放的就是从SPI配置文件中读取配置信息
// 实际上就是将配置中的 key=value 读取装在进map中
private Map<String, Class<?>> getExtensionClasses() {
// todo cachedClasses是 ExtensionLoader的属性: Holder<Map<String, Class<?>>> cachedClasses
// todo 用于存储提前约定好了存储在 类路径下的 METE-INF/services 以及dubbo原生提供的扩展点
// todo 同样是双重同步锁 + volatile 保证线程安全
Map<String, Class<?>> classes = cachedClasses.get();
if (classes == null) {
synchronized (cachedClasses) {
classes = cachedClasses.get();
if (classes == null) {
// todo 着重看这个函数
classes = loadExtensionClasses();
cachedClasses.set(classes);
}
}
}
return classes;
}
进行跟进loadExtensionClasses();
可以看到, Dubbo会按照约定读取下面几个配置文件中的配置信息, 下面我注释上的文件的全路径所对应的文件中会记录Dubbo原生的SPI的实现, 我们也能遵循这个规则提供自己的实现类
比如随便查看一个配置文件
// synchronized in getExtensionClasses
private Map<String, Class<?>> loadExtensionClasses() {
cacheDefaultExtensionName();
Map<String, Class<?>> extensionClasses = new HashMap<>();
// todo 跟进这个 loadDirectory() 方法, 看看
// todo META-INF/dubbo/internal/org.apache.dubbo.common.extension.ExtensionFactory
loadDirectory(extensionClasses, DUBBO_INTERNAL_DIRECTORY, type.getName());
// todo META-INF/dubbo/internal/com.alibaba.dubbo.common.extension.ExtensionFactory
loadDirectory(extensionClasses, DUBBO_INTERNAL_DIRECTORY, type.getName().replace("org.apache", "com.alibaba"));
// todo META-INF/dubbo/org.apache.dubbo.common.extension.ExtensionFactory
loadDirectory(extensionClasses, DUBBO_DIRECTORY, type.getName());
//todo META-INF/dubbo/com.alibaba.dubbo.common.extension.ExtensionFactory
loadDirectory(extensionClasses, DUBBO_DIRECTORY, type.getName().replace("org.apache", "com.alibaba"));
//todo META-INF/services/org.apache.dubbo.common.extension.ExtensionFactory
loadDirectory(extensionClasses, SERVICES_DIRECTORY, type.getName());
//todo META-INF/services/com.alibaba.dubbo.common.extension.ExtensionFactory
loadDirectory(extensionClasses, SERVICES_DIRECTORY, type.getName().replace("org.apache", "com.alibaba"));
return extensionClasses;
}
下面看一下处理的详细细节信息:
如果从配置文件中读取到的 SPI的实现类添加了@Adaptive注解, 就先缓存起来
- Dubbo将
AdaptiveExtensionFactory.java
暂时缓存起来了
- Dubbo将
没添加@Adaptive的话, 同样将其缓存在不同额容器中, 稍后使用
- Dubbo创建的实例对象是:
SpiExtensionFactory,java
- Dubbo创建的实例对象是:
private void loadClass(Map<String, Class<?>> extensionClasses, java.net.URL resourceURL, Class<?> clazz, String name) throws NoSuchMethodException {
if (!type.isAssignableFrom(clazz)) {
throw new IllegalStateException("Error occurred when loading extension class (interface: " +
type + ", class line: " + clazz.getName() + "), class "
+ clazz.getName() + " is not subtype of interface.");
}
// todo 查看有没有标注 @Adaptive 注解, 如果标注有这个注解的话, 那么就将他暂时存放起来, 而不是执行下面的逻辑, 构造出对象来
// todo 昌武, 你看, 你在验证ioc时, 你提供的PersonInterface很显然是存在这个@Adaptive注解 ,他会在上面提到getAdaptiveClasss() 后 然后newInstance()创建实例
if (clazz.isAnnotationPresent(Adaptive.class)) {
cacheAdaptiveClass(clazz);
} else if (isWrapperClass(clazz)) {
cacheWrapperClass(clazz);
} else {
clazz.getConstructor();
if (StringUtils.isEmpty(name)) {
name = findAnnotationName(clazz);
if (name.length() == 0) {
throw new IllegalStateException("No such extension name for the class " + clazz.getName() + " in the config " + resourceURL);
}
}
String[] names = NAME_SEPARATOR.split(name);
if (ArrayUtils.isNotEmpty(names)) {
cacheActivateClass(clazz, names[0]);
for (String n : names) {
cacheName(clazz, n);
saveInExtensionClass(extensionClasses, clazz, name);
}
}
}
}
小结: 到这里基本上就到了Dubbo的底层确实会去读取配置文件, 根据他们的配置情况, 缓存在不同容器中
好, 到这里上面所说的getExtensionClasses();
方法就说完了, 回到下面的方法中
得到了AdaptiveExtensionFactory
类之后, 接着就通过反射创建的它实例对象, 所以说, 我们要去看他的构造方法, 如下:
- AdaptiveExtensionFactory继承了 ExtensionFactory, 因此它需要重写
getExtension(Class<T> type, String name)
- 重点看他的构造方法
又看到了这行代码 ExtensionLoader<ExtensionFactory> loader = ExtensionLoader.getExtensionLoader(ExtensionFactory.class);
这行代码的执行流程其实已经说过了, 这次根据名称获取的 Extension是 SpiExtensionFactory, 并将它维护起来
新的问题就来了, 这个SPIExtensionFactory是谁呢? 有啥用呢 看下面, 说白了, 用它处理添加有Dubbo的SPI注解的接口, 然后尝试获取这些接口的 实现
构建方法执行完成了, 也就说明 AdaptiveExtension 创建完成了, 刚才所说的 createAdaptiveExtension
injectExtension其实就是回去做IOC / AOP 相关的操作, 现在我们跟踪的实现类是 AdaptiveExtension
它没有依赖其他的属性, 但是我提供的PersonInterface依赖了, 所以说我们暂时先不进如这个方法,稍后再进去查看他的实现
**小结: 下图是我们的启动类, 到目前为止, 我们就看完了启动类的第一行代码做了什么, 那它主要是做了哪些事情呢? **
- 实例化 : AdaptiveExtensionFactory
- 实例化 : SPIExtensionFactory
- 可用来处理用户后续添加进来的SPI相关逻辑
- 实例化 : 用户提供的Spi接口的 ExtensionLoader
Dubbo的IOC细节
下面就继续看这行extensionLoader.getExtension("human")
, 看他的返回值, 很明显, 就是要返回我们需要的personInterface的实现类, 并在这个过程中穿插这IOC和AOP的逻辑
回顾一下实验的环境, 重新整理一下思路: 我们想获取出 key = human的 PersonInterface的实现类, 这实现类长下面这样:
public class Human implements PersonInterface {
private PersonInterface personInterface;
//todo 第一个关注点: 我们的关注点就是说, Human 会帮我们将哪一个实现类当成入参注入进来呢?
//todo 答案是 URL ,dubbo自己封装的URL, 统一资源定位符, dubbo 会解析入参位置的 url中封装的map
//todo map中的key 与 PersonInteface中的使用 @Adaptive("person") 注解标记的value对应, 那么值就是将要注入的实际类型
//todo 第二个关注点: dubbo底层很可能是通过反射使用构造方法完成的属性注入
public void setPersonInterface(PersonInterface personInterface) {
this.personInterface = personInterface;
}
@Override
public String getName(URL url) {
System.out.println("i am Human ");
return "i am Human + " + personInterface.getName(url);
}
}
可以很直接的看到, 这个实现类其实是依赖了一个 PersionInterface的属性,需要将这个属性注入给他, 于是问题来了, 注入的是谁呢? 下面继续往下拉看
进入下面的方法, 主要逻辑如下
- 根据那么取出ExtensionClasses的 Class 对象,
- 这获取出来的对象就是我们前面所说的,就是读取SPI配置文件时获取出来的对象
- 调用injectExtension()方法, 完成对象依赖属性的注入
- 实现Dubbo的AOP , 完成对象方法切面的增强
我们先看下: injectExtension(instance)的实现细节:
主要逻辑如下:
- 通过反射获取出对象的所有的方法
- 如果不是setter方法就返回 (体现出, Dubbo的依赖注入是借助setter方法实现的)
- 如果添加的@Disable注解, 表示明确指定不会进行注入
- 尝试获取出当前对象所依赖的对象, 也就是下面的
objectFactory.getExtension(pt,property)
- 其中objectFactory就是前面创建出来的SPIExtensionFactory
- pt=PersonInterface的Class 描述对象
- property 是从 setPersonInterface()方法中截取出来的: personInterface 字符串
上图中的主要目的就是完成依赖注入, 什么依赖注入呢? 就是在 Human.java中 依赖了一个PersonInterface类型的属性, Dubbo需要帮我填充上 , HumanInterface.java 中锁依赖的那个具体的实现类是谁呢? 就是在上面函数中的通过 objectFactory.getExtension(Class,name)
动态生成出来的
当我们继续跟进这个getExtension(), 就会发现下面的现象, 看我在下图中标出来的绿色部分, 可以发现 , 他获取出来的 ExtensionLoader全称如下: 它就是Dubbo我们生成出来的代理 ExtensionLoader
再进一步, 通过loader 获取出自适应的拓展类: getAdapativeExtension()
通过反编译看一下生成的Interface是谁, 可以看一下,它的实现, 这就是为什么Dubbo通过URL就能知道该注入谁, 用谁取干活
Dubbo的AOP细节 (wrapper)
先说啥是AOP, 就是面向切面编程, 其实说白了就是对现有的对象进行增强
Dubbo是怎么做的呢? 按照Dubbo的约定, 我们的这样编码:即 通过继承+构造方法 实现AOP , 就像下面这样
Dubbo的底层实现: 处理AOP的逻辑在下面
Dubbo会从SPI配置文件中找到我们添加就进去的Wrapperlei, 通过构造方法反射出他们的实例,, 重要的是将反射出来的这个实例替换成了原来未被增强的 对象, 就跟java的感觉就像是升级版的静态代理一样
最后打一个小广告: 我是bloger 赐我白日梦, 本科大三在读, 热衷java研发, 期望有一份Java相关实习岗位的工作, 可以全职实习半年左右, 最理想城市是北京, 求大佬的内推哇
探究Dubbo的拓展机制: 下的更多相关文章
- 探究Dubbo的拓展机制: 上
这篇博文是我决心深度学习Dubbo框架时记录的笔记, 主题是Dubbo的拓展点, 下面的几个部分相对来说比较零散, 貌似是不和主题挂钩的 , 并且是一些很冷门的知识点 , 但是它们确实是深入学习Dub ...
- jdk和dubbo的SPI机制
前言:开闭原则一直是软件开发领域中所追求的,开闭原则中的"开"是指对于组件功能的扩展是开放的,是允许对其进行功能扩展的,“闭”,是指对于原有代码的修改是封闭的,即不应该修改原有的代 ...
- Dubbo剖析-SPI机制
文章要点: 1.什么是SPi 2.Dubbo为什么要实现自己的SPi 3.Dubbo的IOC和AOP 4.Dubbo的Adaptive机制 5.Dubbo动态编译机制 6.Dubbo与Spring的融 ...
- Dubbo的SPI机制与JDK机制的不同及原理分析
从今天开始,将会逐步介绍关于DUbbo的有关知识.首先先简单介绍一下DUbbo的整体概述. 概述 Dubbo是SOA(面向服务架构)服务治理方案的核心框架.用于分布式调用,其重点在于分布式的治理. 简 ...
- 面试常问的dubbo的spi机制到底是什么?
前言 dubbo是一款微服务开发框架,它提供了 RPC通信 与 微服务治理 两大关键能力.作为spring cloud alibaba体系中重要的一部分,随着spring cloud alibaba在 ...
- Wcf实现IServiceBehavior拓展机制
IServiceBehavior接口 描述:提供一种在整个服务内修改或插入自定义拓展机制: 命名空间: System.ServiceModel.Description程序集: System.Ser ...
- 9.5 dubbo事件通知机制
dubbo事件通知机制:http://dubbo.io/books/dubbo-user-book/demos/events-notify.html 一.使用方式 两个服务: DemoService: ...
- dubbo的spi机制
SPI SPI是一种扩展机制,在java中SPI机制被广泛应用,比如Spring中的SpringServletContainerInitializer 使得容器启动的时候SpringServletCo ...
- dubbo事件通知机制(1)
此文已由作者岳猛授权网易云社区发布. 欢迎访问网易云社区,了解更多网易技术产品运营经验. dubbo事件通知机制:http://dubbo.io/books/dubbo-user-book/demos ...
随机推荐
- 阿里巴巴Java编程规范考试
阿里巴巴Java编程规范考试 今天在阿里云官网把阿里巴巴Java编程规范认证考试考过了, 写下这篇文章总结一下考试中需要注意的知识点, 主体内容还是要直接看规范: 编程规约 异常日志 单元测试 安全规 ...
- HashMap和HashSet的使用,区别。集合,Array、Collection(List/Set/Queue)、Map
HashMap和HashSet的区别 HashMap和HashSet的区别是Java面试中最常被问到的问题.如果没有涉及到Collection框架以及多线程的面试,可以说是不完整.而Collectio ...
- js基础-原型
1.定义:我们创建的函数都有一个prototype(原型)属性,该属性是一个对象, 原型模式声明中多了两个属性(自动生成). 构造函数: function Box(nam ...
- H3C 环路避免机制三:毒性逆转
- springboot上传文件时500错误,提示临时目录无效
org.springframework.web.multipart.MultipartException: Could not parse multipart servlet request; nes ...
- H3C 示例:计算子网内可用地址数
- SpringBoot: 浅谈文件上传和访问的坑 (MultiPartFile)
本次的项目环境为 SpringBoot 2.0.4, JDK8.0. 服务器环境为CentOS7.0, Nginx的忘了版本. 前言 SpringBoot使用MultiPartFile接收来自表单的f ...
- js快速替换json里的key值
需求是将b根据a的值替换对象中的key值 let a = ["code","name","date","font"]; ...
- ZR8.2 DP
DP 1CF1101D 我们发现,最终答案一定和质因数有关 我们发现\(w_i <= 2*10^5\)级别的树,他的素因子的个数不会非常多(\(<=10\)) 然后我们就设 gcd是\(d ...
- HDU - 5015 233 Matrix (矩阵快速幂)
In our daily life we often use 233 to express our feelings. Actually, we may say 2333, 23333, or 233 ...