承接上篇, 本篇博文的主题就是认认真真捋一捋, 看一下 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暂时缓存起来了
  • 没添加@Adaptive的话, 同样将其缓存在不同额容器中, 稍后使用

    • Dubbo创建的实例对象是: SpiExtensionFactory,java
    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的拓展机制: 下的更多相关文章

  1. 探究Dubbo的拓展机制: 上

    这篇博文是我决心深度学习Dubbo框架时记录的笔记, 主题是Dubbo的拓展点, 下面的几个部分相对来说比较零散, 貌似是不和主题挂钩的 , 并且是一些很冷门的知识点 , 但是它们确实是深入学习Dub ...

  2. jdk和dubbo的SPI机制

    前言:开闭原则一直是软件开发领域中所追求的,开闭原则中的"开"是指对于组件功能的扩展是开放的,是允许对其进行功能扩展的,“闭”,是指对于原有代码的修改是封闭的,即不应该修改原有的代 ...

  3. Dubbo剖析-SPI机制

    文章要点: 1.什么是SPi 2.Dubbo为什么要实现自己的SPi 3.Dubbo的IOC和AOP 4.Dubbo的Adaptive机制 5.Dubbo动态编译机制 6.Dubbo与Spring的融 ...

  4. Dubbo的SPI机制与JDK机制的不同及原理分析

    从今天开始,将会逐步介绍关于DUbbo的有关知识.首先先简单介绍一下DUbbo的整体概述. 概述 Dubbo是SOA(面向服务架构)服务治理方案的核心框架.用于分布式调用,其重点在于分布式的治理. 简 ...

  5. 面试常问的dubbo的spi机制到底是什么?

    前言 dubbo是一款微服务开发框架,它提供了 RPC通信 与 微服务治理 两大关键能力.作为spring cloud alibaba体系中重要的一部分,随着spring cloud alibaba在 ...

  6. Wcf实现IServiceBehavior拓展机制

    IServiceBehavior接口 描述:提供一种在整个服务内修改或插入自定义拓展机制: 命名空间:  System.ServiceModel.Description程序集:  System.Ser ...

  7. 9.5 dubbo事件通知机制

    dubbo事件通知机制:http://dubbo.io/books/dubbo-user-book/demos/events-notify.html 一.使用方式 两个服务: DemoService: ...

  8. dubbo的spi机制

    SPI SPI是一种扩展机制,在java中SPI机制被广泛应用,比如Spring中的SpringServletContainerInitializer 使得容器启动的时候SpringServletCo ...

  9. dubbo事件通知机制(1)

    此文已由作者岳猛授权网易云社区发布. 欢迎访问网易云社区,了解更多网易技术产品运营经验. dubbo事件通知机制:http://dubbo.io/books/dubbo-user-book/demos ...

随机推荐

  1. 微信小程序wx.request POST获取不到数据解决办法

    get //发起请求     wx.request({       url: 'http://www.xiaochengxu.com/home/index/curd', //仅为示例,并非真实的接口地 ...

  2. HDU 5971"Wrestling Match"(二分图染色)

    传送门 •题意 给出 n 个人,m 场比赛: 这 m 场比赛,每一场比赛中的对决的两人,一个属于 "good player" 另一个属于 "bad player" ...

  3. Native memory allocation (mmap) failed to map xxx bytes for committing reserved memory

    遇到问题 在服务器上运行 nexus 出现Native memory allocation (mmap) failed to map 838860800 bytes for committing re ...

  4. 2018-12-25-SourceYard-制作源代码包

    title author date CreateTime categories SourceYard 制作源代码包 lindexi 2018-12-25 9:43:7 +0800 2018-12-09 ...

  5. vue 组件的强制刷新

    组件 <vue-component v-if="hackReset"></vue-component> <button @click="a& ...

  6. Python--day60--建立第一个Djiango项目

  7. Java语言中的正则表达式

    正则表达式是什么? 正则表达式是一种强大而灵活的文本处理工具.初学正则表达式时,其语法是一个难点,但它确实是一种简洁.动态的语言.正则表达式提供了一种完全通用的方式,能够解决各种字符串处理相关的问题: ...

  8. C# 单例类

    单例类 有时候我们不要在一个程序中创建太多的实例.只想用一个全局的实例和一个可以访问点.那么我们需要一个单例类. 因为是单例类啦,所以构造函数肯定是私有的. 需要了解的术语 懒汉式 顾名思义.什么时候 ...

  9. Java并发编程(您不知道的线程池操作), 最受欢迎的 8 位 Java 大师,Java并发包中的同步队列SynchronousQueue实现原理

    Java_并发编程培训 java并发程序设计教程 JUC Exchanger 一.概述 Exchanger 可以在对中对元素进行配对和交换的线程的同步点.每个线程将条目上的某个方法呈现给 exchan ...

  10. vue-learning:17- js - methods

    methods 函数是十分优雅的语言特性,它让我们可以采用可复用的方式存储一段逻辑,从而不用重复代码就可以在多处调用.函数.组件.模块等都有复用代码的考虑,函数应该是最早组织复用代码的实现. 在vue ...