分析Dubbo中的扩展机制

在dubbo框架中,Extension扩展机制可谓是一大设计亮点,此扩展机制贯穿了dubbo源码,几乎很多地方都有用到,例如配合dubbo的URL机制去动态选择类,可以做到在配置中动态选择,比如在dubbo中的registry,可以选择zk,可以选择redis,只需要在配置文件中替换一下字符串即可达到注册中心的变更。所以了解dubbo中的扩展机制是很有必要的。

Dubbo SPI扩展机制

示例

首先先做一个demo来演示,什么叫SPI扩展机制。

首先定义一个接口,作为SPI的扩展机制接口:

//注意要打上SPI注解,表示这个是扩展点,在后面的源码分析也会提到
@SPI
public interface ITestService {
    String sayHello(String word);
}

做两个接口的实现类:

public class TestServiceImpl implements ITestService {
    @Override
    public String sayHello(String word) {
        return "hello " + word;
    }
}
public class Test2ServiceImpl implements ITestService {
    @Override
    public String sayHello(String word) {
        return "hello2 " + word;
    }
}

接着在resource目录下,创建一个文件夹叫META-INF/dubbo(文件夹路径有三个可选,在后面源码分析中会再次提到),在此目录下创建文件,文件名为扩展点接口的全类名
在这里插入图片描述
文件内容为各个扩展点的具体类全限定类名

test=org.apache.dubbo.demo.consumer.service.TestServiceImpl
test2=org.apache.dubbo.demo.consumer.service.Test2ServiceImpl

这样就大功告成了,写一个测试类测试一下:

public static void main(String[] args) {
  ITestService service = ExtensionLoader.getExtensionLoader(ITestService.class).getExtension("test");
  System.out.println(service.sayHello("world"));
  ITestService service2 = ExtensionLoader.getExtensionLoader(ITestService.class).getExtension("test2");
  System.out.println(service2.sayHello("world"));
}

控制台打印:
在这里插入图片描述
这就是dubbo中的SPI机制,接下来分析其源码了解底层做了什么

源码分析

从示例中我们可以知道,是从ExtensionLoader这样一个类似工厂的类调用getExtensionLoader的API,做了一个扩展点Extension,再从中调用getExtension,传入指定名字即可获取到指定的扩展点,由此作为一个源码入口来看:

public static <T> ExtensionLoader<T> getExtensionLoader(Class<T> type) {
 	  ...
    //查看是否有缓存此扩展点类型的Loader
    ExtensionLoader<T> loader = (ExtensionLoader<T>) EXTENSION_LOADERS.get(type);
  if (loader == null) {
    //new一个ExtensionLoader,传入Class对象作为构造器参数
    EXTENSION_LOADERS.putIfAbsent(type, new ExtensionLoader<T>(type));
    loader = (ExtensionLoader<T>) EXTENSION_LOADERS.get(type);
  }
  return loader;
}

这段代码没什么好讲的,只是new了一个对象,将扩展点类型Class对象作为参数传入对象中,作为ExtensionLoader的变量type而已。接下来看看ExtensionLoader.getExtension的逻辑:

扫描二维码关注公众号,回复: 8831004 查看本文章
public T getExtension(String name) {
  if (StringUtils.isEmpty(name)) {
    throw new IllegalArgumentException("Extension name == null");
  }
  //如果传入的name字符串为"true"
  if ("true".equals(name)) {
    //获取默认的Extension
    return getDefaultExtension();
  }
  //dubbo将实际扩展类封装在Holder对象里
  Holder<Object> holder = getOrCreateHolder(name);
  //查看缓存中是否有此实例
  Object instance = holder.get();
  if (instance == null) {
    //double-check,确保线程安全,接下来将会有许多这种操作,不多赘述
    synchronized (holder) {
      instance = holder.get();
      if (instance == null) {
        //创建一个扩展点,关键入口
        instance = createExtension(name);
        //放入Holder
        holder.set(instance);
      }
    }
  }
  return (T) instance;
}

这里需要关注的点是获取默认的扩展点和创建一个扩展点实例,首先先粗略讲解一下如何获取默认的扩展点,在之后会详细讲到,此时只需要记住cachedDefaultName这个变量:

public T getDefaultExtension() {
  //此方法为load所有扩展点的关键方法
  getExtensionClasses();
  //cachedDefaultName变量即为默认扩展点的名称
  if (StringUtils.isBlank(cachedDefaultName) || "true".equals(cachedDefaultName)) {
    return null;
  }
  //拿到默认扩展点名称后,再次获取一次
  return getExtension(cachedDefaultName);
}

这个方法主要是去拿到默认扩展点名称,然后类似递归一样再次去根据此名称获取扩展点类。那么是怎么获取到默认扩展点名称呢?在接下来的load所有扩展点的方法里,会判断@SPI中的value值,此值即为默认的扩展点名称,将会给cachedDefaultName赋值,所以这里可以拿到此名称并返回一个默认的扩展点类。所以扩展点接口上的@SPI注解里的值,都是一个默认的扩展点名称,可以通过调用api,获取默认扩展点,或者传入name=“true”。

接下来回到主线,createExtension创建扩展点方法:

private T createExtension(String name) {
  //和上面一样,load所有扩展点的方法
  //此方法返回一个Map,key:name,value:class
  Class<?> clazz = getExtensionClasses().get(name);
  if (clazz == null) {
    throw findException(name);
  }
  try {
    //缓存
    T instance = (T) EXTENSION_INSTANCES.get(clazz);
    if (instance == null) {
      //反射将其实例创建
      EXTENSION_INSTANCES.putIfAbsent(clazz, clazz.newInstance());
      instance = (T) EXTENSION_INSTANCES.get(clazz);
    }
    //IOC注入
    injectExtension(instance);
    Set<Class<?>> wrapperClasses = cachedWrapperClasses;
    //包装类装饰
    if (CollectionUtils.isNotEmpty(wrapperClasses)) {
      for (Class<?> wrapperClass : wrapperClasses) {
        instance = injectExtension((T) wrapperClass.getConstructor(type).newInstance(instance));
      }
    }
    return instance;
  } catch (Throwable t) {
    ...
  }
}

和之前默认扩展点创建一样,在开头都会调用getExtensionClasses方法去寻找所有的扩展点。

其中,有几个关键点分支,需要读者记住:

  1. IOC注入:dubbo中有一个ExtensionFactory类,就像一个IOC容器一样,injectExtension方法可以将扩展点中需要注入的依赖去ExtensionFactory中找到,并给扩展点赋值,关于注入依赖,会在下面讲解,这里主要关注如何找到并创建扩展点。
  2. 包装类装饰:这里需要读者记住cachedWrapperClasses这个变量,从代码中可以看出,如果这个变量不为空,则会遍历它,拿到具体Class对象的构造器,将我们刚刚反射创建的扩展点包装进去,并进行IOC注入依赖。这里读者有个印象即可,在下面单独开一个章节讲解。

这里还是关注我们的主线,getExtensionClasses寻找扩展点:

private Map<String, Class<?>> getExtensionClasses() {
  //缓存
  Map<String, Class<?>> classes = cachedClasses.get();
  if (classes == null) {
    synchronized (cachedClasses) {
      //double-check
      classes = cachedClasses.get();
      if (classes == null) {
        //关键方法,获取所有扩展点
        classes = loadExtensionClasses();
        cachedClasses.set(classes);
      }
    }
  }
  return classes;
}

进入关键方法loadExtensionClasses:

private Map<String, Class<?>> loadExtensionClasses() {
  //获取扩展点上的@SPI注解,缓存默认扩展点名称
  cacheDefaultExtensionName();

  //前面也有提到,所有扩展点都会存放在一个Map中返回
  Map<String, Class<?>> extensionClasses = new HashMap<>();
  //下面从3个文件夹中,加载扩展点
  loadDirectory(extensionClasses, DUBBO_INTERNAL_DIRECTORY, type.getName());
  loadDirectory(extensionClasses, DUBBO_INTERNAL_DIRECTORY, type.getName().replace("org.apache", "com.alibaba"));
  loadDirectory(extensionClasses, DUBBO_DIRECTORY, type.getName());
  loadDirectory(extensionClasses, DUBBO_DIRECTORY, type.getName().replace("org.apache", "com.alibaba"));
  loadDirectory(extensionClasses, SERVICES_DIRECTORY, type.getName());
  loadDirectory(extensionClasses, SERVICES_DIRECTORY, type.getName().replace("org.apache", "com.alibaba"));
  return extensionClasses;
}

我们先来看看cacheDefaultExtensionName方法做了什么:

private void cacheDefaultExtensionName() {
  //拿到SPI注解对象
  final SPI defaultAnnotation = type.getAnnotation(SPI.class);
  if (defaultAnnotation != null) {
    //拿到注解上的value
    String value = defaultAnnotation.value();
    if ((value = value.trim()).length() > 0) {
      String[] names = NAME_SEPARATOR.split(value);
      //这里我们可以看出来,默认的扩展点只能设置一个
      if (names.length > 1) {
        ...
      }
      if (names.length == 1) {
        //将其赋值给cachedDefaultName
        cachedDefaultName = names[0];
      }
    }
  }
}

这里就呼应了上文中,默认扩展点的获取方式,在load扩展点时,会获取@SPI注解上的value值,将其赋值给cachedDefaultName变量,在获取默认扩展点时,检查此变量,再次get一次即可获取到默认扩展点。

接下来来看loadDirectory方法都load了哪些文件夹?

loadDirectory(extensionClasses, DUBBO_INTERNAL_DIRECTORY, type.getName());
loadDirectory(extensionClasses, DUBBO_DIRECTORY, type.getName());
loadDirectory(extensionClasses, SERVICES_DIRECTORY, type.getName());

private static final String DUBBO_INTERNAL_DIRECTORY = "META-INF/dubbo/internal/";
private static final String DUBBO_DIRECTORY = "META-INF/dubbo/";
private static final String SERVICES_DIRECTORY = "META-INF/services/";

也就是说,在以上三个路径下的文件都将可以被加载到,其中loadDirectory方法的第三个变量是扩展点接口的全限定类名,后面会将相对路径加上全限定作为文件路径去文件资源类load,由此可以看出来,我们的配置文件需要以扩展接口全限定名称作为文件名,放入以上三个相对路径其一之下,即可被loadDirectory方法读取到,那么loadDirectory方法做了什么呢:

private void loadDirectory(Map<String, Class<?>> extensionClasses, String dir, String type) {
  //将相对路径与全限定类名作为文件路径
  String fileName = dir + type;
  try {
    Enumeration<java.net.URL> urls;
    ClassLoader classLoader = findClassLoader();
    if (classLoader != null) {
      //根据文件路径,获取文件
      urls = classLoader.getResources(fileName);
    } else {
      urls = ClassLoader.getSystemResources(fileName);
    }
    if (urls != null) {
      while (urls.hasMoreElements()) {
        java.net.URL resourceURL = urls.nextElement();
        //加载资源
        loadResource(extensionClasses, classLoader, resourceURL);
      }
    }
  } catch (Throwable t) {
    ...
  }
}

这里通过特定的路径加全限定类名寻找文件,并且loadResource加载此资源:

private void loadResource(Map<String, Class<?>> extensionClasses, ClassLoader classLoader, java.net.URL resourceURL) {
  try {
    //创建流,准备读取文件中的内容
    try (BufferedReader reader = new BufferedReader(new InputStreamReader(resourceURL.openStream(), StandardCharsets.UTF_8))) {
      String line;
      while ((line = reader.readLine()) != null) {
        //定位‘#’字符,为注释,需要被忽略
        final int ci = line.indexOf('#');
        if (ci >= 0) {
          line = line.substring(0, ci);
        }
        line = line.trim();
        if (line.length() > 0) {
          try {
            String name = null;
            //定位‘=’字符
            int i = line.indexOf('=');
            if (i > 0) {
              //=左边的为name
              name = line.substring(0, i).trim();
              //=右边的为扩展点全限定类名
              line = line.substring(i + 1).trim();
            }
            if (line.length() > 0) {
              //加载类,利用反射预加载此全限定类名
              loadClass(extensionClasses, resourceURL, Class.forName(line, true, classLoader), name);
            }
          } catch (Throwable t) {
            ...
          }
        }
      }
    }
  } catch (Throwable t) {
    ...
  }
}

这里利用流读取文件内容,截取了#、=字符,不多赘述,重点是接下来为loadClass方法,加载获取到的全限定类名与名称,缓存到extensionClasses这个Map中:

private void loadClass(Map<String, Class<?>> extensionClasses, java.net.URL resourceURL, Class<?> clazz, String name) throws NoSuchMethodException {
  //扩展点类对象必须是扩展点接口的子类,不然报错
  if (!type.isAssignableFrom(clazz)) {
    ...
  }
  //如果此扩展点类上有Adaptive注解,认为其为自适应扩展点,并不是具体的扩展点
  //关于自适应扩展点,下面一章中会介绍到
  if (clazz.isAnnotationPresent(Adaptive.class)) {
    //将cachedAdaptiveClass变量设置为此clazz
    cacheAdaptiveClass(clazz);
  }
  //此方法主要判断扩展点的构造函数中是否有参数是当前扩展接口类型的
  //如果是,则为包装装饰类,将cachedWrapperClasses变量add当前clazz
  else if (isWrapperClass(clazz)) {
    cacheWrapperClass(clazz);
  } else {
    //若到这里,说明扩展点为普通扩展点
    //获取默认构造器,若没有默认构造器,在这里会报错
    clazz.getConstructor();
    if (StringUtils.isEmpty(name)) {
      //如果name为空,则查看类上是否有@Extension注解,该注解value值作为name
      //若无此注解,使用小写类名作为name
      name = findAnnotationName(clazz);
      if (name.length() == 0) {
        ...
      }
    }

    String[] names = NAME_SEPARATOR.split(name);
    if (ArrayUtils.isNotEmpty(names)) {
      //如果类中有@Activate注解,将name与注解对象缓存到cachedActivates变量中
      cacheActivateClass(clazz, names[0]);
      for (String n : names) {
        //缓存name,key->clazz,value->name
        cacheName(clazz, n);
        //最后,将拿到的扩展点类放入Map中
        saveInExtensionClass(extensionClasses, clazz, name);
      }
    }
  }
}

若扩展点为普通扩展点,则会将其放入extensionClasses这个Map中,供之后拿到:

private void saveInExtensionClass(Map<String, Class<?>> extensionClasses, Class<?> clazz, String name) {
  Class<?> c = extensionClasses.get(name);
  if (c == null) {
    //放入Map
    extensionClasses.put(name, clazz);
  } else if (c != clazz) {
    ...
  }
}

到这里,SPI扩展点的获取就基本完成了,其中loadClass方法可以看到,只有普通的扩展点类才会被放入Map:

  1. 如果类中有Adaptive注解,将clazz放入cachedAdaptiveClass变量中,表示此类为自适应扩展类
  2. 如果类的构造器的参数是扩展点接口类型的,加入cachedWrapperClasses变量中,表示此类为包装装饰类
  3. 上面两者都不满足,代表此类为普通扩展类,放入extensionClasses这个Map,后续从Map中获取

一般来说,扩展点都是第三种普通扩展类,读者可以回到上面的createExtension方法,可以看到此方法从extensionClasses这个Map中用name取出clazz对象,利用反射将其实例化出来使用。下面我们来聊聊IOC依赖注入机制与包装机制(其中第一个分支自适应扩展类的分析在下面一个章节,自适应扩展机制中会提到)

IOC依赖注入机制

上面多多少少都会有提到这个机制,在获取扩展类的过程中并不是单纯的返回扩展类对象,而是会调用injectExtension方法,再进行返回,包括包装类也是会调用此方法进行注入一下依赖,所以此方法作为入口来看看这个机制是什么:

private T injectExtension(T instance) {
  try {
    //objectFactory变量在类构造器时根据自适应扩展点进行初始化
    //类似一个IOC容器工厂一样的角色
    if (objectFactory != null) {
      //利用反射,遍历实例中所有方法
      for (Method method : instance.getClass().getMethods()) {
        //判断是否是set方法,依据如下
        //1. 方法名以set开头
        //2. 方法参数只有一个
        //3. 方法为public
        if (isSetter(method)) {
          //如果方法被打上了DisableInject注解,不进行注入操作
          if (method.getAnnotation(DisableInject.class) != null) {
            continue;
          }
          //获取方法参数类型Class对象
          Class<?> pt = method.getParameterTypes()[0];
          if (ReflectUtils.isPrimitives(pt)) {
            continue;
          }
          try {
            //获取方法名中需要注入的property名称
            //例如setProtocal,此时property=protocal
            String property = getSetterProperty(method);
            //使用参数的Class与参数名称,去IOC容器factory中拿到扩展点类
            Object object = objectFactory.getExtension(pt, property);
            if (object != null) {
              //调用set方法,完成注入
              method.invoke(instance, object);
            }
          } catch (Exception e) {
            ...
          }
        }
      }
    }
  } catch (Exception e) {
    logger.error(e.getMessage(), e);
  }
  return instance;
}

该方法不难懂,有两点我们可以知道:

  1. 方法需要为set开头,参数为需要注入的依赖类型
  2. 若方法不需要注入,打上@DisableInject注解

至于factory是如何获取到扩展点的,需要读者理解下面的章节,自适应扩展机制,然后去看看具体fatcory中的获取逻辑即可。

包装类装饰

在获取扩展类的过程中,可以知道有的类是包装类,并不会被放入最终Map中被获取,而是在返回扩展类实例的时候包装其他扩展类实例,就像createExtension方法中有几行代码如下:

private T createExtension(String name) {
  Class<?> clazz = getExtensionClasses().get(name);
  ...
  //还记得吗,若有包装类的扩展点,cachedWrapperClasses实例就不为空了
  Set<Class<?>> wrapperClasses = cachedWrapperClasses;
  if (CollectionUtils.isNotEmpty(wrapperClasses)) {
    //若Set不为空,则进入这里,进行包装
    for (Class<?> wrapperClass : wrapperClasses) {
      instance = injectExtension((T) wrapperClass.getConstructor(type).newInstance(instance));
    }
  }
  return instance;
} catch (Throwable t) {
  ...
}
}

这里如果有包装扩展点,cachedWrapperClasses变量就不为空,就会利用反射创建此包装扩展点实例,并将普通扩展点作为构造器参数传入包装扩展点进行实例化,此时返回的实例是包装器扩展点,读者需要注意,包装器里是有普通扩展点的引用的,若理解装饰器模式相信会很好理解。那么有什么用呢?主要是为了在原有的功能上增加一道处理,类似Filter一样的功能,在后面的文章分析RPC服务发布与调用时读者自然会明白。

Dubbo 自适应扩展机制

自适应扩展介绍

在分析SPI扩展机制中,其实已经有接触到自适应扩展机制了,在ExtensionLoader对象初始化构造函数中有这样一段代码:

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

还记得objectFactory吗?就是上面提到的IOC容器factory,它是用自适应扩展机制获取到的,也就是说,调用getAdaptiveExtension这样一个方法,获取到的是一个自适应扩展,并不是一个具体的扩展类。

那么为什么自适应扩展机制存在呢?有时候具体的扩展点类并不想一开始的时候就获取,而是在运行时去动态获取,如果一开始就获取,就调用了ExtensionLoader去get指定name的扩展点,那么之后我无法去变更它,如果是动态的,就像在开篇我们其实提到的,dubbo就可以通过一个url去动态的加载扩展点,而url是从配置文件中获取的,这样,就可以做到在配置文件中可以使用zk作为注册中心,修改配置文件又可以使用redis作为注册中心,dubbo是根据配置文件转换为url,利用url进行驱动的,所以自适应扩展机制是一个很好的设计,在后面dubbo源码中也是频频出现,其与上面的SPI扩展机制获取指定扩展点相配合,可以做到灵活扩展动态配置

自定义一个自适应扩展机制(@Adaptive):

  1. 将注解打在类上,dubbo将直接使用此类作为自适应扩展类,代表自适应扩展类此时由人工编码生成
  2. 将注解打在方法上,dubbo将动态地生成代码,代码逻辑全由dubbo生成,例如TestService是一个自适应扩展类,注解在方法上时,将动态产生一个类叫做TestService$Adaptive,其中方法里的逻辑都是dubbo生成的。

一般自适应扩展指的是从dubbo的URL中获取到参数,用dubbo的SPI方式使用参数拿到扩展点,从而达到动态自适应的效果。

源码分析

回到刚刚的例子中去,我们可以发现,获取自适应扩展点的入口在getAdaptiveExtension方法中:

public T getAdaptiveExtension() {
  //缓存
  Object instance = cachedAdaptiveInstance.get();
  if (instance == null) {
    if (createAdaptiveInstanceError == null) {
      synchronized (cachedAdaptiveInstance) {
        instance = cachedAdaptiveInstance.get();
        //double-check
        if (instance == null) {
          try {
            //创建自适应扩展实例
            instance = createAdaptiveExtension();
            cachedAdaptiveInstance.set(instance);
          } catch (Throwable t) {
            createAdaptiveInstanceError = t;
            ...
          }
        }
      }
    } else {
      ...
    }
  }

  return (T) instance;
}

这里关注createAdaptiveExtension方法:

private T createAdaptiveExtension() {
  try {
    //获取自适应类后IOC依赖注入
    return injectExtension((T) getAdaptiveExtensionClass().newInstance());
  } catch (Exception e) {
    ...
  }
}

关于IOC依赖注入上面已经介绍过了,主要关注getAdaptiveExtensionClass方法:

private Class<?> getAdaptiveExtensionClass() {
  //先load所有扩展类
  getExtensionClasses();
  //若有@Adaptive注解打在类上,则cachedAdaptiveClass变量不为空
  if (cachedAdaptiveClass != null) {
    //此时表示自适应扩展类为人工编码,直接返回即可
    return cachedAdaptiveClass;
  }
  //表示没有人工编码,dubbo将动态自动生成类
  return cachedAdaptiveClass = createAdaptiveExtensionClass();
}

首先,getExtensionClasses方法在上面已经介绍过了,其中在最后会有3个分支判断:

  1. 如果类上有@Adaptive注解,赋值给cachedAdaptiveClass变量,表示此时为人工编码的自适应扩展类
  2. 如果构造器参数为扩展点接口类型,表示此时为装饰类
  3. 普通类,为扩展点类

如果存在1分支,在此时就可以直接返回了,并不需要dubbo生成,就像AdaptiveExtensionFactory这个类一样:

@Adaptive
public class AdaptiveExtensionFactory implements ExtensionFactory {

    private final List<ExtensionFactory> factories;

    //这里自己来定义如何去动态加载
    public AdaptiveExtensionFactory() {
        ExtensionLoader<ExtensionFactory> loader = ExtensionLoader.getExtensionLoader(ExtensionFactory.class);
        List<ExtensionFactory> list = new ArrayList<ExtensionFactory>();
        for (String name : loader.getSupportedExtensions()) {
            //获取所有ExtensionFactory的扩展类,
            //并且保存下来到factories变量中
            list.add(loader.getExtension(name));
        }
        factories = Collections.unmodifiableList(list);
    }

    @Override
    public <T> T getExtension(Class<T> type, String name) {
        //这里从构造函数中构造的所有ExtensionFactory的扩展类中
        //一个个看有没有type、name这样的扩展点
        for (ExtensionFactory factory : factories) {
            //若可以获取到此扩展点,直接返回
            T extension = factory.getExtension(type, name);
            if (extension != null) {
                return extension;
            }
        }
        return null;
    }
}

这里展示了上面所提到的AdaptiveExtensionFactory,用自定义的方式去动态加载所有ExtensionFactory的扩展类,从而达到只需要往文件中增加ExtensionFactory的实现类,即可动态增加IOC容器factory的实现,多增加一个容器,你就可以多增加一个IOC依赖注入的来源

上面提到的AdaptiveExtensionFactory类就会直接返回,不会动态创建,那么如果没有这样的有@Adaptive的类怎么办?那么dubbo就动态创建一个,进入createAdaptiveExtensionClass方法:

private Class<?> createAdaptiveExtensionClass() {
  //new一个生成类,传入type=DemoService 扩展点接口Class对象 和默认扩展点名称
  String code = new AdaptiveClassCodeGenerator(type, cachedDefaultName).generate();
  //拿到ExtensionLoader类的ClassLoader
  ClassLoader classLoader = findClassLoader();
  //编译类,也是通过自适应方式拿到
  org.apache.dubbo.common.compiler.Compiler compiler = ExtensionLoader.getExtensionLoader(org.apache.dubbo.common.compiler.Compiler.class).getAdaptiveExtension();
  //将代码进行编译,classLoader加载,最终变为一个Class对象
  return compiler.compile(code, classLoader);
}

这里关键是生成类的generate方法,关注其中生成的是怎样的代码:

public String generate() {
  // no need to generate adaptive class since there's no adaptive method found.
  if (!hasAdaptiveMethod()) {
    throw new IllegalStateException("No adaptive method exist on extension " + type.getName() + ", refuse to create the adaptive class!");
  }

  StringBuilder code = new StringBuilder();
  //这里没什么可说的,只是增加一个包名,import...
  //例:ProxyFactory ->
  //package org.apache.dubbo.rpc;
  code.append(generatePackageInfo());
  //import org.apache.dubbo.common.extension.ExtensionLoader;
  code.append(generateImports());
  //public class ProxyFactory$Adaptive implements org.apache.dubbo.rpc.ProxyFactory
  code.append(generateClassDeclaration());

  Method[] methods = type.getMethods();
  for (Method method : methods) {
    //生成方法代码
    code.append(generateMethod(method));
  }
  code.append("}");

  if (logger.isDebugEnabled()) {
    logger.debug(code.toString());
  }
  //返回代码
  return code.toString();
}

这里真正复杂的是generateMethod,生成方法的代码:

private String generateMethod(Method method) {
  //获取方法返回类型
  String methodReturnType = method.getReturnType().getCanonicalName();
  //获取方法名
  String methodName = method.getName();
  //生成方法内容
  String methodContent = generateMethodContent(method);
  //生成方法参数
  String methodArgs = generateMethodArguments(method);
  //生成throw代码
  String methodThrows = generateMethodThrows(method);
  //按照public %s %s(%s) %s {%s}格式format
  return String.format(CODE_METHOD_DECLARATION, methodReturnType, methodName, methodArgs, methodThrows, methodContent);
}

相信到这里已经可以看出来代码的萌芽了,遍历每个方法,一个个进行拼接,就像日常写代码那样,这里需要关注的是generateMethodContent方法,是如何生产方法内容的:

private String generateMethodContent(Method method) {
  //获取方法上的Adaptive注解
  Adaptive adaptiveAnnotation = method.getAnnotation(Adaptive.class);
  StringBuilder code = new StringBuilder(512);
  //1.若此方法没有Adaptive注解,不为此方法生成动态方法
  if (adaptiveAnnotation == null) {
    return generateUnsupported(method);
  } else {
    //获取Url这个参数的参数索引
    int urlTypeIndex = getUrlTypeIndex(method);

    // found parameter in URL type
    //如果不为-1,表示找到了Url这个参数
    if (urlTypeIndex != -1) {
      // Null Point check
      //2.生成getUrl的方法
      code.append(generateUrlNullCheck(urlTypeIndex));
    } else {
      // did not find parameter in URL type
      //3.没有找到Url这个参数,找其他参数是否存在可以获取到Url的方法
      code.append(generateUrlAssignmentIndirectly(method));
    }

    //获取注解上的value值,此值代表获取扩展点时get的name值
    String[] value = getMethodAdaptiveValue(adaptiveAnnotation);

    //检测是否有Invocation类型的参数
    boolean hasInvocation = hasInvocationArgument(method);

    //4.生成获取Invocation参数的值的代码
    code.append(generateInvocationArgumentNullCheck(method));

    //5.生成从url获取参数的代码
    code.append(generateExtNameAssignment(value, hasInvocation));
    // check extName == null?
    //检查extName == null的代码
    code.append(generateExtNameNullCheck(value));

    //生成getExtensionLoader(xx).getExtension(extName)代码
    code.append(generateExtensionAssignment());

    // return statement
    //生成return 扩展点类执行同样方法的代码
    code.append(generateReturnAndInvocation(method));
  }

  return code.toString();
}

此方法看上去难免抽象,我们需要结合调试去看看它到底会生成怎样的代码,这样会比较好理解,我将上面几个关键点都标上了数字,现在来一一的讲解:

  1. 若此方法没有Adaptive注解,不为此方法生成动态方法:

    throw new UnsupportedOperationException("The method %s of interface %s is not adaptive method!")";	
    

    此时的处理方式仅仅为生产一段抛出异常的代码,表示此方法不支持动态

  2. 生成getUrl的方法

    由于dubbo中动态的过程需要利用URL-Bus总线的机制去动态获取配置的参数,所以获取URL这个参数必不可少,这里会生成这样一串代码(注意此时是检测到参数中有URL这个参数了):

    if (arg2 == null) throw new IllegalArgumentException("url == null");
    org.apache.dubbo.common.URL url = arg2;
    

    首先判空,接着将url变量赋值URL参数

  3. 没有找到Url这个参数,找其他参数是否存在可以获取到Url的方法

    这里这个方法略微复杂:

    private String generateUrlAssignmentIndirectly(Method method) {
      Class<?>[] pts = method.getParameterTypes();
    
      // find URL getter method
      //遍历所有方法中的参数
      for (int i = 0; i < pts.length; ++i) {
        //遍历参数的所有方法
        for (Method m : pts[i].getMethods()) {
          String name = m.getName();
          //这里需要满足以下条件
          //1. 方法名以get开头或者方法名长度大于3
          //2. 方法是public的
          //3. 方法不是static的
          //4. 方法没有参数
          //5. 方法的返回值为URL类型
          if ((name.startsWith("get") || name.length() > 3)
              && Modifier.isPublic(m.getModifiers())
              && !Modifier.isStatic(m.getModifiers())
              && m.getParameterTypes().length == 0
              && m.getReturnType() == URL.class) {
            //生成获取URL的代码
            return generateGetUrlNullCheck(i, pts[i], name);
          }
        }
      }
    
      // getter method not found, throw
      //若找不到,抛出异常,表示无法动态生成
      throw new IllegalStateException("Failed to create adaptive class for interface " + type.getName()
                                      + ": not found url parameter or url attribute in parameters of method " + method.getName());
    
    }
    

    若此时满足以上条件,将生成像下面这样的代码:

    if (arg0 == null) throw new IllegalArgumentException("org.apache.dubbo.rpc.Invoker argument == null");
    if (arg0.getUrl() == null)
      throw new IllegalArgumentException("org.apache.dubbo.rpc.Invoker argument getUrl() == null");
    org.apache.dubbo.common.URL url = arg0.getUrl();
    

    总之都是在获取URL这个类型,并赋值给url变量

  4. 生成获取Invocation参数的值的代码

    这里主要是判断参数类型是否存在Invocation的类型,若有,将会生成以下代码:

    if (arg2 == null) throw new IllegalArgumentException("invocation == null");
    String methodName = arg2.getMethodName();
    
  5. 生成从url获取参数的代码

    这里代码逻辑相当繁杂,直接看生成的样子会很好理解:

    //若有Invocation时生成的样子
    String extName = url.getMethodParameter(methodName, "loadbalance", "random");
    
    //没有Invocation时生成的样子
    String extName = url.getParameter("dispatcher", url.getParameter("dispather", url.getParameter("channel.handler", "all")));
    

    也就是说,我们从url获取参数,优先查看是否存在Invocation类型,若有就优先从url中获取Invocation.getMethodName这样的参数,若没有这样的参数,次之获取@Adaptive注解里的value值,若value值有多个,那优先获取value1,value1没有,就获取value2,value2也没有,则获取@SPI里value值,这里随便举一个URL为例子:

    dubbo://192.168.0.101:20880/XxxService?wheel.maker=MichelinWheelMaker
    

    此时如果extName=wheel.maker,那我就可以获取到MichelinWheelMaker这个值,就可以获取名叫MichelinWheelMaker这个扩展点,所以,我们可以通过URL传参数的方式动态的去获取扩展点,这里逻辑相对有点复杂,还需反复揣摩

到这里,自适应扩展机制也就介绍完了,有些地方确实逻辑复杂难懂,需要大家耐心思考,在阅读自适应扩展的源码时可以多多调试,可以加深理解。

这里贴出一个自适应动态生成的代码(ProxyFactory)以做例子参考:

package org.apache.dubbo.rpc;

import org.apache.dubbo.common.extension.ExtensionLoader;

public class ProxyFactory$Adaptive implements org.apache.dubbo.rpc.ProxyFactory {
    public java.lang.Object getProxy(org.apache.dubbo.rpc.Invoker arg0) throws org.apache.dubbo.rpc.RpcException {
        if (arg0 == null) throw new IllegalArgumentException("org.apache.dubbo.rpc.Invoker argument == null");
        if (arg0.getUrl() == null)
            throw new IllegalArgumentException("org.apache.dubbo.rpc.Invoker argument getUrl() == null");
        org.apache.dubbo.common.URL url = arg0.getUrl();
        String extName = url.getParameter("proxy", "javassist");
        if (extName == null)
            throw new IllegalStateException("Failed to get extension (org.apache.dubbo.rpc.ProxyFactory) name from url (" + url.toString() + ") use keys([proxy])");
        org.apache.dubbo.rpc.ProxyFactory extension = ExtensionLoader.getExtensionLoader(org.apache.dubbo.rpc.ProxyFactory.class).getExtension(extName);
        return extension.getProxy(arg0);
    }

    public java.lang.Object getProxy(org.apache.dubbo.rpc.Invoker arg0, boolean arg1) throws org.apache.dubbo.rpc.RpcException {
        if (arg0 == null) throw new IllegalArgumentException("org.apache.dubbo.rpc.Invoker argument == null");
        if (arg0.getUrl() == null)
            throw new IllegalArgumentException("org.apache.dubbo.rpc.Invoker argument getUrl() == null");
        org.apache.dubbo.common.URL url = arg0.getUrl();
        String extName = url.getParameter("proxy", "javassist");
        if (extName == null)
            throw new IllegalStateException("Failed to get extension (org.apache.dubbo.rpc.ProxyFactory) name from url (" + url.toString() + ") use keys([proxy])");
        org.apache.dubbo.rpc.ProxyFactory extension = (org.apache.dubbo.rpc.ProxyFactory) ExtensionLoader.getExtensionLoader(org.apache.dubbo.rpc.ProxyFactory.class).getExtension(extName);
        return extension.getProxy(arg0, arg1);
    }

    public org.apache.dubbo.rpc.Invoker getInvoker(java.lang.Object arg0, java.lang.Class arg1, org.apache.dubbo.common.URL arg2) throws org.apache.dubbo.rpc.RpcException {
        if (arg2 == null) throw new IllegalArgumentException("url == null");
        org.apache.dubbo.common.URL url = arg2;
        String extName = url.getParameter("proxy", "javassist");
        if (extName == null)
            throw new IllegalStateException("Failed to get extension (org.apache.dubbo.rpc.ProxyFactory) name from url (" + url.toString() + ") use keys([proxy])");
        org.apache.dubbo.rpc.ProxyFactory extension = (org.apache.dubbo.rpc.ProxyFactory) ExtensionLoader.getExtensionLoader(org.apache.dubbo.rpc.ProxyFactory.class).getExtension(extName);
        return extension.getInvoker(arg0, arg1, arg2);
    }
}
发布了84 篇原创文章 · 获赞 85 · 访问量 10万+

猜你喜欢

转载自blog.csdn.net/qq_41737716/article/details/91644334
今日推荐