Dubbo2.7.3版本源码学习系列一: 初始Dubbo利用SPI机制实现AOP和IOC的源码分析

前言

  • 互联网时代,单体架构的项目已经不能满足用户的需求了,于是,微服务被应用得越来越广泛。所谓微服务,即是为了保证项目的高可用而将所有的模块抽成一个个具体的项目,最后采取分布式部署策略进行线上运行。于是,一个个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
  1. UserService.java
    在这里插入图片描述
  2. UserServiceImpl.java
    在这里插入图片描述
  3. UserServiceImplProxy1.java
    在这里插入图片描述
  4. UserServiceImplProxy2.java
    在这里插入图片描述
  5. SPI文件: com.eugene.sumarry.aop.UserService
    在这里插入图片描述
  6. Entry.java
    在这里插入图片描述
  7. 运行结果:
    在这里插入图片描述
  8. 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的文件。

    序号路径
    1META-INF/dubbo/internal/
    2META-INF/apache/internal/
    3META-INF/services/
    4META-INF/dubbo/
    5META-INF/apache/

    最终将解析后的SPI文件分别以如下表格的形式进行存储:

    实现类类型存入目标属性属性类型作用注意事项
    被@Adaptive注解标识cachedAdaptiveClassClass<?>默认的代理类只能有一个实现类被标识,且至少有一个普通类才能起作用
    Wrapper类cachedWrapperClassesSet<Class<?>>给普通类做AOP时会用到它目前未测试
    被@Active注解标识cachedActivatesMap<String, Object>目前未测试目前未测试
    被@Active注解标识cachedNamesConcurrentMap<Class<?>, String>目前未测试目前未测试
    普通实现类cachedNamesConcurrentMap<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功能
  1. IndexService.java
    在这里插入图片描述
  2. IndexServiceImpl.java
    在这里插入图片描述
  3. PersonService.java
    在这里插入图片描述
  4. ManServiceImpl.java
    在这里插入图片描述
  5. SPI: com.eugene.sumarry.ioc.IndexService
    在这里插入图片描述
  6. SPI: com.eugene.sumarry.ioc.PersonService
    在这里插入图片描述
  7. 运行结果:
    在这里插入图片描述
3.2.3 IOC总结
  • 可以发现ManServiceImpl.javasay方法中打印出了两句话,其中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不注入的原始类型
    BooleanCharacter
    IntegerLong
    VoidString

    的条件后,即开始创建依赖对象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.
  • 4
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值