前言

学习了解dubbo,第一步的基础就是了解spi相关的。知道dubbo怎么来加载这些类。像Spring可以使用注解来实现自己的容器,进行IoC控制所有的类。dubbo采取了另一个方式来管理它所需要的所有的类。这就是使用SPI机制。

SPI介绍

SPI 的本质是将接口实现类的全限定名配置在文件中,并由服务加载器读取配置文件,加载实现类。java原生有SPI机制,

Java SPI

介绍dubbo spi之前,我们先了解java的spi。dubbo的spi就是创建一个接口和实现类,然后在resource目录下,建立 META-INF/services/xxx 的文件,xxx是接口名称,文件里面写上类全量名即可。类定义如下:

  1. public interface Robot {
  2. void sayHello();
  3. }
  4. public class Bumblebee implements Robot {
  5. @Override
  6. public void sayHello() {
  7. System.out.println("amazing...");
  8. }
  9. }

文件名: META-INF/services/com.lml.part.dubbo.spi.Robot ,内容如下:

  1. com.lml.part.dubbo.spi.Bumblebee

使用如下:

  1. ServiceLoader<Robot> serviceLoader = ServiceLoader.load(Robot.class);
  2. System.out.println("java spi. ");
  3. for (Robot robot : serviceLoader) {
  4. robot.sayHello();
  5. }

Java SPI源码

首先创建出来 ServiceLoader 这里没什么特别的,主要是创建了 LazyIterator

  1. public static <S> ServiceLoader<S> load(Class<S> service) {
  2. ClassLoader cl = Thread.currentThread().getContextClassLoader();
  3. //调用方法load
  4. return ServiceLoader.load(service, cl);
  5. }
  6. public static <S> ServiceLoader<S> load(Class<S> service,ClassLoader loader){
  7. return new ServiceLoader<>(service, loader);
  8. }
  9. private ServiceLoader(Class<S> svc, ClassLoader cl) {
  10. service = Objects.requireNonNull(svc, "Service interface cannot be null");
  11. loader = (cl == null) ? ClassLoader.getSystemClassLoader() : cl;
  12. acc = (System.getSecurityManager() != null) ? AccessController.getContext() : null;
  13. reload();
  14. }
  15. public void reload() {
  16. providers.clear();
  17. //主要加载了LayIterator类,所有的实现也在这里
  18. lookupIterator = new LazyIterator(service, loader);
  19. }

获取的时候,进行 iterator 循环迭代获取,iterator 调用的是 providers 的循环,最后调用的是 LazyIterator 。以下是读取所有的文件返回出文件名称集合。

  1. private boolean hasNextService() {
  2. if (nextName != null) {
  3. return true;
  4. }
  5. if (configs == null) {
  6. try {
  7. //PREFIX前缀:META-INF/services/
  8. String fullName = PREFIX + service.getName();
  9. if (loader == null)
  10. configs = ClassLoader.getSystemResources(fullName);
  11. else
  12. //加载配置文件
  13. configs = loader.getResources(fullName);
  14. } catch (IOException x) {
  15. fail(service, "Error locating configuration files", x);
  16. }
  17. }
  18. while ((pending == null) || !pending.hasNext()) {
  19. if (!configs.hasMoreElements()) {
  20. return false;
  21. }
  22. //解析配置文件
  23. pending = parse(service, configs.nextElement());
  24. }
  25. nextName = pending.next();
  26. return true;
  27. }
  28. //按照文件一行行读取配置文件并进行解析返回
  29. private Iterator<String> parse(Class<?> service, URL u) throws ServiceConfigurationError{
  30. InputStream in = null;
  31. BufferedReader r = null;
  32. ArrayList<String> names = new ArrayList<>();
  33. try {
  34. in = u.openStream();
  35. r = new BufferedReader(new InputStreamReader(in, "utf-8"));
  36. int lc = 1;
  37. while ((lc = parseLine(service, u, r, lc, names)) >= 0);
  38. } catch (IOException x) {
  39. fail(service, "Error reading configuration file", x);
  40. } finally {
  41. try {
  42. if (r != null) r.close();
  43. if (in != null) in.close();
  44. } catch (IOException y) {
  45. fail(service, "Error closing configuration file", y);
  46. }
  47. }
  48. return names.iterator();
  49. }

以下是加载出来class文件。

  1. private S nextService() {
  2. if (!hasNextService())
  3. throw new NoSuchElementException();
  4. String cn = nextName;
  5. nextName = null;
  6. Class<?> c = null;
  7. try {
  8. //上一步拿到了文件名,这里进行实例化
  9. c = Class.forName(cn, false, loader);
  10. } catch (ClassNotFoundException x) {
  11. //没找到class报错
  12. fail(service,
  13. "Provider " + cn + " not found");
  14. }
  15. if (!service.isAssignableFrom(c)) {
  16. fail(service,
  17. "Provider " + cn + " not a subtype");
  18. }
  19. try {
  20. //进行cast转化
  21. S p = service.cast(c.newInstance());
  22. //将加载的class,使用providers缓存起来
  23. providers.put(cn, p);
  24. return p;
  25. } catch (Throwable x) {
  26. fail(service,
  27. "Provider " + cn + " could not be instantiated",
  28. x);
  29. }
  30. throw new Error(); // This cannot happen
  31. }

java spi的缺点是:

  1. iterator 虽然进行了延迟加载,但是加载时,也是依旧读取文件将所有的class进行加载出来了,这里还是有些浪费性能的。
  2. 获取class的时候,只能通过iterator形式获取。

Dubbo SPI介绍

dubbo基于这种形式,实现了一套更强的SPI机制,它将所有的类封装在了ExtensionLoader 类中,通过 ExtensionLoader,去加载指定的实现类。Dubbo SPI 所需的配置文件需放置在 META-INF/dubbo 路径下,文件名还是和java spi一样,是加载接口的文件名,文件格式是key,value形式的,格式如下:

  1. bumblebee = org.apache.spi.Bumblebee

另外加载的接口必须标记 SPI 注解才能使用,使用方式如下。

  1. ExtensionLoader<Robot> extensionLoader = ExtensionLoader.getExtensionLoader(Robot.class);
  2. Robot optimusPrime = extensionLoader.getExtension("optimusPrime");
  3. optimusPrime.sayHello();

SPI标记类如下:

  1. @Documented
  2. @Retention(RetentionPolicy.RUNTIME)
  3. @Target({ElementType.TYPE})
  4. public @interface SPI {
  5. /**
  6. * default extension name
  7. */
  8. String value() default "";
  9. }

说明

dubbo的spi实现的功能更为强大,不仅仅实现了刚刚类的加载,同时,它将加载的类缓存起来,我们再次获取时,将直接从缓存获取返回,这样相当于实现了简单的容器。容器里面所有的元素,我们都能拿到了。
当我们有了容器之后,对于我们加载的接口实现也就能实现一定的依赖注入了。同时,能够拿到接口的实现类,也可以进行AOP包装拦截了,这个可以一步步说明。

每一个接口都有很多的实现,这些实现都被封装进入同一个ExtensionLoader了, 既然要获取接口实现类,首先就要获取到接口的ExtensionLoader实现类,这个接口的loader会缓存接口加载的所有实现类,默认激活类等等。

获取当前接口ExtensionLoader 之前,我们还需要首先获取loader加载的factory,用来加载所有标记SPI 的接口,这个后续讲解。

getExtensionLoader

首先获取loader,这里会进行缓存。

  1. //缓存所有的Loader
  2. private static final ConcurrentMap<Class<?>, ExtensionLoader<?>> EXTENSION_LOADERS = new ConcurrentHashMap<>(64);
  3. public static <T> ExtensionLoader<T> getExtensionLoader(Class<T> type) {
  4. if (type == null) {
  5. throw new IllegalArgumentException("Extension type == null");
  6. }
  7. if (!type.isInterface()) {
  8. throw new IllegalArgumentException("Extension type (" + type + ") is not an interface!");
  9. }
  10. //验证必须标记了SPI接口信息
  11. if (!withExtensionAnnotation(type)) {
  12. throw new IllegalArgumentException("Extension type (" + type +
  13. ") is not an extension, because it is NOT annotated with @" + SPI.class.getSimpleName() + "!");
  14. }
  15. //缓存中获取接口的Loader,找不到进行创建
  16. ExtensionLoader<T> loader = (ExtensionLoader<T>) EXTENSION_LOADERS.get(type);
  17. if (loader == null) {
  18. EXTENSION_LOADERS.putIfAbsent(type, new ExtensionLoader<T>(type));
  19. loader = (ExtensionLoader<T>) EXTENSION_LOADERS.get(type);
  20. }
  21. return loader;
  22. }
  23. //每一个Loader的扩展工厂,用于实现加载class时的setting方法注入
  24. private final ExtensionFactory objectFactory;
  25. private ExtensionLoader(Class<?> type) {
  26. this.type = type;
  27. //设置扩展工厂,这里最后加载的objectFactory是AdaptiveExtensionFactory
  28. objectFactory = (type == ExtensionFactory.class ? null : ExtensionLoader.getExtensionLoader(ExtensionFactory.class).getAdaptiveExtension());
  29. }

getExtension(String name)

获取指定xxx的class,这里的xxx就是dubbo文件中key-value对应的key,这样就获取到了接口的实现类。

  1. //缓存的接口实现类。key=xxx,holder是持有的实例
  2. private final ConcurrentMap<String, Holder<Object>> cachedInstances = new ConcurrentHashMap<>();
  3. public T getExtension(String name) {
  4. if (StringUtils.isEmpty(name)) {
  5. throw new IllegalArgumentException("Extension name == null");
  6. }
  7. if ("true".equals(name)) {
  8. return getDefaultExtension();
  9. }
  10. final Holder<Object> holder = getOrCreateHolder(name);
  11. Object instance = holder.get();
  12. // 双重检查
  13. if (instance == null) {
  14. synchronized (holder) {
  15. instance = holder.get();
  16. if (instance == null) {
  17. // 创建拓展实例
  18. instance = createExtension(name);
  19. holder.set(instance);
  20. }
  21. }
  22. }
  23. return (T) instance;
  24. }

这里依旧先从当前loader的缓存中获取,获取不到时,进行创建。

createExtension(String name)

这里进行接口的创建工作。

  1. //缓存的所有通过ExtensionLoader加载好的实例
  2. private static final ConcurrentMap<Class<?>, Object> EXTENSION_INSTANCES = new ConcurrentHashMap<>(64);
  3. //缓存所有的wrapper类
  4. private Set<Class<?>> cachedWrapperClasses;
  5. private T createExtension(String name) {
  6. //这里是从配置文件中加载所有的拓展类集合,拿到这个扩展类集合后,再继续拿到指定key的class
  7. Class<?> clazz = getExtensionClasses().get(name);
  8. if (clazz == null) {
  9. throw findException(name);
  10. }
  11. try {
  12. //检查class是否存在,通过反射实例化
  13. T instance = (T) EXTENSION_INSTANCES.get(clazz);
  14. if (instance == null) {
  15. EXTENSION_INSTANCES.putIfAbsent(clazz, clazz.newInstance());
  16. instance = (T) EXTENSION_INSTANCES.get(clazz);
  17. }
  18. //向实例中注入依赖,依赖上面的ExtensionFactory 这里就是Dubbo的IOC
  19. injectExtension(instance);
  20. Set<Class<?>> wrapperClasses = cachedWrapperClasses;
  21. //如果发现是wrapper类,开始注入加载
  22. if (CollectionUtils.isNotEmpty(wrapperClasses)) {
  23. for (Class<?> wrapperClass : wrapperClasses) {
  24. //将拿到的instance注入到wrapper类当中去,然后将wrapper类再次赋值给当前的instance
  25. //这里是dubbo的AOP体现
  26. instance = injectExtension((T) wrapperClass.getConstructor(type).newInstance(instance));
  27. }
  28. }
  29. //确定下这个instance是否继承了Lifecycle,帮它进行初始化工作
  30. initExtension(instance);
  31. return instance;
  32. } catch (Throwable t) {
  33. throw new IllegalStateException("Extension instance (name: " + name + ", class: " +
  34. type + ") couldn't be instantiated: " + t.getMessage(), t);
  35. }
  36. }

通过这样的方式,就将配置文件中加载的class实例化出来,并且返回了。这里需要关注下,首先getExtensionClasses配置文件加载的方法,这里加载了所有的class。其次是,injectExtension进行了注入,如何注入的,作用是什么。最后是,wrapperClasses的处理和用处。

getExtensionClasses()

加载获取该接口所有的class

  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. //缓存下这些class,真正的加载地方
  8. classes = loadExtensionClasses();
  9. cachedClasses.set(classes);
  10. }
  11. }
  12. }
  13. return classes;
  14. }
  15. //dubbo默认加载的路径
  16. private static final String SERVICES_DIRECTORY = "META-INF/services/";
  17. private static final String DUBBO_DIRECTORY = "META-INF/dubbo/";
  18. private static final String DUBBO_INTERNAL_DIRECTORY = DUBBO_DIRECTORY + "internal/";
  19. private static LoadingStrategy[] strategies = new LoadingStrategy[] { DUBBO_INTERNAL_STRATEGY, DUBBO_STRATEGY, SERVICES_STRATEGY };
  20. private Map<String, Class<?>> loadExtensionClasses() {
  21. cacheDefaultExtensionName();
  22. Map<String, Class<?>> extensionClasses = new HashMap<>();
  23. //循环从以下地址的文件路径上去加载出来这些class
  24. for (LoadingStrategy strategy : strategies) {
  25. loadDirectory(extensionClasses, strategy.directory(), type.getName(), strategy.preferExtensionClassLoader(), strategy.excludedPackages());
  26. loadDirectory(extensionClasses, strategy.directory(), type.getName().replace("org.apache", "com.alibaba"), strategy.preferExtensionClassLoader(), strategy.excludedPackages());
  27. }
  28. return extensionClasses;
  29. }

loadDirectory

加载这些路径的文件,主要就是读取这些路径的文件,然后逐条解析下,之后进行分类加载。

  1. private void loadDirectory(Map<String, Class<?>> extensionClasses, String dir, String type,
  2. boolean extensionLoaderClassLoaderFirst, String... excludedPackages) {
  3. // fileName = 文件夹路径 + type 全限定名
  4. String fileName = dir + type;
  5. try {
  6. Enumeration<java.net.URL> urls = null;
  7. ClassLoader classLoader = findClassLoader();
  8. // try to load from ExtensionLoader's ClassLoader first
  9. if (extensionLoaderClassLoaderFirst) {
  10. ClassLoader extensionLoaderClassLoader = ExtensionLoader.class.getClassLoader();
  11. if (ClassLoader.getSystemClassLoader() != extensionLoaderClassLoader) {
  12. urls = extensionLoaderClassLoader.getResources(fileName);
  13. }
  14. }
  15. //加载出这些文件的urls
  16. if(urls == null || !urls.hasMoreElements()) {
  17. if (classLoader != null) {
  18. urls = classLoader.getResources(fileName);
  19. } else {
  20. urls = ClassLoader.getSystemResources(fileName);
  21. }
  22. }
  23. if (urls != null) {
  24. while (urls.hasMoreElements()) {
  25. java.net.URL resourceURL = urls.nextElement();
  26. //进一步将拿到的url进行解析
  27. loadResource(extensionClasses, classLoader, resourceURL, excludedPackages);
  28. }
  29. }
  30. } catch (Throwable t) {
  31. logger.error("Exception occurred when loading extension class (interface: " +
  32. type + ", description file: " + fileName + ").", t);
  33. }
  34. }
  35. private void loadResource(Map<String, Class<?>> extensionClasses, ClassLoader classLoader,
  36. java.net.URL resourceURL, String... excludedPackages) {
  37. try {
  38. try (BufferedReader reader = new BufferedReader(new InputStreamReader(resourceURL.openStream(), StandardCharsets.UTF_8))) {
  39. String line;
  40. //开始每行文件的读取
  41. while ((line = reader.readLine()) != null) {
  42. final int ci = line.indexOf('#');
  43. if (ci >= 0) {
  44. line = line.substring(0, ci);
  45. }
  46. line = line.trim();
  47. if (line.length() > 0) {
  48. try {
  49. String name = null;
  50. int i = line.indexOf('=');
  51. if (i > 0) {
  52. name = line.substring(0, i).trim();
  53. line = line.substring(i + 1).trim();
  54. }
  55. if (line.length() > 0 && !isExcluded(line, excludedPackages)) {
  56. //反射这个class,并加载这些class
  57. loadClass(extensionClasses, resourceURL, Class.forName(line, true, classLoader), name);
  58. }
  59. } catch (Throwable t) {
  60. IllegalStateException e = new IllegalStateException("Failed to load extension class (interface: " + type + ", class line: " + line + ") in " + resourceURL + ", cause: " + t.getMessage(), t);
  61. exceptions.put(line, e);
  62. }
  63. }
  64. }
  65. }
  66. } catch (Throwable t) {
  67. logger.error("Exception occurred when loading extension class (interface: " +
  68. type + ", class file: " + resourceURL + ") in " + resourceURL, t);
  69. }
  70. }

loadClass

这里就是class详细区分和加载。

  1. private void loadClass(Map<String, Class<?>> extensionClasses, java.net.URL resourceURL, Class<?> clazz, String name) throws NoSuchMethodException {
  2. //必须是当前type的实现类
  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. // 检测目标类上是否有 Adaptive 注解
  10. // 设置 cachedAdaptiveClass缓存
  11. cacheAdaptiveClass(clazz);
  12. } else if (isWrapperClass(clazz)) {
  13. // 检测 clazz 是否是 Wrapper 类型(这里检测方式就是这个类是有有一个此type的构造函数存在)
  14. // 存储 clazz 到 cachedWrapperClasses 缓存中
  15. cacheWrapperClass(clazz);
  16. } else {
  17. // 检测 clazz 是否有默认的构造方法,如果没有,则抛出异常
  18. clazz.getConstructor();
  19. if (StringUtils.isEmpty(name)) {
  20. // 如果 name 为空,则尝试从 Extension 注解中获取 name,或使用小写的类名作为 name
  21. name = findAnnotationName(clazz);
  22. if (name.length() == 0) {
  23. throw new IllegalStateException("No such extension name for the class " + clazz.getName() + " in the config " + resourceURL);
  24. }
  25. }
  26. String[] names = NAME_SEPARATOR.split(name);
  27. if (ArrayUtils.isNotEmpty(names)) {
  28. // 如果类上有 Activate 注解,则使用 names 数组的第一个元素作为键,
  29. // 存储 name 缓存到Activate注解对应的cachedActivates
  30. cacheActivateClass(clazz, names[0]);
  31. for (String n : names) {
  32. //缓存到cachedNames当中去
  33. cacheName(clazz, n);
  34. //将这个class放到参数extensionClasses当中去
  35. saveInExtensionClass(extensionClasses, clazz, n);
  36. }
  37. }
  38. }
  39. }

经过了以上的步骤,我们就拿到了这个类了,并且这个类还被注入过了。

Loader缓存的数据

当我们按照上述的方法获取该class之后,我们的ExtensionLoader 也缓存了不少数据了。

  1. //总缓存的Loader,以type为key
  2. private static final ConcurrentMap<Class<?>, ExtensionLoader<?>> EXTENSION_LOADERS = new ConcurrentHashMap<>(64);
  3. //总缓存的所有的class的实例
  4. private static final ConcurrentMap<Class<?>, Object> EXTENSION_INSTANCES = new ConcurrentHashMap<>(64);
  5. //当前Loader的数据情况
  6. //当前接口
  7. private final Class<?> type;
  8. //当前接口对应的扩展factory,用于自动注入
  9. private final ExtensionFactory objectFactory;
  10. //此type下的class的名称
  11. private final ConcurrentMap<Class<?>, String> cachedNames = new ConcurrentHashMap<>();
  12. //此type下的class的实例
  13. private final Holder<Map<String, Class<?>>> cachedClasses = new Holder<>();
  14. //缓存的activetes实例
  15. private final Map<String, Object> cachedActivates = new ConcurrentHashMap<>();
  16. private final ConcurrentMap<String, Holder<Object>> cachedInstances = new ConcurrentHashMap<>();
  17. private final Holder<Object> cachedAdaptiveInstance = new Holder<>();
  18. //缓存的wrapper
  19. private Set<Class<?>> cachedWrapperClasses;

以上就是一个type的加载过程,这里面还有Adaptive 的了解,factory的加载,wrapper的处理,后续讲解下。

参考