Berbagi teknologi

Kerangka Kerja DangerWind-RPC---4

2024-07-12

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

SPI adalah singkatan dari Service Provider Interface, yang dapat dipahami sebagai antarmuka yang khusus disediakan untuk penyedia layanan atau pengembang yang memperluas fungsi kerangka kerja. SPI memisahkan antarmuka layanan dari implementasi layanan tertentu, memisahkan pemanggil layanan dan pelaksana layanan, dan dapat meningkatkan skalabilitas dan pemeliharaan program. Memodifikasi atau mengganti implementasi layanan tidak memerlukan modifikasi pemanggil. Banyak kerangka kerja yang menggunakan mekanisme SPI Java, seperti: kerangka pegas, driver pemuatan basis data, antarmuka log, dan implementasi ekstensi Dubbo, dll. Lihat mekanisme SPI Dubbo untuk mengimplementasikan bagian SPI dari kerangka RPC ini.

Misalnya, ketika klien berkomunikasi dengan server, ia perlu membuat pesan bersambung. Ada banyak algoritma serialisasi yang dapat digunakan selama serialisasi, antara lain Hessian, Kryo, dan ProtoStuff. Persyaratan sistem adalah memanggil metode di kelas yang sesuai dengan algoritma serialisasi yang relevan berdasarkan nama algoritma serialisasi dalam pesan untuk melakukan serialisasi dan deserialisasi. Selain itu, untuk memfasilitasi ekspansi, SPI perlu digunakan untuk decoupling .

SPI digunakan sebagai berikut:

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

codecName adalah nama algoritma serialisasi, dan kelas terkait perlu dimuat sesuai dengan nama ini.

  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. }

Setiap antarmuka SPI memiliki ExtensionLoader sendiri. Saat memanggil getExtensionLoader, pertama-tama ia akan melakukan serangkaian operasi pemeriksaan hukum, dan kemudian mencoba mendapatkan ExtensionLoader antarmuka. Pertama, coba dapatkan dari cache lokal CHM diperoleh, buat objek Loader.

Setelah itu, instance diperoleh melalui getExtension, dan instance tersebut juga di-cache secara lokal. Jika tidak ada dalam cache, buat kembali instance tersebut.

  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. }

Setelah mendapatkan objek Kelas dari kelas tersebut, Anda dapat membuat objek ini melalui refleksi.

  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. }

Kuncinya adalah proses mendapatkan objek Kelas, yaitu metode getExtensionCalsses:

  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

Pemanggilan metode lapis demi lapis menyadari proses memuat file konfigurasi SPI yang sesuai di bawah jalur META-INF/extensions/ untuk memuat objek Kelas dan mendapatkan instance. Silakan merujuk ke komentar untuk bagian penting.

Perlu dicatat bahwa nama file di bawah META-INF/extensions/ harus konsisten dengan kode. Nama file yang ditentukan dalam kode adalah nama kelas lengkap dari kelas antarmuka SPI. Konten dalam file juga perlu ditulis sesuai dengan (pengidentifikasi kelas implementasi = nama kelas lengkap dari kelas implementasi), sehingga dapat konsisten dengan kode, dan program dapat mengurai file dengan benar dan menggunakan pemuat kelas untuk memuat Kelas yang sesuai. Terakhir, cache menurut &lt;pengidentifikasi kelas implementasi, objek kelas implementasi&gt;.

Karena adanya tiga cache (pengidentifikasi kelas, objek Kelas), (Objek kelas, contoh objek), dan (pengidentifikasi kelas, contoh objek), Anda dapat langsung meneruskan pengidentifikasi untuk mendapatkan turunan dari kelas yang sesuai, yang mana juga mengoptimalkan kinerja kerangka RPC.