τα στοιχεία επικοινωνίας μου
Ταχυδρομείο[email protected]
2024-07-12
한어Русский языкEnglishFrançaisIndonesianSanskrit日本語DeutschPortuguêsΕλληνικάespañolItalianoSuomalainenLatina
Το SPI σημαίνει Διασύνδεση παρόχου υπηρεσιών, η οποία μπορεί να γίνει κατανοητή ως μια διεπαφή που παρέχεται ειδικά σε παρόχους υπηρεσιών ή προγραμματιστές που επεκτείνουν τις λειτουργίες πλαισίου. Το SPI διαχωρίζει τη διεπαφή υπηρεσίας από τη συγκεκριμένη υλοποίηση υπηρεσίας, αποσυνδέει τον καλούντα υπηρεσίας και τον υλοποιητή υπηρεσίας και μπορεί να βελτιώσει την επεκτασιμότητα και τη δυνατότητα συντήρησης του προγράμματος. Η τροποποίηση ή η αντικατάσταση μιας υλοποίησης υπηρεσίας δεν απαιτεί τροποποίηση του καλούντος. Πολλά πλαίσια χρησιμοποιούν τον μηχανισμό SPI της Java, όπως: Spring frame, πρόγραμμα οδήγησης φόρτωσης βάσης δεδομένων, διεπαφή καταγραφής και υλοποίηση επέκτασης Dubbo κ.λπ. Ανατρέξτε στον μηχανισμό SPI της Dubbo για να εφαρμόσετε το τμήμα SPI αυτού του πλαισίου RPC.
Για παράδειγμα, όταν ο πελάτης επικοινωνεί με τον διακομιστή, πρέπει να σειριοποιήσει το μήνυμα. Υπάρχουν πολλοί αλγόριθμοι σειριοποίησης που μπορούν να χρησιμοποιηθούν κατά τη σειριοποίηση, συμπεριλαμβανομένων των Hessian, Kryo και ProtoStuff. Η απαίτηση του συστήματος είναι να καλέσει τη μέθοδο στην κλάση που αντιστοιχεί στον σχετικό αλγόριθμο σειριοποίησης με βάση το όνομα του αλγόριθμου σειριοποίησης στο μήνυμα για να πραγματοποιήσει σειριοποίηση και αποσειροποίηση .
Το SPI χρησιμοποιείται ως εξής:
- Serializer serializer = ExtensionLoader.getExtensionLoader(Serializer.class)
- .getExtension(codecName);
codecName είναι το όνομα του αλγορίθμου σειριοποίησης και η αντίστοιχη κλάση πρέπει να φορτωθεί σύμφωνα με αυτό το όνομα.
- private final Class<?> type;
-
- private ExtensionLoader(Class<?> type) {
- this.type = type;
- }
-
- // 每个SPI接口都有自身的ExtensionLoader
- public static <S> ExtensionLoader<S> getExtensionLoader(Class<S> type) {
- if (type == null) {
- throw new IllegalArgumentException("Extension type should not be null.");
- }
- if (!type.isInterface()) {
- throw new IllegalArgumentException("Extension type must be an interface.");
- }
- if (type.getAnnotation(SPI.class) == null) {
- throw new IllegalArgumentException("Extension type must be annotated by @SPI");
- }
- // firstly get from cache, if not hit, create one
- ExtensionLoader<S> extensionLoader = (ExtensionLoader<S>) EXTENSION_LOADERS.get(type);
- if (extensionLoader == null) {
- EXTENSION_LOADERS.putIfAbsent(type, new ExtensionLoader<S>(type));
- extensionLoader = (ExtensionLoader<S>) EXTENSION_LOADERS.get(type);
- }
- return extensionLoader;
- }
Κάθε διεπαφή SPI έχει το δικό του ExtensionLoader Όταν καλείτε το getExtensionLoader, θα εκτελέσει πρώτα μια σειρά από νομικές λειτουργίες και στη συνέχεια θα προσπαθήσει να αποκτήσει το ExtensionLoader της διεπαφής αποκτηθεί, δημιουργήστε ένα αντικείμενο Loader.
Μετά από αυτό, το στιγμιότυπο λαμβάνεται μέσω του getExtension και το στιγμιότυπο αποθηκεύεται επίσης τοπικά, εάν δεν βρίσκεται στη μνήμη cache, δημιουργήστε ξανά το στιγμιότυπο.
- private final Map<String, Holder<Object>> cachedInstances = new ConcurrentHashMap<>();
-
- public T getExtension(String name) {
- if (StringUtil.isBlank(name)) {
- throw new IllegalArgumentException("Extension name should not be null or empty.");
- }
- // firstly get from cache, if not hit, create one
- // 缓存holder
- Holder<Object> holder = cachedInstances.get(name);
- if (holder == null) {
- cachedInstances.putIfAbsent(name, new Holder<>());
- holder = cachedInstances.get(name);
- }
- // create a singleton if no instance exists
- // holder为空,双重检查锁创建示例
- Object instance = holder.get();
- if (instance == null) {
- synchronized (holder) {
- instance = holder.get();
- if (instance == null) {
- instance = createExtension(name);
- holder.set(instance);
- }
- }
- }
- return (T) instance;
- }
Αφού αποκτήσετε το αντικείμενο Class της κλάσης, μπορείτε να δημιουργήσετε αυτό το αντικείμενο μέσω ανάκλασης.
- // 缓存
- private static final Map<Class<?>, Object> EXTENSION_INSTANCES = new ConcurrentHashMap<>();
-
- private T createExtension(String name) {
- // load all extension classes of type T from file and get specific one by name
- // SPI接口对应的实现类,其标识名与class文件的映射,根据标识名获取class
- Class<?> clazz = getExtensionClasses().get(name);
- if (clazz == null) {
- throw new RuntimeException("No such extension of name " + name);
- }
- T instance = (T) EXTENSION_INSTANCES.get(clazz);
- if (instance == null) {
- try {
- // 缓存中不存在,则创建实例
- EXTENSION_INSTANCES.putIfAbsent(clazz, clazz.newInstance());
- instance = (T) EXTENSION_INSTANCES.get(clazz);
- } catch (Exception e) {
- log.error(e.getMessage());
- }
- }
- return instance;
- }
Το κλειδί είναι η διαδικασία απόκτησης του αντικειμένου Class, δηλαδή η μέθοδος getExtensionCalsses:
- // 该SPI接口所有实现类的标识与其Class对象的缓存
- private final Holder<Map<String, Class<?>>> cachedClasses = new Holder<>();
-
- private static final String SERVICE_DIRECTORY = "META-INF/extensions/";
-
- private Map<String, Class<?>> getExtensionClasses() {
- // get the loaded extension class from the cache
- // 根据Interface实现类的类名获取对应类的缓存
- Map<String, Class<?>> classes = cachedClasses.get();
- // double check
- if (classes == null) {
- synchronized (cachedClasses) {
- classes = cachedClasses.get();
- if (classes == null) {
- classes = new HashMap<>();
- // load all extensions from our extensions directory
- loadDirectory(classes);
- // 将Map集合存储在Holder中进行缓存
- cachedClasses.set(classes);
- }
- }
- }
- return classes;
- }
-
- private void loadDirectory(Map<String, Class<?>> extensionClasses) {
- // 固定路径下的文件,SPI接口的类名作为文件名,在此文件中规定需要加载的实现类
- String fileName = ExtensionLoader.SERVICE_DIRECTORY + type.getName();
- try {
- Enumeration<URL> urls;
- // 系统类加载器,它能够加载用户类路径(ClassPath)上的类和资源。对于SPI机制尤为重要,因为SPI的实现类通常是由应用程序提供并放置在应用程序的类路径下的
- ClassLoader classLoader = ExtensionLoader.class.getClassLoader();
- // 获取当前类加载器加载的URL资源,文件名确定一般urls是唯一的
- urls = classLoader.getResources(fileName);
- if (urls != null) {
- while (urls.hasMoreElements()) {
- URL resourceUrl = urls.nextElement();
- // 使用classLoader加载资源,资源目标在resourceUrl下,加载后的class存储在extensionClasses Map集合当中
- loadResource(extensionClasses, classLoader, resourceUrl);
- }
- }
- } catch (IOException e) {
- log.error(e.getMessage());
- }
- }
-
- private void loadResource(Map<String, Class<?>> extensionClasses, ClassLoader classLoader, URL resourceUrl) {
- try (BufferedReader reader = new BufferedReader(new InputStreamReader(resourceUrl.openStream(), UTF_8))) {
- String line;
- // read every line
- // #是注释,截取注释之前的部分
- while ((line = reader.readLine()) != null) {
- // get index of comment
- final int ci = line.indexOf('#');
- if (ci >= 0) {
- // string after # is comment so we ignore it
- line = line.substring(0, ci);
- }
- line = line.trim();
- if (line.length() > 0) {
- try {
- final int ei = line.indexOf('=');
- // 标识与类名
- String name = line.substring(0, ei).trim();
- String clazzName = line.substring(ei + 1).trim();
- // our SPI use key-value pair so both of them must not be empty
- if (name.length() > 0 && clazzName.length() > 0) {
- // 加载类
- Class<?> clazz = classLoader.loadClass(clazzName);
- // 在map中保存
- extensionClasses.put(name, clazz);
- }
- } catch (ClassNotFoundException e) {
- log.error(e.getMessage());
- }
- }
-
- }
- } catch (IOException e) {
- log.error(e.getMessage());
- }
- }
- kyro=github.javaguide.serialize.kyro.KryoSerializer
- protostuff=github.javaguide.serialize.protostuff.ProtostuffSerializer
- hessian=github.javaguide.serialize.hessian.HessianSerializer
Η κλήση της μεθόδου επίπεδο-προς-στρώμα πραγματοποιεί τη διαδικασία φόρτωσης του αντίστοιχου αρχείου διαμόρφωσης SPI κάτω από τη διαδρομή META-INF/extensions/για τη φόρτωση του αντικειμένου Class και τη λήψη του στιγμιότυπου.
Θα πρέπει να σημειωθεί ότι το όνομα αρχείου στο META-INF/extensions/ πρέπει να είναι συνεπές με τον κώδικα Το όνομα αρχείου που καθορίζεται στον κώδικα είναι το πλήρες όνομα της κλάσης της κλάσης διασύνδεσης SPI. Το περιεχόμενο του αρχείου πρέπει επίσης να γραφτεί σύμφωνα με (αναγνωριστικό κλάσης υλοποίησης = πλήρες όνομα κλάσης της κλάσης υλοποίησης), ώστε να μπορεί να συνάδει με τον κώδικα και το πρόγραμμα να αναλύει σωστά το αρχείο και να χρησιμοποιεί τον φορτωτή κλάσης για να φορτώστε την αντίστοιχη Τάξη. Τέλος, κρυφή μνήμη σύμφωνα με <αναγνωριστικό κλάσης υλοποίησης, κλάση υλοποίησης αντικείμενο κλάσης>.
Λόγω της ύπαρξης των τριών κρυφών μνήμων (αναγνωριστικό κλάσης, αντικείμενο κλάσης), (αντικείμενο κλάσης, παρουσία αντικειμένου) και (αναγνωριστικό κλάσης, παρουσία αντικειμένου), μπορείτε να περάσετε απευθείας στο αναγνωριστικό για να αποκτήσετε την παρουσία της αντίστοιχης κλάσης, η οποία βελτιστοποιεί επίσης την απόδοση του RPC.