标签:原来 取出 拓展 容器 rect static 处理 activate 北京
承接上篇, 本篇博文的主题就是认认真真捋一捋, 看一下 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 , 见名知意, 拓展点的加载器, 那什么是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
, 方法执行的逻辑如下
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;
}
那我们是第一次进来, 肯定是没有的, 因此我们看他是如何进行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());
}
在上面的构造方法中, 就有蹊跷了, 逻辑如下
我所说的有蹊跷的地方: 我们本来不是前来创建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()
这个 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();
方法, 源码如下: 主要逻辑如下:
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;
}
下面看一下处理的详细细节信息:
AdaptiveExtensionFactory.java
暂时缓存起来了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
类之后, 接着就通过反射创建的它实例对象, 所以说, 我们要去看他的构造方法, 如下:
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依赖了, 所以说我们暂时先不进如这个方法,稍后再进去查看他的实现
小结: 下图是我们的启动类, 到目前为止, 我们就看完了启动类的第一行代码做了什么, 那它主要是做了哪些事情呢?
下面就继续看这行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的属性,需要将这个属性注入给他, 于是问题来了, 注入的是谁呢? 下面继续往下拉看
进入下面的方法, 主要逻辑如下
我们先看下: injectExtension(instance)的实现细节:
主要逻辑如下:
objectFactory.getExtension(pt,property)
上图中的主要目的就是完成依赖注入, 什么依赖注入呢? 就是在 Human.java中 依赖了一个PersonInterface类型的属性, Dubbo需要帮我填充上 , HumanInterface.java 中锁依赖的那个具体的实现类是谁呢? 就是在上面函数中的通过 objectFactory.getExtension(Class,name)
动态生成出来的
当我们继续跟进这个getExtension(), 就会发现下面的现象, 看我在下图中标出来的绿色部分, 可以发现 , 他获取出来的 ExtensionLoader全称如下: 它就是Dubbo我们生成出来的代理 ExtensionLoader
再进一步, 通过loader 获取出自适应的拓展类: getAdapativeExtension()
通过反编译看一下生成的Interface是谁, 可以看一下,它的实现, 这就是为什么Dubbo通过URL就能知道该注入谁, 用谁取干活
先说啥是AOP, 就是面向切面编程, 其实说白了就是对现有的对象进行增强
Dubbo是怎么做的呢? 按照Dubbo的约定, 我们的这样编码:即 通过继承+构造方法 实现AOP , 就像下面这样
Dubbo的底层实现: 处理AOP的逻辑在下面
Dubbo会从SPI配置文件中找到我们添加就进去的Wrapperlei, 通过构造方法反射出他们的实例,, 重要的是将反射出来的这个实例替换成了原来未被增强的 对象, 就跟java的感觉就像是升级版的静态代理一样
最后打一个小广告: 我是bloger 赐我白日梦, 本科大三在读, 热衷java研发, 期望有一份Java相关实习岗位的工作, 可以全职实习半年左右, 最理想城市是北京, 求大佬的内推哇
标签:原来 取出 拓展 容器 rect static 处理 activate 北京
原文地址:https://www.cnblogs.com/ZhuChangwu/p/12189152.html