趁同事上厕所的时间,看完了 Dubbo SPI 的源码,瞬间觉得 JDK SPI 不香了

2024-03-20 5953阅读
  • 👏作者简介:大家好,我是爱敲代码的小黄,独角兽企业的Java开发工程师,CSDN博客专家,阿里云专家博主
  • 📕系列专栏:Java设计模式、Spring源码系列、Netty源码系列、Kafka源码系列、JUC源码系列、duubo源码系列
  • 🔥如果感觉博主的文章还不错的话,请👍三连支持👍一下博主哦
  • 🍂博主正在努力完成2023计划中:以梦为马,扬帆起航,2023追梦人
  • 📝联系方式:hls1793929520,加我进群,大家一起学习,一起进步,一起对抗互联网寒冬👀

    趁同事上厕所的时间,看完了 Dubbo SPI 的源码,瞬间觉得 JDK 不香了 第1张

    文章目录

      • 一、引言
      • 二、SPI是什么
      • 三、使用介绍
      • 四、原理介绍
        • 1、SPI注解
        • 五、源码剖析
          • 1、Loader的创建
          • 2、获取实例
            • 2.1 解析文件配置
            • 2.2 实例化创建
            • 2.3 前置处理
            • 2.4 依赖注入
            • 2.5 后置操作
            • 2.6 Wrapper 的包装
              • 2.6.1 Wrapper缓存
              • 2.6.2 Wrapper实现
              • 2.7 生命周期管理
              • 六、流程图
              • 七、总结

                一、引言

                兄弟们,上次的故障结果出来了

                还好销售团队给力,没有让客户几千万的单子丢掉,成功挽回了本次损失

                趁同事上厕所的时间,看完了 Dubbo SPI 的源码,瞬间觉得 JDK 不香了 第2张

                不过内部处罚还是相对严重,年终奖悬了

                趁同事上厕所的时间,看完了 Dubbo SPI 的源码,瞬间觉得 JDK 不香了 第3张

                这也告诫我们 要对生产保持敬畏之情!

                恰巧最近领导看我在写 Dubbo 源码系列,看到我们的项目中用了 SPI 扩展

                于是给我一个将功补过的机会,让我好好的分析分析 Dubbo 的 SPI 的扩展机制,进行组内技术分享

                作为一个常年分享 源码系列 文章的选手,当然不会拒绝!

                乾坤未定,你我皆是黑马,冲!

                二、SPI是什么

                SPI 全称 Service Provider Interface ,是 Java 提供的一套用来被第三方实现或者扩展的 API,它可以用来启用框架扩展和替换组件。

                趁同事上厕所的时间,看完了 Dubbo SPI 的源码,瞬间觉得 JDK 不香了 第4张

                Java SPI 实际上是 基于接口的编程+策略模式+配置文件 组合实现的动态加载机制。

                Java SPI 就是提供这样的一个机制:为某个接口寻找服务实现的机制。

                将装配的控制权移到程序之外,在模块化设计中这个机制尤其重要。

                所以 SPI 的核心思想就是解耦。

                三、使用介绍

                我们定义一个接口:City

                @SPI
                public interface City {
                    String getCityName();
                }
                

                实现其两个类:

                • BeijingCity
                  public class BeijingCity implements City{
                      @Override
                      public String getCityName() {
                          return "北京";
                      }
                  }
                  
                  • TianjinCity
                    public class TianjinCity implements City{
                        @Override
                        public String getCityName() {
                            return "天津";
                        }
                    }
                    

                    重点来了:我们要在 resources 文件夹下面建立一个路径:META-INF/dubbo

                    然后我们建立一个 txt 名为:com.dubbo.provider.SPI.Dubbo.City,如下:

                    我们在这个文件中写上各实现类的路径:

                    beijing=com.dubbo.provider.SPI.Dubbo.BeijingCity
                    tianjin=com.dubbo.provider.SPI.Dubbo.TianjinCity
                    

                    有的朋友可能会问,这里为什么和 Java SPI 的实现不同?

                    这也正是 Dubbo 实现精准实例化的原因,我们后面也会聊到

                    测试方法:

                    public class DubboSPITest {
                        public static void main(String[] args) {
                            ExtensionLoader loader = ExtensionLoader.getExtensionLoader(City.class);
                            City tianjin = loader.getExtension("beijing");
                            System.out.println(tianjin.getCityName());
                        }
                    }
                    

                    测试结果:

                    北京
                    

                    从这里我们可以看出,Dubbo 可以通过 loader.getExtension("beijing") 精确的生成我们需要的实例

                    精确生成是如何实现的呢?我们继续往下看

                    四、原理介绍

                    在源码介绍之前,我们先说几个原理细节,防止大家后面的源码看迷糊

                    1、SPI注解

                    @Documented
                    @Retention(RetentionPolicy.RUNTIME)
                    @Target({ElementType.TYPE})
                    public @interface SPI {
                        /**
                         * default extension name
                         */
                        String value() default "";
                        /**
                         * scope of SPI, default value is application scope.
                         */
                        ExtensionScope scope() default ExtensionScope.APPLICATION;
                    }
                    

                    在 SPI 注解中,存在两个参数:value、scope

                    value

                    • 作用:如果某个 SPI 扩展没有指定实现类名称,则会使用 @SPI 注解中指定的默认值

                      scope:指定 SPI 扩展实现类的作用域( Constants.SINGLETON)

                      • Constants.FRAMEWORK(框架作用域):实现类在 Dubbo 框架中只会创建一个实例,并且在整个应用程序中共享。
                      • Constants.APPLICATION(应用程序作用域):实现类在应用程序上下文中只会创建一个实例,并且在整个应用程序中共享。
                      • Constants.MODULE(模块作用域):实现类在模块上下文中只会创建一个实例,并且在整个模块中共享。
                      • Constants.SELF(自定义作用域):实现类的作用范围由用户自行定义,可以是任何范围。

                        当然,这里 Dubbo 默认的是 Constants.APPLICATION,我们也只需要关注这个即可。

                        五、源码剖析

                        1、Loader的创建

                        我们 Dubbo 的 SPI 从 ExtensionLoader.getExtensionLoader(City.class) 开始,看一看其实现方案

                        public  ExtensionLoader getExtensionLoader(Class type) {
                            // 1、校验
                            checkDestroyed();
                            // 2、是否有本地Loader缓存
                            ExtensionLoader loader = (ExtensionLoader) extensionLoadersMap.get(type);
                            // 3、是否有本地Scope缓存
                            ExtensionScope scope = extensionScopeMap.get(type);
                            
                            // 4、如果当前的Scope为空
                            // 4.1 获取当前接口类的SPI注解
                            // 4.2 获取当前注解的scope
                            // 4.3 放入scope缓存
                            if (scope == null) {
                                SPI annotation = type.getAnnotation(SPI.class);
                                scope = annotation.scope();
                                extensionScopeMap.put(type, scope);
                            }
                            // 5、如果加载器为空且当前是SELF,直接创建loader
                            if (loader == null && scope == ExtensionScope.SELF) {
                                loader = createExtensionLoader0(type);
                            }
                            // 6、如果当前加载器为空,去父类找加载器
                            if (loader == null) {
                                if (this.parent != null) {
                                    loader = this.parent.getExtensionLoader(type);
                                }
                            }
                            // 7、如果父类也没有实例化,那么实例化并放入缓存
                            if (loader == null) {
                                loader = createExtensionLoader(type);
                            }
                        	
                            // 8、返回加载器
                            return loader;
                        }
                        

                        从上面的源码我们可以看到,获取 ExtensionLoader 采用了 缓存 + 父类继承 的模式

                        这种继承机制设计得比较巧妙,可以避免重复加载类,提高系统性能。

                        2、获取实例

                        Dubbo 通过 loader.getExtension("tianjin") 获取对应的实例

                        public T getExtension(String name) {
                            T extension = getExtension(name, true);
                            return extension;
                        }
                        public T getExtension(String name, boolean wrap) {
                            // 1、校验
                            checkDestroyed();
                            
                            // 2、参数为true,表明采用默认的实现类
                            // 2.1 我们上面SPI中的value参数,若指定tianjin,则采用tianjin的实现类
                            if ("true".equals(name)) {
                                return getDefaultExtension();
                            }
                            
                            String cacheKey = name;
                            if (!wrap) {
                                cacheKey += "_origin";
                            }
                            // 3、查看当前缓存中是否含有该实例
                            // 3.1 如果当前的cacheKey没有Holder的话,创建一个
                            final Holder holder = getOrCreateHolder(cacheKey);
                            
                            // 4、如果实例为空,采用DCL机制创建实例
                            Object instance = holder.get();
                            if (instance == null) {
                                synchronized (holder) {
                                    instance = holder.get();
                                    if (instance == null) {
                                        instance = createExtension(name, wrap);
                                        holder.set(instance);
                                    }
                                }
                            }
                            return (T) instance;
                        }
                        private Holder getOrCreateHolder(String name) {
                            // 1、获取当前name的Holder
                            Holder holder = cachedInstances.get(name);
                            // 2、没有则创建并扔进缓存
                            if (holder == null) {
                                cachedInstances.putIfAbsent(name, new Holder());
                                holder = cachedInstances.get(name);
                            }
                            // 3、返回
                            return holder;
                        }
                        

                        Holder 类是一个简单的容器类,用于保存某个对象的引用

                        在 Dubbo 的 ExtensionLoader 类中,Holder 类被用于实现对 SPI 扩展实现类的缓存

                        Holder 结构如下:

                        public class Holder {
                            private volatile T value;
                            public void set(T value) {
                                this.value = value;
                            }
                            public T get() {
                                return value;
                            }
                        }
                        

                        我们创建实例一共有以下几部分:

                        • 解析文件配置得到对应的类
                        • 通过实例化创建相关的类
                        • 初始化之前前置操作
                        • 依赖注入
                        • 初始化之后后置操作
                        • Wrapper 的包装
                        • 是否具有生命周期管理的能力

                          我们挨个的讲解

                          2.1 解析文件配置
                          Class clazz = getExtensionClasses().get(name);
                          private Map> classes = cachedClasses.get();
                              
                              // 2、DCL创建(经典的单例设计模式)
                              if (classes == null) {
                                  synchronized (cachedClasses) {
                                      classes = cachedClasses.get();
                                      if (classes == null) {
                                          // 3、加载类信息并放至缓存中
                                          classes = loadExtensionClasses();
                                          cachedClasses.set(classes);
                                      }
                                  }
                              }
                              return classes;
                          }
                          private Map> extensionClasses = new HashMap();
                              for (LoadingStrategy strategy : strategies) {
                                  loadDirectory(extensionClasses, strategy, type.getName());
                          		
                                  if (this.type == ExtensionInjector.class) {
                                      loadDirectory(extensionClasses, strategy, ExtensionFactory.class.getName());
                                  }
                              }
                          	
                              // tianjin:"class com.msb.dubbo.provider.SPI.Dubbo.TianjinCity"
                              // beijing:"class com.msb.dubbo.provider.SPI.Dubbo.BeijingCity"
                              return extensionClasses;
                          }
                          
                          2.2 实例化创建
                          // 1、从缓存中获取
                          T instance = (T) extensionInstances.get(clazz);
                          if (instance == null) {
                              // 2、缓存为空则创建并放至缓存
                              extensionInstances.putIfAbsent(clazz, createExtensionInstance(clazz));
                              instance = (T) extensionInstances.get(clazz);
                          }
                          // 1、获取当前类的所有的构造方法
                          // 2、判断是否有符合的构造方法,若没有则报错
                          // 3、有符合的构造犯法,返回即可
                          private Object createExtensionInstance(Class type) throws ReflectiveOperationException {
                              return instantiationStrategy.instantiate(type);
                          }
                          
                          2.3 前置处理

                          类似 Spirng 的前置处理器,之前也说过,感兴趣的可以看一下,整体思路区别不大

                          instance = postProcessBeforeInitialization(instance, name);
                          private T postProcessBeforeInitialization(T instance, String name) throws Exception {
                              if (extensionPostProcessors != null) {
                                  for (ExtensionPostProcessor processor : extensionPostProcessors) {
                                      instance = (T) processor.postProcessBeforeInitialization(instance, name);
                                  }
                              }
                              return instance;
                          }
                          
                          2.4 依赖注入
                          • 首先,如果依赖注入器为 null,则直接返回传入的实例。
                          • 然后,遍历传入实例的所有方法,找到所有的 setter 方法。
                          • 对于每个 setter 方法,如果标注了 @DisableInject 注解,则跳过该方法,不进行注入。
                          • 如果 setter 方法的参数类型是基本类型,则跳过该方法,不进行注入。
                          • 如果 setter 方法的参数类型不是基本类型,则尝试从依赖注入器中获取该类型对应的实例,并调用该 setter 方法进行注入。
                          • 如果获取实例失败,则记录错误日志。
                          • 最后,返回注入后的实例。
                            injectExtension(instance);
                            private T injectExtension(T instance) {
                                for (Method method : instance.getClass().getMethods()) {
                                    // 1、如果不是setter方法,直接跳过
                                    if (!isSetter(method)) {
                                        continue;
                                    }
                                    
                                    // 2、包含了DisableInject注解,直接跳过
                                    if (method.isAnnotationPresent(DisableInject.class)) {
                                            continue;
                                        }
                                        
                                        if (method.getDeclaringClass() == ScopeModelAware.class) {
                                            continue;
                                        }
                                    	
                                    	// 3、如果是基本数据类型,跳过
                                        if (instance instanceof ScopeModelAware || instance instanceof ExtensionAccessorAware) {
                                            if (ignoredInjectMethodsDesc.contains(ReflectUtils.getDesc(method))) {
                                                continue;
                                            }
                                        }
                                        Class pt = method.getParameterTypes()[0];
                                        if (ReflectUtils.isPrimitives(pt)) {
                                            continue;
                                        }
                                        try {
                                            // 4、依赖注入器中获取该类型对应的实例,并调用该 setter 方法进行注入
                                            // 4.1 这里直接拿取的ListableBeanFactory->DefaultListableBeanFactory
                                            String property = getSetterProperty(method);
                                            Object object = injector.getInstance(pt, property);
                                            
                                            // 5、将当前的对象注入到实例里面
                                            if (object != null) {
                                                method.invoke(instance, object);
                                            }
                                        }
                                return instance;
                            }
                            
                            2.5 后置操作
                            • 类似 Spirng 的后置处理器,之前也说过,感兴趣的可以看一下,整体思路区别不大
                              instance = postProcessAfterInitialization(instance, name);
                              private T postProcessAfterInitialization(T instance, String name) throws Exception {
                                  if (instance instanceof ExtensionAccessorAware) {
                                      ((ExtensionAccessorAware) instance).setExtensionAccessor(extensionDirector);
                                  }
                                  if (extensionPostProcessors != null) {
                                      for (ExtensionPostProcessor processor : extensionPostProcessors) {
                                          instance = (T) processor.postProcessAfterInitialization(instance, name);
                                      }
                                  }
                                  return instance;
                              }
                              
                              2.6 Wrapper 的包装
                              2.6.1 Wrapper缓存

                              在讲该部分之前,我们先来看 cachedWrapperClasses 这个缓存的来历:

                              在我们上面解析文件配置时,会进行 loadClass,这里不仅会解析正常的类,也会解析 Wrapper 类,方便后面的包装

                              private void loadClass(Map clazz, String name,boolean overridden) {
                                  if (isWrapperClass(clazz)) {
                                      cacheWrapperClass(clazz);
                                  }
                              }
                              

                              从这里我们可以看到,最关键的当属判断当前的 Class 是不是属于 WrapperClass

                              protected boolean isWrapperClass(Class clazz) {
                                  // 1、获取构造方法
                                  Constructor[] constructors = clazz.getConstructors();
                                  // 2、从构造方法中取出参数为 1 且类型等于当前接口的
                                  for (Constructor constructor : constructors) {
                                      if (constructor.getParameterTypes().length == 1 && constructor.getParameterTypes()[0] == type) {
                                          return true;
                                      }
                                  }
                                  return false;
                              }
                              

                              而具体的实现如下:

                              public class CityWrapper implements City{
                                  private City city;
                                  // 怎样判断扩展点还是aop切面呢?
                                  // 通过是否有这样的一个构造方法来判断
                                  public CityWrapper(City city) {
                                      this.city = city;
                                  }
                                  @Override
                                  public String getCityName() {
                                      return "文明城市" + city.getCityName();
                                  }
                              }
                              

                              了解这个之后,我们再来看看 Dubbo 如何处理这些类似 AOP 的包装

                              2.6.2 Wrapper实现
                              if (wrap) {
                                  List

    免责声明:我们致力于保护作者版权,注重分享,被刊用文章因无法核实真实出处,未能及时与作者取得联系,或有版权异议的,请联系管理员,我们会立即处理! 部分文章是来自自研大数据AI进行生成,内容摘自(百度百科,百度知道,头条百科,中国民法典,刑法,牛津词典,新华词典,汉语词典,国家院校,科普平台)等数据,内容仅供学习参考,不准确地方联系删除处理! 图片声明:本站部分配图来自人工智能系统AI生成,觅知网授权图片,PxHere摄影无版权图库和百度,360,搜狗等多加搜索引擎自动关键词搜索配图,如有侵权的图片,请第一时间联系我们,邮箱:ciyunidc@ciyunshuju.com。本站只作为美观性配图使用,无任何非法侵犯第三方意图,一切解释权归图片著作权方,本站不承担任何责任。如有恶意碰瓷者,必当奉陪到底严惩不贷!

    目录[+]