Technologieaustausch

DangerWind-RPC-Framework---4

2024-07-12

한어Русский языкEnglishFrançaisIndonesianSanskrit日本語DeutschPortuguêsΕλληνικάespañolItalianoSuomalainenLatina

SPI steht für Service Provider Interface und kann als Schnittstelle verstanden werden, die speziell für Dienstanbieter oder Entwickler bereitgestellt wird, die Framework-Funktionen erweitern. SPI trennt die Serviceschnittstelle von der spezifischen Serviceimplementierung, entkoppelt den Serviceaufrufer und den Serviceimplementierer und kann die Skalierbarkeit und Wartbarkeit des Programms verbessern. Das Ändern oder Ersetzen einer Dienstimplementierung erfordert keine Änderung des Aufrufers. Viele Frameworks verwenden den SPI-Mechanismus von Java, z. B. Spring Framework, Datenbankladetreiber, Protokollschnittstelle und Dubbo-Erweiterungsimplementierung usw. Informationen zur Implementierung des SPI-Teils dieses RPC-Frameworks finden Sie im SPI-Mechanismus von Dubbo.

Wenn der Client beispielsweise mit dem Server kommuniziert, muss er die Nachricht serialisieren. Es gibt viele Serialisierungsalgorithmen, die während der Serialisierung verwendet werden können, darunter Hessian, Kryo und ProtoStuff. Die Anforderung des Systems besteht darin, die Methode in der Klasse aufzurufen, die dem relevanten Serialisierungsalgorithmus entspricht, basierend auf dem Namen des Serialisierungsalgorithmus in der Nachricht, um die Serialisierung und Deserialisierung durchzuführen. Darüber hinaus muss SPI zur Entkopplung verwendet werden .

SPI wird wie folgt verwendet:

  1. Serializer serializer = ExtensionLoader.getExtensionLoader(Serializer.class)
  2. .getExtension(codecName);

CodecName ist der Name des Serialisierungsalgorithmus, und die entsprechende Klasse muss entsprechend diesem Namen geladen werden.

  1. private final Class<?> type;
  2. private ExtensionLoader(Class<?> type) {
  3. this.type = type;
  4. }
  5. // 每个SPI接口都有自身的ExtensionLoader
  6. public static <S> ExtensionLoader<S> getExtensionLoader(Class<S> type) {
  7. if (type == null) {
  8. throw new IllegalArgumentException("Extension type should not be null.");
  9. }
  10. if (!type.isInterface()) {
  11. throw new IllegalArgumentException("Extension type must be an interface.");
  12. }
  13. if (type.getAnnotation(SPI.class) == null) {
  14. throw new IllegalArgumentException("Extension type must be annotated by @SPI");
  15. }
  16. // firstly get from cache, if not hit, create one
  17. ExtensionLoader<S> extensionLoader = (ExtensionLoader<S>) EXTENSION_LOADERS.get(type);
  18. if (extensionLoader == null) {
  19. EXTENSION_LOADERS.putIfAbsent(type, new ExtensionLoader<S>(type));
  20. extensionLoader = (ExtensionLoader<S>) EXTENSION_LOADERS.get(type);
  21. }
  22. return extensionLoader;
  23. }

Jede SPI-Schnittstelle verfügt über einen eigenen ExtensionLoader. Wenn dies nicht möglich ist, führt sie zunächst eine Reihe zulässiger Prüfvorgänge durch und versucht dann, den ExtensionLoader der Schnittstelle abzurufen erhalten, erstellen Sie ein Loader-Objekt.

Danach wird die Instanz über getExtension abgerufen und auch lokal zwischengespeichert. Wenn sie sich nicht im Cache befindet, erstellen Sie die Instanz erneut.

  1. private final Map<String, Holder<Object>> cachedInstances = new ConcurrentHashMap<>();
  2. public T getExtension(String name) {
  3. if (StringUtil.isBlank(name)) {
  4. throw new IllegalArgumentException("Extension name should not be null or empty.");
  5. }
  6. // firstly get from cache, if not hit, create one
  7. // 缓存holder
  8. Holder<Object> holder = cachedInstances.get(name);
  9. if (holder == null) {
  10. cachedInstances.putIfAbsent(name, new Holder<>());
  11. holder = cachedInstances.get(name);
  12. }
  13. // create a singleton if no instance exists
  14. // holder为空,双重检查锁创建示例
  15. Object instance = holder.get();
  16. if (instance == null) {
  17. synchronized (holder) {
  18. instance = holder.get();
  19. if (instance == null) {
  20. instance = createExtension(name);
  21. holder.set(instance);
  22. }
  23. }
  24. }
  25. return (T) instance;
  26. }

Nachdem Sie das Klassenobjekt der Klasse erhalten haben, können Sie dieses Objekt durch Reflektion erstellen.

  1. // 缓存
  2. private static final Map<Class<?>, Object> EXTENSION_INSTANCES = new ConcurrentHashMap<>();
  3. private T createExtension(String name) {
  4. // load all extension classes of type T from file and get specific one by name
  5. // SPI接口对应的实现类,其标识名与class文件的映射,根据标识名获取class
  6. Class<?> clazz = getExtensionClasses().get(name);
  7. if (clazz == null) {
  8. throw new RuntimeException("No such extension of name " + name);
  9. }
  10. T instance = (T) EXTENSION_INSTANCES.get(clazz);
  11. if (instance == null) {
  12. try {
  13. // 缓存中不存在,则创建实例
  14. EXTENSION_INSTANCES.putIfAbsent(clazz, clazz.newInstance());
  15. instance = (T) EXTENSION_INSTANCES.get(clazz);
  16. } catch (Exception e) {
  17. log.error(e.getMessage());
  18. }
  19. }
  20. return instance;
  21. }

Der Schlüssel ist der Prozess zum Abrufen des Klassenobjekts, dh der getExtensionCalsses-Methode:

  1. // 该SPI接口所有实现类的标识与其Class对象的缓存
  2. private final Holder<Map<String, Class<?>>> cachedClasses = new Holder<>();
  3. private static final String SERVICE_DIRECTORY = "META-INF/extensions/";
  4. private Map<String, Class<?>> getExtensionClasses() {
  5. // get the loaded extension class from the cache
  6. // 根据Interface实现类的类名获取对应类的缓存
  7. Map<String, Class<?>> classes = cachedClasses.get();
  8. // double check
  9. if (classes == null) {
  10. synchronized (cachedClasses) {
  11. classes = cachedClasses.get();
  12. if (classes == null) {
  13. classes = new HashMap<>();
  14. // load all extensions from our extensions directory
  15. loadDirectory(classes);
  16. // 将Map集合存储在Holder中进行缓存
  17. cachedClasses.set(classes);
  18. }
  19. }
  20. }
  21. return classes;
  22. }
  23. private void loadDirectory(Map<String, Class<?>> extensionClasses) {
  24. // 固定路径下的文件,SPI接口的类名作为文件名,在此文件中规定需要加载的实现类
  25. String fileName = ExtensionLoader.SERVICE_DIRECTORY + type.getName();
  26. try {
  27. Enumeration<URL> urls;
  28. // 系统类加载器,它能够加载用户类路径(ClassPath)上的类和资源。对于SPI机制尤为重要,因为SPI的实现类通常是由应用程序提供并放置在应用程序的类路径下的
  29. ClassLoader classLoader = ExtensionLoader.class.getClassLoader();
  30. // 获取当前类加载器加载的URL资源,文件名确定一般urls是唯一的
  31. urls = classLoader.getResources(fileName);
  32. if (urls != null) {
  33. while (urls.hasMoreElements()) {
  34. URL resourceUrl = urls.nextElement();
  35. // 使用classLoader加载资源,资源目标在resourceUrl下,加载后的class存储在extensionClasses Map集合当中
  36. loadResource(extensionClasses, classLoader, resourceUrl);
  37. }
  38. }
  39. } catch (IOException e) {
  40. log.error(e.getMessage());
  41. }
  42. }
  43. private void loadResource(Map<String, Class<?>> extensionClasses, ClassLoader classLoader, URL resourceUrl) {
  44. try (BufferedReader reader = new BufferedReader(new InputStreamReader(resourceUrl.openStream(), UTF_8))) {
  45. String line;
  46. // read every line
  47. // #是注释,截取注释之前的部分
  48. while ((line = reader.readLine()) != null) {
  49. // get index of comment
  50. final int ci = line.indexOf('#');
  51. if (ci >= 0) {
  52. // string after # is comment so we ignore it
  53. line = line.substring(0, ci);
  54. }
  55. line = line.trim();
  56. if (line.length() > 0) {
  57. try {
  58. final int ei = line.indexOf('=');
  59. // 标识与类名
  60. String name = line.substring(0, ei).trim();
  61. String clazzName = line.substring(ei + 1).trim();
  62. // our SPI use key-value pair so both of them must not be empty
  63. if (name.length() > 0 && clazzName.length() > 0) {
  64. // 加载类
  65. Class<?> clazz = classLoader.loadClass(clazzName);
  66. // 在map中保存
  67. extensionClasses.put(name, clazz);
  68. }
  69. } catch (ClassNotFoundException e) {
  70. log.error(e.getMessage());
  71. }
  72. }
  73. }
  74. } catch (IOException e) {
  75. log.error(e.getMessage());
  76. }
  77. }
  1. kyro=github.javaguide.serialize.kyro.KryoSerializer
  2. protostuff=github.javaguide.serialize.protostuff.ProtostuffSerializer
  3. hessian=github.javaguide.serialize.hessian.HessianSerializer

Der schichtweise Methodenaufruf realisiert den Prozess des Ladens der entsprechenden SPI-Konfigurationsdatei unter dem Pfad META-INF/extensions/, um das Klassenobjekt zu laden und die Instanz zu erhalten. Bitte beachten Sie die Kommentare für wichtige Teile.

Es ist zu beachten, dass der Dateiname unter META-INF/extensions/ mit dem Code übereinstimmen muss. Der im Code angegebene Dateiname ist der vollständige Klassenname der SPI-Schnittstellenklasse. Der Inhalt der Datei muss außerdem gemäß (Implementierungsklassenkennung = vollständiger Klassenname der Implementierungsklasse) geschrieben werden, damit er mit dem Code übereinstimmt und das Programm die Datei korrekt analysieren und den Klassenlader verwenden kann Laden Sie die entsprechende Klasse. Schließlich wird der Cache gemäß &lt;Implementierungsklassenkennung, Implementierungsklassenklassenobjekt&gt; gespeichert.

Aufgrund der Existenz der drei Caches (Klassenkennung, Klassenobjekt), (Klassenobjekt, Objektinstanz) und (Klassenkennung, Objektinstanz) können Sie die Kennung direkt übergeben, um die Instanz der entsprechenden Klasse zu erhalten Optimiert auch die Leistung des RPC-Frameworks.