前言
- 互联网时代,单体架构的项目已经不能满足用户的需求了,于是,微服务被应用得越来越广泛。所谓微服务,即是为了保证项目的高可用而将所有的模块抽成一个个具体的项目,最后采取分布式部署策略进行线上运行。于是,一个个
RPC(Remote Process Call)
框架就这样火热了起来。(其实我就知道spring cloud和dubbo。。。) - 作为国人开发框架的代表,前端有vue.js。后端呢,有Dubbo,接下来我们就从源码的角度来认识Dubbo吧。
一、Dubbo简介
- 直接看官网的介绍不香吗? 传送门
二、Java SPI机制
- 要学习dubbo,还是要先了解下java的SPI(Service Provider Interface)机制。因为dubbo就充分利用了SPI来达到高扩展性的。
- Java SPI,大致就是在运行时,使用指定的类
java.util.ServiceLoader
去加载classpath:META-INF/services/xxxxx
的文件。其中xxxxx
是以某个接口的全限定名命名的文件,eg: com.eugene.sumarry.UserService。文件中的内容是一行行的UserService接口实现类。大致流程就是加载文件、读取文件、反射创建对象
。具体可以看Demo
三、Dubbo SPI机制
- Java 有SPI了,Dubbo为啥还要自己实现一个类似的功能?有病哦? 不不不,不是人家有病,是因为java的SPI功能太鸡肋了,我要获取特定的实现类还要使用迭代器去循环获取,拿到后再停止循环。。。(憋住不笑,我要忍住。。。)
- Java自带的SPI吐槽完了。我们来看下Dubbo的SPI。Dubbo的SPI不仅能提供实现类还能进行AOP、IOC。是的,你没听错,它和spring一样也能实现这样的功能(
只不过功能没有spring那么强,谅解一下嘛,毕竟人家不是专门做AOP、IOC的,人家可是大名鼎鼎的RPC框架
)。淡也扯完了,那就开始实现Dubbo的SPI吧。
3.1 Dubbo之AOP
- 为什么先讲AOP呢?因为AOP比较简单。所谓AOP,其实就是基于代理的思想实现的。据咱们所知,JDK有动态代理、spring借用了CGLIB代理。那Dubbo是不是也是基于CGLIB进行开发的呢?
不不不,Dubbo使用AOP的方式不是CGLIB也不是JDK动态代理,而是静态代理!
- 何为静态代理?看如下代码:
public class XiaoMing { private XiaoHua xiaoHua; public void sellSomething(Something something) { // 使用这个something谋取点小利 xiaoHua.sellSomething(); } }
- 你没看错,这就是一个代理。小明代理了小花卖东西的操作。在小花真正卖东西之前,我可以拿这个东西谋取点小利(
现实中,你别这么干,不然会没朋友的!
)再交给小花去卖。在这儿,代理起的作用就是在执行某个操作的前后都能做一些事情
,也就是增强方法逻辑
。在Dubbo中,AOP就是这么实现的,我们代理类中维护了目标对象,然后对目标对象的方法进行增强,这样就能在目标做操作的前后做手脚了(你真是个小人。。废话了那么多你倒是告诉我怎么做啊。)
3.1.1 实现AOP
- UserService.java
- UserServiceImpl.java
- UserServiceImplProxy1.java
- UserServiceImplProxy2.java
- SPI文件: com.eugene.sumarry.aop.UserService
- Entry.java
- 运行结果:
- debug结果
3.1.2 AOP总结
- 通过上述的8组图片,大家可能已经懵逼了。这尼玛,这哪是AOP, 与我认识的AOP不一样呀,切面呢?切点呢?刚刚说了,这是采用的静态代理,当我们以
userService
作为key去获取extension时,它实际上获取的是UserServiceImplProxy2
, 那为什么是它而不是UserServiceImpl
呢?这么乱,我怎么用啊。好嘛,下面来讲讲如何使用它
3.1.3 如何使用AOP
-
细心的小伙伴可能发现了,为啥两个以
Proxy
后缀的实现类就能代理UserServiceImpl呢?而且为啥Proxy2
还能代理Proxy1
。答案是这样的: 在Dubbo中执行userServiceExtensionLoader.getExtension("userService");
代码时,会去五个目录解析com.eugene.sumarry.aop.UserService文件:目录名 META-INF/dubbo/internal/ META-INF/apache/internal/ META-INF/services/ META-INF/dubbo/ META-INF/apache/ 并将相关实现类缓存起来(这里面有大文章,后续总结源码时列出来),其中有个叫
Wrapper
的概念。能被称作为Wrapper
, 需要具有如下条件:条件 是当前接口的实现类 维护一个当前接口类型的属性A 拥有一个有参构造函数,并使用此有参构造函数填充属性A ok。拥有上述的条件就能被称为一个
Wrapper类
,并且他们会被存入一个叫cachedWrapperClasses
的set集合中(记住它,后面会用到)。在解析SPI文件时,除了会缓存Wrapper
类外,还会缓存普通类,何为普通类
?,见如下条件条件 实现类中无@Adaptive注解修饰 不是一个wrapper类 ok。那我们可以确定
UserServiceImpl
就是一个名副其实的普通类,并且会把普通类存入一个叫cachedNames
的ConcurrentMap中。
基于以上的解释,我觉得我可以描述下Dubbo的AOP流程了。解析完SPI文件后,cachedNames
中存在了一些普通类的信息,cachedWrapperClasses
中存储了一些Wrapper
类的信息。然后我们从cachedNames
中根据"userService"来获取它在SPI配置的实现类com.eugene.sumarry.aop.UserServiceImpl的Class对象,然后利用反射实例化这个对象。代码如下:// 模拟的代码,具体的源码不是这样的,但是效果差不多 Class<?> clazz = cachedClasses.get(name); Object instance= clazz.newInstance()
创建完对象后,关键的代码来了:
// 循环了cachedWrapperClasses属性, // 并将当前创建的实例做为参数,传入了wrapper类的构造方法中 // injectExtension方法是一个根据set方法注入属性的方法,因为目前wrapper属性中没有set方法,所以这里只返回了wrapper的实例,并赋值给了刚刚创建出来的instance,然后再将自己作为参数传入到下一个wrapper的构造方法中 // 注意:这里存储wrapper的底层实现是ConcurrentHashSet,而ConcurrentHashSet的底层是 // ConcurrentHashMap,因此存入缓存的顺序完全取决于key的hash值,因此这里不一定是 // 下面的类包装上的类! Set<Class<?>> wrapperClasses = cachedWrapperClasses; if (CollectionUtils.isNotEmpty(wrapperClasses)) { for (Class<?> wrapperClass : wrapperClasses) { instance = injectExtension((T) wrapperClass.getConstructor(type).newInstance(instance)); } }
看到这里,似乎已经了解Dubbo的AOP机制了,不过它好像只能对同种类型进行代理。而且包装类会对所有的普通类都包装。
3.2 Dubbo之IOC
- 在了解Dubbo的IOC的前提,咱们先了解下ExtensionLoader的获取过程吧,了解了ExtensionLoader的初始化过程,再了解IOC就简单了
3.2.1 Dubbo之初始化ExtensionLoader
-
流程图如下:
-
在执行
ExtensionLoader.getExtensionLoader(UserService.class);
代码时,会先从静态变量EXTENSION_LOADERS
中去取(因为每个接口对应一个ExtensionLoader,并且每个ExtensionLoader初始化时都会去加载自己识别的SPI文件,所以直接把他缓存起来就行了,因为之后只会对Extension进行读操作,所以存到静态变量没有关系。而且再次获取ExtensionLoader的时候就可以从静态变量中取了),若取不到则创建,在它的构造方法中,会维护一个变量objectFactory
, 源码如下:private ExtensionLoader(Class<?> type) { this.type = type; objectFactory = (type == ExtensionFactory.class ? null : ExtensionLoader.getExtensionLoader(ExtensionFactory.class).getAdaptiveExtension()); }
可以看到,
objectFactory
的值会因传入的class而变化,当传入的class类型为ExtensionFactory
时,当前的实例化的ExtensionLoader中的objectFactory
则为空。若不是,将会递归调用getExtensionLoader方法,只不过这次获取的类型为ExtensionFactory
。因为是第一次获取,静态变量中也没得对应的ExtensionLoader,所以也需要去new。当再次new的时候,在构造方法中发现要new的类型为ExtensionFactory,于是,类型为ExtensionFactory
中维护的objectFactory的属性赋值为null。此时,类型为ExtensionFactory
的ExtensionLoader被new出来了,于是去执行类型为ExtensionFactory
的ExtensionLoader的getAdaptiveExtension()
方法。关于这个方法,记住它,他是创建一个代理类的方法,并且带有解析SPI文件的功能。所以此时,它会去解析classpath中下述五个目录的所有叫org.apache.dubbo.common.extension.ExtensionFactory
的文件。序号 路径 1 META-INF/dubbo/internal/ 2 META-INF/apache/internal/ 3 META-INF/services/ 4 META-INF/dubbo/ 5 META-INF/apache/ 最终将解析后的SPI文件分别以如下表格的形式进行存储:
实现类类型 存入目标属性 属性类型 作用 注意事项 被@Adaptive注解标识 cachedAdaptiveClass Class<?> 默认的代理类 只能有一个实现类被标识,且至少有一个普通类才能起作用 Wrapper类 cachedWrapperClasses Set<Class<?>> 给普通类做AOP时会用到它 目前未测试 被@Active注解标识 cachedActivates Map<String, Object> 目前未测试 目前未测试 被@Active注解标识 cachedNames ConcurrentMap<Class<?>, String> 目前未测试 目前未测试 普通实现类 cachedNames ConcurrentMap<Class<?>, String> getExtension api中的name就是从此变量中验证是否存在 如一个普通实现类都没有,那么无法通过getAdaptiveExtention api获取代理对象 上面说了,被@Adaptive注解标识的类会作为默认代理类,而ExtensionFactory接口的实现类如下:
实现类 是否有@Adaptive注解 AdaptiveExtensionFactory 是 SpiExtensionFactory 否 SpringExtensionFactory 否 综上所述,所以代码
ExtensionLoader.getExtensionLoader(UserService.class)
获取到的ExtensionLoader中的objectFactory为AdaptiveExtensionFactory
的对象(并且在它的构造方法中,会将SpiExtensionFactory和SpringExtensionFactory的实例构建出来并存到自己维护的factories属性上)。同时,别忘了,SpiExtensionFactory和SpringExtensionFactory会作为普通会存到类型为ExtensionFactory的ExtensionLoader的cachedNames属性中。好了,到现在,一个ExtensionLoader的初始化操作就完成了。总结下,一个接口对应一个ExtensionLoader,只有第一次获取的时候会去new并去加载自己能识别的SPI文件,最后并将new出来的ExtensionLoader缓存至静态变量中。并且在解析SPI文件时,每种实现类存放的位置不一样,具体参考上述的表格
3.2.2 Dubbo之实现IOC功能
- 了解了ExtensionLoader初始化流程之后,我们开始来实现IOC功能
- IndexService.java
- IndexServiceImpl.java
- PersonService.java
- ManServiceImpl.java
- SPI: com.eugene.sumarry.ioc.IndexService
- SPI: com.eugene.sumarry.ioc.PersonService
- 运行结果:
3.2.3 IOC总结
-
可以发现ManServiceImpl.java的
say
方法中打印出了两句话,其中IndexServiceImpl index的打印内容
来自依赖的indexService属性
。 看到这,可能有小伙伴又要吐槽,这IOC也太。。。。。兄弟,稳住好吧。都说了,人家不是专注于做AOP、IOC的,人家是大名鼎鼎的RPC框架!
不过说实话,本人当时接触这个功能时,心里也很难接受,觉得很乱,我依赖一个属性为什么要在接口的方法上添加注解为什么不和spring一样直接添加在属性上,这样难道不香吗?奈何我有看源码的能力,终于明白了dubbo这么干的原因了 ------ 这里先放个彩蛋: 因为dubbo这样做可以做到不同的方法依赖于不同的实例对象
。听到这,眼睛是否已经大放溢彩了呢?真香!
-
针对上述对
ExtensionLoader
初始化流程,大家可以猜测下执行完这段代码ExtensionLoader.getExtensionLoader(PersonService.class)
后cacheNames属性中会有哪些值?emmm,我自问自答吧,那肯定是一个呀, 因为就一个实现类呀… O(∩_∩)O哈哈~,开玩笑的,因为实现类ManServiceImpl
是一个普通类
。所以,我们接着往这些走,当执行到extensionLoader.getExtension("man");
代码时,我们知道,它肯定会从cacheNames
中去判断有没有man
这个key,进而反射创建对象,咱们用图来说话:
可以看到,的确是用反射来创建了ManServiceImpl
对象,并且还能看到里面的indexService
属性为空的。不知道大家有没有看到下面的injectExtension(instance)
代码,此代码就是完成了IOC,接下来我们仔细看:
还记得objectFactory
对象吗?它就是AdaptiveExtensionFactory
,injectExtension方法里面的逻辑呢是拿到实例化好的manServiceImpl
对象的所有方法,当此方法满足set方法
、无@DisableInjection注解
且不是原始类型
(如下表):dubbo不注入的原始类型 dubbo不注入的原始类型 Boolean Character Integer Long Void String 的条件后,即开始创建依赖对象
indexService
,即Object object = objectFactory.getExtension(pt, property);
上面说了objectFactory是AdaptiveExtensionFactory
,于是我们进入它的getExtension方法, 如下图
可以发现,它是遍历自己维护的factories,去获取实例。上面也说了,factories中存的是
SpiExtensionFactory和SpringExtensionFactory的实例(在没有集成spring时,基本上都是用SpiExtensionFactory来获取实例
),如下图:
进入的是SpiExtensionFactory,并且这里我专门执行了loader.getSupportedExtensions()
代码,可以看到,结果是里面有值,不为空(我上面说过getAdaptiveExtension()
方法是获取代理对象的),所以会生成代理类(自适应扩展类),代理类的校验逻辑就不看了,校验通过后会进入createAdaptiveExtensionClass()
方法, 我们看到这里的代理类已经生成了,如下(我格式化了,并添加了注释):
package com.eugene.sumarry.ioc; import org.apache.dubbo.common.extension.ExtensionLoader; public class IndexService$Adaptive implements com.eugene.sumarry.ioc.IndexService { public void index(org.apache.dubbo.common.URL arg0) { if (arg0 == null) throw new IllegalArgumentException("url == null"); org.apache.dubbo.common.URL url = arg0; // ****************很重要******************** // 此处的man和test取决于IndexService接口中的@SPI注解以及 // 接口中index方法中的@Adaptive中的值 // 最终会根据这两个key从传入的URL中去取, // 还记得传入的URL中有个参数叫"man"吗?所以最终会拿出man对应的value: indexServiceImpl // 即最终,会拿到IndexService的ExtensionLoader,并通过indexServiceImpl去获取对应的实例,并执行实例的index方法。关于这里面写死的两个参数名也有大文章,接下来会总结 // ****************很重要******************** String extName = url.getParameter("man", "test"); if(extName == null) throw new IllegalStateException("Failed to get extension (com.eugene.sumarry.ioc.IndexService) name from url (" + url.toString() + ") use keys([man])"); com.eugene.sumarry.ioc.IndexService extension = (com.eugene.sumarry.ioc.IndexService)ExtensionLoader.getExtensionLoader(com.eugene.sumarry.ioc.IndexService.class).getExtension(extName); extension.index(arg0); } public void index5(org.apache.dubbo.common.URL arg0) { if (arg0 == null) throw new IllegalArgumentException("url == null"); org.apache.dubbo.common.URL url = arg0; String extName = url.getParameter("index.service", "test"); if(extName == null) throw new IllegalStateException("Failed to get extension (com.eugene.sumarry.ioc.IndexService) name from url (" + url.toString() + ") use keys([index.service])"); com.eugene.sumarry.ioc.IndexService extension = (com.eugene.sumarry.ioc.IndexService)ExtensionLoader.getExtensionLoader(com.eugene.sumarry.ioc.IndexService.class).getExtension(extName); extension.index5(arg0); } public void index2(org.apache.dubbo.common.URL arg0) { if (arg0 == null) throw new IllegalArgumentException("url == null"); org.apache.dubbo.common.URL url = arg0; String extName = url.getParameter("man1", "test"); if(extName == null) throw new IllegalStateException("Failed to get extension (com.eugene.sumarry.ioc.IndexService) name from url (" + url.toString() + ") use keys([man1])"); com.eugene.sumarry.ioc.IndexService extension = (com.eugene.sumarry.ioc.IndexService)ExtensionLoader.getExtensionLoader(com.eugene.sumarry.ioc.IndexService.class).getExtension(extName); extension.index2(arg0); } public void index3(org.apache.dubbo.common.URL arg0) { throw new UnsupportedOperationException("The method public abstract void com.eugene.sumarry.ioc.IndexService.index3(org.apache.dubbo.common.URL) of interface com.eugene.sumarry.ioc.IndexService is not adaptive method!"); } public void index4() { throw new UnsupportedOperationException("The method public abstract void com.eugene.sumarry.ioc.IndexService.index4() of interface com.eugene.sumarry.ioc.IndexService is not adaptive method!"); } }
代理类方法内部中
url.getParameter("man", "test");
中man和test的来源, 如下A. 若@SPI注解指定了key,@Adaptive未指定key。则使用@Adaptive默认的key和@SPI指定的key B. 若@SPI注解指定了key,@Adaptive指定key。 则使用他们两 C. 若@SPI注解未指定key,@Adaptive指定key。 则使用@Adaptive指定key D. 若@SPI注解未指定key, @Adaptive未指定key,则使用@Adaptive默认的key E. 若实现类中有@Adaptive注解,则也跟不会走代理逻辑,因为直接把这个实现类当做代理类返回出去了 上述说的默认key解释如下: 接口名驼峰之间用.隔开,eg: IndexService, 那么默认key为"index.service"
看到这,似乎已经明白了,
这个代理类中并没有维护一个叫
IndexService的属性,而是在每一个方法内部根据此依赖的属性的类型获取ExtensionLoader并根据指定key从URL中获取value,最终再通过value从extensionLoader获取对应的实例
。这也证明了我上述所说的: dubbo的ioc可以实现每一个方法依赖注入不同的实例。通过上述的阐述,我们在使用IOC时能得出这样的结论: 我们需要依赖哪个接口,那么这个接口中的所有方法都必须添加URL参数和@Adaptive注解,否则代理对象中的对应方法会抛出异常,eg: 上述的index3、index4
方法。最终导致方法不可用。
ok。IndexService
的代理对象的java类出来了,剩下的就是把它编译成class类,并使用反射创建实例咯,再后面就是把它注入到创建的ManServiceImpl
实例中去。至此,Dubbo的IOC完成了。实际上,Dubbo的依赖注入功能是基于扩展而言的,也就是说只能从扩展中获取类来完成注入。
四、总结
- 好了,终于把他写完了。通过此次的学习呢,我们已经了解Dubbo ExtensionLoader的初始化流程、SPI实现IOC、AOP机制,这应该是为后面的源码学习提供的基础。个人感觉,这就像是spring创建bean和获取bean的流程,后续肯定会经常用到它们
- Github关于dubbo的相关总结: 传送门, 欢迎Star
- I am a slow walker, but I never walk backwards.