Dubbo系列讲解之扩展点实现原理分析【2万字分享】

淩亂°似流年 2022-09-04 11:50 216阅读 0赞

在这里插入图片描述

Apache Dubbo 是一款微服务开发框架,它提供了 RPC通信 与 微服务治理 两大关键能力。这意味着,使用 Dubbo 开发的微服务,将具备相互之间的远程发现与通信能力, 同时利用 Dubbo 提供的丰富服务治理能力,可以实现诸如服务发现、负载均衡、流量调度等服务治理诉求。同时 Dubbo 是高度可扩展的,用户几乎可以在任意功能点去定制自己的实现,以改变框架的默认行为来满足自己的业务需求

  本文主要给大家讲解下Dubbo的扩展点原理。

一、SPI介绍

  JDK中的SPI(Service Provider Interface)提供了一种基于接口的扩展机制,主要实现步骤如下:

  • 定义一个接口作为一个标准。
  • 在实现扩展点的工程中创建一个META-INF/services目录,以定义的接口的全类名作为文件名创建一个文件名,将实现类的全类名写入到文件中。
  • 在需要使用扩展点的地方调用java.util.ServiceLoader#load(java.lang.Class)方法,传入接口的全类名,返回java.util.ServiceLoader,ServiceLoader是一个Iterable的实现类,可以通过迭代器获取到所有的扩展,进行执行。

具体不清楚的可以参考下我的另一篇专门介绍SPI的文章:
Java SPI内容详解 学习交流 463257262

二、Dubbo扩展详解

1.Dubbo中的扩展点的增强

  在Dubbo中的扩展点主要是对JDK的扩展点思想做了增强,主要增强了一下功能:

  • 全类名文件中的内容通过key-value的规范书写,加载时也是K-V的存储方式,增加扩展点查找的灵活性
  • JDK中的扩展点的加载会一次性的将所有的扩展点加载到内存中,如果有些扩展点没用,但是改扩展点初始化很耗时,JDK也会将所有的扩展点加载到内存中,这些会造成一些浪费,而Dubbo中的扩展点会按需进行加载(加载时传入扩展点的name,这也是需要依赖于文件的K-V格式)
  • Dubbo增加了对扩展点 IoC 和 AOP 的支持,一个扩展点可以直接 setter 注入其它扩展点。同时在对扩展点进行依赖注入时,也会通过扫描到的Wrapper对扩展点实现进行包装。

2.Dubbo中扩展点的使用方式

  • 定义一个接口,在接口上标注一个@SPI,标识这是一个扩展点
  • 在扩展点实现工程中创建文件:/META-INF/dubbo/扩展点全类名
  • 在文件中定义扩展点实现的k-v格式数据

    helloService=com.bobo.spring.cloud.alibaba.consumer.spi.impl.HelloServiceImpl

  • 调用如下代码获取扩展的实现进行调用

    HelloService HelloService = ExtensionLoader

    1. .getExtensionLoader(HelloService.class)
    2. .getExtension("helloService");

    System.out.println(HelloService.sayHello(“wangxing”));

3.Dubbo扩展点源码分析

  在Dubbo中存在了以下三种类型的扩展点(Q群:463257262):

  1. 指定名称扩展点
  2. 自适应扩展点
  3. 激活扩展点

3.1 自适应扩展点源码分析

  在Dubbo中,通过在接口上标注【@SPI】标识该接口是一个扩展点,同时在其扩展点实现类或方法上,如果存在【@Adaptive】注解,则表示该类或方法是一个自适应的扩展点。标注在类上时,表示该扩展类是默认的自适应扩展点,标注在方法上时,表示该方法是自适应扩展点,将会重写该方法,使得Dubbo能够在运行时获取到具体的扩展点。加下来就进入源码的分析吧…

  Dubbo的扩展点的入口如下:

  1. HelloService helloService2 = ExtensionLoader
  2. .getExtensionLoader(HelloService.class)
  3. .getAdaptiveExtension();

  首先进入到getExtensionLoader()方法

  1. public static <T> ExtensionLoader<T> getExtensionLoader(Class<T> type) {
  2. if (type == null) {
  3. throw new IllegalArgumentException("Extension type == null");
  4. }
  5. if (!type.isInterface()) {
  6. throw new IllegalArgumentException("Extension type (" + type + ") is not an interface!");
  7. }
  8. if (!withExtensionAnnotation(type)) {
  9. throw new IllegalArgumentException("Extension type (" + type +
  10. ") is not an extension, because it is NOT annotated with @" + SPI.class.getSimpleName() + "!");
  11. }
  12. ExtensionLoader<T> loader = (ExtensionLoader<T>) EXTENSION_LOADERS.get(type);
  13. if (loader == null) {
  14. EXTENSION_LOADERS.putIfAbsent(type, new ExtensionLoader<T>(type));
  15. loader = (ExtensionLoader<T>) EXTENSION_LOADERS.get(type);
  16. }
  17. return loader;
  18. }

  该方法主要做了以下几件事

  • 对传入的的扩展点进行判断
    空值判断
    是否是接口判断
    是否标识了@SPI注解的判断,这也印证了前面我们说的只有标识了@SPI注解的接口Dubbo才会认为它是个扩展点接口
  • 根据类型从EXTENSION_LOADERS缓存中获取ExtensionLoader,获取到就直接返回ExtensionLoader。(EXTENSION_LOADERS是一个CurrentHashMap集合,key为扩展点接口的.class对象,value为该扩展点对应的ExtensionLoader)
  • 如果缓存中未获取到的ExtensionLoader,以扩展点.class对象为key,创建一个ExtensionLoader对象为value存储到EXTENSION_LOADERS中,返回创建的ExtensionLoader。到此就可以获取到一个ExtensionLoader了,通过返回的ExtensionLoader对象可以获得对应的扩展点的实现对象

接下来进入ExtensionLoader类中的构造方法,看看ExtensionLoader实例化时做了什么

  1. private ExtensionLoader(Class<?> type) {
  2. this.type = type;
  3. objectFactory = (type == ExtensionFactory.class ? null : ExtensionLoader.getExtensionLoader(ExtensionFactory.class).getAdaptiveExtension());
  4. }

  在构造器中为type属性复制传入的扩展点的.class对象。同时通过自适应扩展点的方式获取到了一个ExtensionFactory的扩展点的实现,赋值给objectFactory。这里先不详细说明会具体获取到哪个实现,本节分析完成再回过来看。

  通过以上的步骤,一个初始化完成的ExtensionLoader对象已经被获取到了,分析getAdaptiveExtension()获取自适应扩展点实现的流程

  进入getAdaptiveExtension()

  1. public T getAdaptiveExtension() {
  2. Object instance = cachedAdaptiveInstance.get();
  3. if (instance == null) {
  4. // 如果获取到的实例为null,切缓存的错误不能null,抛出异常
  5. if (createAdaptiveInstanceError != null) {
  6. throw new IllegalStateException("Failed to create adaptive instance: " +
  7. createAdaptiveInstanceError.toString(),
  8. createAdaptiveInstanceError);
  9. }
  10. synchronized (cachedAdaptiveInstance) {
  11. instance = cachedAdaptiveInstance.get();
  12. if (instance == null) {
  13. try {
  14. instance = createAdaptiveExtension();
  15. cachedAdaptiveInstance.set(instance);
  16. } catch (Throwable t) {
  17. // 异常信息缓存起来,下一次进来时如果发现是创建实例是出现异常,就直接抛出异常。这里的设计应该是当扩展点创建异常时避免多次执行创建流程的优化
  18. createAdaptiveInstanceError = t;
  19. throw new IllegalStateException("Failed to create adaptive instance: " + t.toString(), t);
  20. }
  21. }
  22. }
  23. }
  24. return (T) instance;
  25. }

该方法主要做了以下几件事

  • 从缓存cachedAdaptiveInstance中获取扩展点实现,存在该扩展点对象,则直接返回该实例。cachedAdaptiveInstance是一个Holder对象,主要用于缓存该扩展点的实现的具体实例,因为这里只会返回一个自适应扩展点的实现(有多个实现类标注了则按文件定义顺序取最后一个),实现对于每个``ExtensionLoader`来说,自适应扩展点是单例的。
  • 如果扩展点实现不存在,调用createAdaptiveExtension()创建一个具体的实现,并将该实例set到cachedAdaptiveInstance中缓存起来。

创建扩展点实现的具体流程是在createAdaptiveExtension方法中

  1. private Class<?> getAdaptiveExtensionClass() {
  2. getExtensionClasses();
  3. if (cachedAdaptiveClass != null) {
  4. return cachedAdaptiveClass;
  5. }
  6. return cachedAdaptiveClass = createAdaptiveExtensionClass();
  7. }

  该方法主要做了以下几件事

  • 调用getExtensionClasses(),顾名思义,该方法主要是获取扩展点的所有实现的.class对象。
  • 如果缓存的cachedAdaptiveClass 对象不为null,直接返回。(cachedAdaptiveClass是一个class对象,用于保存该扩展点的自适应扩展点的实现,即是该扩展点的实现类中存在有将@Adaptive标注在类上的默认自适应扩展点)
  • 如果为缓存有cachedAdaptiveClass对象,则调用createAdaptiveExtensionClass创建一个cachedAdaptiveClass,并复制给cachedAdaptiveClass

接下来首先进入getExtensionClasses()方法

  1. private Map<String, Class<?>> getExtensionClasses() {
  2. Map<String, Class<?>> classes = cachedClasses.get();
  3. if (classes == null) {
  4. synchronized (cachedClasses) {
  5. classes = cachedClasses.get();
  6. if (classes == null) {
  7. classes = loadExtensionClasses();
  8. cachedClasses.set(classes);
  9. }
  10. }
  11. }
  12. return classes;
  13. }

  该方法首先会从cachedClasses(cachedClasses也是一个holder,用于存储每个扩展点的所有扩展实现的map集合)获取该.class对象,存在则直接返回,否则调用loadExtensionClasses方法加载扩展点的classs,将加载到的classes存到cachedClasses中。
接下来进入loadExtensionClasses

  1. private Map<String, Class<?>> loadExtensionClasses() {
  2. cacheDefaultExtensionName();
  3. Map<String, Class<?>> extensionClasses = new HashMap<>();
  4. for (LoadingStrategy strategy : strategies) {
  5. // 扫描每个加载策略目录中的扩展点实现
  6. loadDirectory(extensionClasses, strategy.directory(), type.getName(), strategy.preferExtensionClassLoader(), strategy.overridden(), strategy.excludedPackages());
  7. // 对alibaba的践行兼容
  8. loadDirectory(extensionClasses, strategy.directory(), type.getName().replace("org.apache", "com.alibaba"), strategy.preferExtensionClassLoader(), strategy.overridden(), strategy.excludedPackages());
  9. }
  10. return extensionClasses;
  11. }

  首先调用cacheDefaultExtensionName()方法。再给接口标注@SPI的时候,可以给个默认的value值,表示指定的默认的扩展点实现,如@SPI(“dubbo”)public interface Protocol 表示默认的扩展点为扩展点实现名为dubbo的实现。而cacheDefaultExtensionName方法就是通过注解获取到该扩展点的默认扩展点name,赋值给cachedDefaultName

  遍历strategies,获取到多个LoadingStrategy,通过stratery.directory()获取到需要扫描的目录,以下是Dubbo中默认的三种策略的实现

  • DubboInternalLoadingStrategy —> META-INF/dubbo/internal/
  • DubboLoadingStrategy —>META-INF/dubbo/
  • ServicesLoadingStrategy —> META-INF/services/

  在Dubbo中,创建ExtensionLoader对象时,会load到所有的LoadingStrategy,这里利用的是JDK原生的SPI的方式,将LoadingStrategy的所有扩展实现都加载进来,保存到strategies中。所以如果需要扩展Dubbo中的扫描的路径,按照JDK的原生方式进行扩展即可

  进入到loadDirectory方法

  1. private void loadDirectory(Map<String, Class<?>> extensionClasses, String dir, String type,
  2. boolean extensionLoaderClassLoaderFirst, boolean overridden, String... excludedPackages) {
  3. String fileName = dir + type;
  4. try {
  5. Enumeration<java.net.URL> urls = null;
  6. ClassLoader classLoader = findClassLoader();
  7. // try to load from ExtensionLoader's ClassLoader first
  8. if (extensionLoaderClassLoaderFirst) {
  9. ClassLoader extensionLoaderClassLoader = ExtensionLoader.class.getClassLoader();
  10. if (ClassLoader.getSystemClassLoader() != extensionLoaderClassLoader) {
  11. urls = extensionLoaderClassLoader.getResources(fileName);
  12. }
  13. }
  14. if (urls == null || !urls.hasMoreElements()) {
  15. if (classLoader != null) {
  16. urls = classLoader.getResources(fileName);
  17. } else {
  18. urls = ClassLoader.getSystemResources(fileName);
  19. }
  20. }
  21. if (urls != null) {
  22. while (urls.hasMoreElements()) {
  23. java.net.URL resourceURL = urls.nextElement();
  24. loadResource(extensionClasses, classLoader, resourceURL, overridden, excludedPackages);
  25. }
  26. }
  27. } catch (Throwable t) {
  28. logger.error("Exception occurred when loading extension class (interface: " +
  29. type + ", description file: " + fileName + ").", t);
  30. }
  31. }

  该方法首先扫描传入路径下的所有的以type全类名命名的文件,获取到资源,将获取到的文件转换成Resource,传入到loadResource()方法中

  1. private void loadResource(Map<String, Class<?>> extensionClasses, ClassLoader classLoader,
  2. java.net.URL resourceURL, boolean overridden, String... excludedPackages) {
  3. try {
  4. try (BufferedReader reader = new BufferedReader(new InputStreamReader(resourceURL.openStream(), StandardCharsets.UTF_8))) {
  5. String line;
  6. // 按行读取文件中的每行数据
  7. while ((line = reader.readLine()) != null) {
  8. final int ci = line.indexOf('#');
  9. if (ci >= 0) {
  10. line = line.substring(0, ci);
  11. }
  12. line = line.trim();
  13. if (line.length() > 0) {
  14. try {
  15. String name = null;
  16. // 通过等号分割,等号前的为key(扩展点name),等号后的为类的全类名
  17. int i = line.indexOf('=');
  18. if (i > 0) {
  19. name = line.substring(0, i).trim();
  20. line = line.substring(i + 1).trim();
  21. }
  22. if (line.length() > 0 && !isExcluded(line, excludedPackages)) {
  23. loadClass(extensionClasses, resourceURL, Class.forName(line, true, classLoader), name, overridden);
  24. }
  25. } catch (Throwable t) {
  26. IllegalStateException e = new IllegalStateException("Failed to load extension class (interface: " + type + ", class line: " + line + ") in " + resourceURL + ", cause: " + t.getMessage(), t);
  27. exceptions.put(line, e);
  28. }
  29. }
  30. }
  31. }
  32. } catch (Throwable t) {
  33. logger.error("Exception occurred when loading extension class (interface: " +
  34. type + ", class file: " + resourceURL + ") in " + resourceURL, t);
  35. }
  36. }

  首先按行读取文件,对读取到的每一行数据通过=号进行分割,=号前为扩展点的名字,=号后的为扩展点的具体扩展的实现类的全类名

  通过Class.forName将实现类加载到内存中。传入到loadClass()方法

  1. private void loadClass(Map<String, Class<?>> extensionClasses, java.net.URL resourceURL, Class<?> clazz, String name,
  2. boolean overridden) throws NoSuchMethodException {
  3. if (!type.isAssignableFrom(clazz)) {
  4. throw new IllegalStateException("Error occurred when loading extension class (interface: " +
  5. type + ", class line: " + clazz.getName() + "), class "
  6. + clazz.getName() + " is not subtype of interface.");
  7. }
  8. if (clazz.isAnnotationPresent(Adaptive.class)) {
  9. // 会覆盖掉之前保存的`cachedAdaptiveClass`
  10. cacheAdaptiveClass(clazz, overridden);
  11. } else if (isWrapperClass(clazz)) {
  12. cacheWrapperClass(clazz);
  13. } else {
  14. clazz.getConstructor();
  15. if (StringUtils.isEmpty(name)) {
  16. // 如果扩展文件中的name为空,则调用findAnnotationName方法获取扩展点名字,具体命名方式这里就不详细看了
  17. name = findAnnotationName(clazz);
  18. if (name.length() == 0) {
  19. throw new IllegalStateException("No such extension name for the class " + clazz.getName() + " in the config " + resourceURL);
  20. }
  21. }
  22. String[] names = NAME_SEPARATOR.split(name);
  23. if (ArrayUtils.isNotEmpty(names)) {
  24. cacheActivateClass(clazz, names[0]);
  25. for (String n : names) {
  26. cacheName(clazz, n);
  27. saveInExtensionClass(extensionClasses, clazz, n, overridden);
  28. }
  29. }
  30. }
  31. }

  该方法主要主要做了以下几件事

  • 判断该对象是否实现了扩展点接口,未实现则抛出异常
  • 判断给实例是否是自适应扩展点,是,调用cacheAdaptiveClass方法将该扩展点复制到cachedAdaptiveClass成员变量中。
  • 判断该实例是否是扩展点的wrapper,是则调用cachedWrapperClasses方法将该实例保存到cachedWrapperClasses中。扩展点实现是否是wrapper的判断条件为该实现类中存在一个以扩展点为入参的构造方法时。
  • 对name进行分割,获取到单个扩展点名字,检查是否是扩展点,是,则将该实例存储到cachedActivates中
  • 缓存扩展点的名字,存储到cachedNames中,以扩展点具体实现类的.class为key,扩展点name为value
  • 调用saveInExtensionClass方法,将扩展点名字及其实现的.class保存到extensionClasses()集合中。

  到这里,该扩展点在项目中的所有实现将被加载完成,且已经区分出了实现中,自适应扩展点,wrapper等不同类型的实现。然后我们回到

  再次回到getAdaptiveExtensionClass()方法,当执行完getExtensionClasses();方法之后,如果cacheAdaptiveClass为null,表示该扩展点没有默认的自适应扩展点,此时扩展点需要将需要自适应扩展的方法上标注@Adaptive(),并且该方法中需要传入URL对象,因为Dubbo中需要将都是通过URL来携带配置的。

  将调用createAdaptiveExtensionClass()方法动态创建一个自适应扩展点

  1. private Class<?> createAdaptiveExtensionClass() {
  2. String code = new AdaptiveClassCodeGenerator(type, cachedDefaultName).generate();
  3. ClassLoader classLoader = findClassLoader();
  4. org.apache.dubbo.common.compiler.Compiler compiler = ExtensionLoader.getExtensionLoader(org.apache.dubbo.common.compiler.Compiler.class).getAdaptiveExtension();
  5. return compiler.compile(code, classLoader);
  6. }

  该方法会动态生成一个自适应扩展点的类,然后编译通过编译器编译,加载其.class文件到内存中。返回该动态类的.class对象。

  生成的动态类代码如下:

  1. import org.apache.dubbo.common.extension.ExtensionLoader;
  2. public class HelloService$Adaptive implements com.wangx.spring.cloud.alibaba.consumer.spi.HelloService {
  3. public java.lang.String sayHello(org.apache.dubbo.common.URL arg0, java.lang.String arg1) {
  4. if (arg0 == null) throw new IllegalArgumentException("url == null");
  5. org.apache.dubbo.common.URL url = arg0;
  6. // 默认类名分割。可以在@Adaptive注解中指定该参数的名称,default为SPI上定义的默认扩展点实现
  7. String extName = url.getParameter("hello.service","default");
  8. if (extName == null)
  9. throw new IllegalStateException("Failed to get extension (com.wangx.spring.cloud.alibaba.consumer.spi.HelloService) name from url (" + url.toString() + ") use keys([hello.service])");
  10. // 根据名称获取到该扩展点类型的扩展实现
  11. com.wangx.spring.cloud.alibaba.consumer.spi.HelloService extension = (com.wangx.spring.cloud.alibaba.consumer.spi.HelloService) ExtensionLoader.getExtensionLoader(com.wangx.spring.cloud.alibaba.consumer.spi.HelloService.class).getExtension(extName);
  12. return extension.sayHello(arg0, arg1);
  13. }
  14. }

  该类实现了 扩展点接口,重写了扩展点中的自适应扩展方法。该方法体现的是,在运行时通过传入的URL的信息动态的获取处理当前URL时的扩展点的实现。

  到这里就可以返回各种情况下的自适应扩展点的.class对象了,接下来再次回到createAdaptiveExtension方法中,通过以上的一系列操作,我们已经获取到了自适应扩展点的.class对象,并调用反射创建一个扩展点实现的对象。然后调用injectExtension进行依赖注入

  1. private T injectExtension(T instance) {
  2. if (objectFactory == null) {
  3. return instance;
  4. }
  5. try {
  6. for (Method method : instance.getClass().getMethods()) {
  7. if (!isSetter(method)) {
  8. continue;
  9. }
  10. /** * Check {@link DisableInject} to see if we need auto injection for this property */
  11. if (method.getAnnotation(DisableInject.class) != null) {
  12. continue;
  13. }
  14. Class<?> pt = method.getParameterTypes()[0];
  15. if (ReflectUtils.isPrimitives(pt)) {
  16. continue;
  17. }
  18. try {
  19. String property = getSetterProperty(method);
  20. Object object = objectFactory.getExtension(pt, property);
  21. if (object != null) {
  22. method.invoke(instance, object);
  23. }
  24. } catch (Exception e) {
  25. logger.error("Failed to inject via method " + method.getName()
  26. + " of interface " + type.getName() + ": " + e.getMessage(), e);
  27. }
  28. }
  29. } catch (Exception e) {
  30. logger.error(e.getMessage(), e);
  31. }
  32. return instance;
  33. }

  在injectExtension方法中,有如下几个操作:

  • 判断是否存在objectFactory,为null,则直接返回实例对象
  • 遍历该对象的所有方法,过滤掉不是setter方法及被标注了@DisableInject注解的方法,过滤表setter方法参数类型为特定类型及原生类型的方法,setter方法的参数就是需要被注入的对象。
  • 根据setter方法获取被依赖注入的属性名称,然后通过 objectFactory.getExtension(pt, property);获取到被注入对象实例,执行setter方法进行依赖注入。

  在初始化ExtensionLoader对象时,objectFactory是通过如下代码获取的

  1. objectFactory = (type == ExtensionFactory.class ? null : ExtensionLoader.getExtensionLoader(ExtensionFactory.class).getAdaptiveExtension());

  通过上面的一些列分析,现在已经可以知道这是一个ExtensionFactory的自适应的扩展点,根据我们自适应扩展点的获取方式,我们可以推断出该扩展点的自适应扩展点的实现。

  在ExtensionFactory的所有子类实现中,我们找到了AdaptiveExtensionFactory类,该类上标注了@Adaptive,所以可以推断出objectFacotory的指向的就是AdaptiveExtensionFactory类的对象。

  下面来看看AdaptiveExtensionFactory类中的实现:

  1. @Adaptive
  2. public class AdaptiveExtensionFactory implements ExtensionFactory {
  3. private final List<ExtensionFactory> factories;
  4. public AdaptiveExtensionFactory() {
  5. ExtensionLoader<ExtensionFactory> loader = ExtensionLoader.getExtensionLoader(ExtensionFactory.class);
  6. List<ExtensionFactory> list = new ArrayList<ExtensionFactory>();
  7. for (String name : loader.getSupportedExtensions()) {
  8. list.add(loader.getExtension(name));
  9. }
  10. factories = Collections.unmodifiableList(list);
  11. }
  12. @Override
  13. public <T> T getExtension(Class<T> type, String name) {
  14. for (ExtensionFactory factory : factories) {
  15. T extension = factory.getExtension(type, name);
  16. if (extension != null) {
  17. return extension;
  18. }
  19. }
  20. return null;
  21. }
  22. }

  在AdaptiveExtensionFactory的构造函数中,会先获取到ExtensionFactory类型的ExtensionLoader,然后通过调用loader.getSupportedExtensions()获取到ExtensionFactory的所有扩展实现的名字。通过loader.getExtension(name)根据名称获取到所有扩展点,存储到factories中。

  在injectExtension中调用getExtension()方法时,将会遍历初始化时获取到的所有的ExtensionFactory的扩展点。只要在其中的一个扩展到那点中找到该扩展对象的实例,则直接返回。传入的对象type和name,获取Dubbo环境下可能存在的扩展点。

  在injectExtension方法中返回的依赖注入完成的对象,即是我们需要获取的自适应扩展点对象

3.2 根据名称获取扩展点

  根据名称获取扩展点,顾名思义,就是根据扩展点的名称,获取到扩展点对应的实现。这种方式在Dubbo中也被广泛应用到,主要是可以通过URL中的参数或协议作为name,在运行时根据URl动态的获取到不同方式的实现。比如获取负载均衡器等

入口如下:

  1. HelloService HelloService = ExtensionLoader.getExtensionLoader(HelloService.class).getExtension("helloService");

getExtensionLoader方法在上述中已经解释清楚了,现在直接进入到getExtension方法中

  1. public T getExtension(String name) {
  2. //判断传入名称是否为null
  3. if (StringUtils.isEmpty(name)) {
  4. throw new IllegalArgumentException("Extension name == null");
  5. }
  6. // 如果为true,获取默认的扩展点,在getDefaultExtension方法中会调用getExtensionClasses->loadExtensionClasses方法,该方法中的cacheDefaultExtensionName会将默认扩展点的name赋值到cachedDefaultName中,所以当调用getDefaultExtension()即可获得默认的扩展点实现
  7. if ("true".equals(name)) {
  8. return getDefaultExtension();
  9. }
  10. final Holder<Object> holder = getOrCreateHolder(name);
  11. Object instance = holder.get();
  12. if (instance == null) {
  13. synchronized (holder) {
  14. instance = holder.get();
  15. if (instance == null) {
  16. instance = createExtension(name);
  17. holder.set(instance);
  18. }
  19. }
  20. }
  21. return (T) instance;
  22. }

  该方法主要做了以下几个操作

  • 如果传入的name为true,则获取该扩展点的默认扩展点
  • 获取或新建一个Holder,这里跟自适应扩展点不同的是,自适应扩展点的只有一个实现会被保存,而通过名称获取扩展点时,需要将每个name对应的扩展点实现包装的holder放存储到cachedInstances中,cachedInstances是一个map集合,保存了name对应的扩展的实现。如果不存在该holder,则新建一个holder对象返回,获取holder保存的instance对象,如果不存在,则直接利用双重检查锁创建一个单例的instance保存到holder中。

  创建instance时,需要调用createExtension(name)方法

  1. @SuppressWarnings("unchecked")
  2. private T createExtension(String name) {
  3. Class<?> clazz = getExtensionClasses().get(name);
  4. if (clazz == null) {
  5. throw findException(name);
  6. }
  7. try {
  8. T instance = (T) EXTENSION_INSTANCES.get(clazz);
  9. if (instance == null) {
  10. EXTENSION_INSTANCES.putIfAbsent(clazz, clazz.newInstance());
  11. instance = (T) EXTENSION_INSTANCES.get(clazz);
  12. }
  13. injectExtension(instance);
  14. Set<Class<?>> wrapperClasses = cachedWrapperClasses;
  15. if (CollectionUtils.isNotEmpty(wrapperClasses)) {
  16. for (Class<?> wrapperClass : wrapperClasses) {
  17. instance = injectExtension((T) wrapperClass.getConstructor(type).newInstance(instance));
  18. }
  19. }
  20. initExtension(instance);
  21. return instance;
  22. } catch (Throwable t) {
  23. throw new IllegalStateException("Extension instance (name: " + name + ", class: " +
  24. type + ") couldn't be instantiated: " + t.getMessage(), t);
  25. }
  26. }

  通过自适应扩展点的分析,我们已经知道了getExtensionClasses()方法返回的是扩展点name为key,value为具体扩展点实现类的class对象。然后可以通过name获取到扩展点对应的class对象。然后通过该class对象,到EXTENSION_INSTANCES缓存中获取实现类的对象,如果不存在,则直接通过反射创建一个对象。

  接下来就跟自适应扩展点一样,调用injectExtension进行依赖注入。依赖注入完成之后,将对该对象进行包装,首先从加载时装载的cachedWrapperClasses缓存中获取所有的wrapper扩展点,遍历所有的装饰器,将创建的实际的扩展点对象通过构造器传入到wrapper中,反射创建出一个wrapper对象,在对该wrapper对象进行依赖注入,完成之后将该对象复制给instance。

  比如现在有一个扩展点S,扩展点实现F,该扩展点有A,B,C三个包装器,那么通过如上遍历包装器之后,最后的到的instance对象的结构可能经过了层层的嵌套,变成了这个样子:A(B(C(F)))。调用instance时,将会从最外层开始执行该对象的方法,最终到最里层才会执行实际扩展点的方法。这种设计使得包装的实现更加简洁和灵活。

  然后调用initExtension方法,如果该对象实现了Lifecycle接口,则调用initialize方法。最后返回一个被包装的对象

3.3 激活扩展点

  激活扩展点的使用也需要在实现类上标注@Activate注解。注解中可以指定group和value,当不指定时,就无条件激活,否则就按照指定的条件进行激活,激活扩展点的入口为:

  1. List<HelloServiceActive> li = ExtensionLoader.getExtensionLoader(HelloServiceActive.class).getActivateExtension(url,"helloService2");
  2. for (HelloServiceActive helloServiceActive : li) {
  3. helloServiceActive.say();
  4. }

  需要传入一个URL对象和一个需要获取激活的的扩展点的参数看key,比如在参数中设置url = url.addParameter(“xing”,“xing”);传入的key为xing。进入getActivateExtension()方法,因为可以指定group等,所以最终调用的方法为

  1. public List<T> getActivateExtension(URL url, String[] values, String group) {
  2. List<T> activateExtensions = new ArrayList<>();
  3. List<String> names = values == null ? new ArrayList<>(0) : asList(values);
  4. if (!names.contains(REMOVE_VALUE_PREFIX + DEFAULT_KEY)) {
  5. getExtensionClasses();
  6. for (Map.Entry<String, Object> entry : cachedActivates.entrySet()) {
  7. String name = entry.getKey();
  8. Object activate = entry.getValue();
  9. String[] activateGroup, activateValue;
  10. if (activate instanceof Activate) {
  11. activateGroup = ((Activate) activate).group();
  12. activateValue = ((Activate) activate).value();
  13. } else if (activate instanceof com.alibaba.dubbo.common.extension.Activate) {
  14. activateGroup = ((com.alibaba.dubbo.common.extension.Activate) activate).group();
  15. activateValue = ((com.alibaba.dubbo.common.extension.Activate) activate).value();
  16. } else {
  17. continue;
  18. }
  19. if (isMatchGroup(group, activateGroup)
  20. && !names.contains(name)
  21. && !names.contains(REMOVE_VALUE_PREFIX + name)
  22. && isActive(activateValue, url)) {
  23. activateExtensions.add(getExtension(name));
  24. }
  25. }
  26. activateExtensions.sort(ActivateComparator.COMPARATOR);
  27. }
  28. List<T> loadedExtensions = new ArrayList<>();
  29. for (int i = 0; i < names.size(); i++) {
  30. String name = names.get(i);
  31. if (!name.startsWith(REMOVE_VALUE_PREFIX)
  32. && !names.contains(REMOVE_VALUE_PREFIX + name)) {
  33. if (DEFAULT_KEY.equals(name)) {
  34. if (!loadedExtensions.isEmpty()) {
  35. activateExtensions.addAll(0, loadedExtensions);
  36. loadedExtensions.clear();
  37. }
  38. } else {
  39. loadedExtensions.add(getExtension(name));
  40. }
  41. }
  42. }
  43. if (!loadedExtensions.isEmpty()) {
  44. activateExtensions.addAll(loadedExtensions);
  45. }
  46. return activateExtensions;
  47. }

  经过我们对上面两种方式的分析,其实第三种方式我们已经能够很轻松的看懂了。主要流程有,先遍历扫描是装载在cachedActivates中的所有激活扩展点,跟url和group进行匹配,匹配成功,则通过扩展点的名name通过根据名称获取扩展点的方式获取扩展点,存储到list中。然后遍历根据key获取到的value解析出来的扩展点名称,通过该名称获取到扩展点装载到list中,然后返回activateExtensions();

  这样就完成了通过激活扩展点的获取。

发表评论

表情:
评论列表 (有 0 条评论,216人围观)

还没有评论,来说两句吧...

相关阅读

    相关 Dubbo扩展注解@Adaptive

    @Adaptive称为自适应扩展点注解。 在实际应用场景中,一个扩展接口往往会有多种实现类,因为Dubbo是基于URL驱动,所以在运行时,通过传入URL中的某些参数来动态控制