2024-07-12
한어Русский языкEnglishFrançaisIndonesianSanskrit日本語DeutschPortuguêsΕλληνικάespañolItalianoSuomalainenLatina
SPI signifie Service Provider Interface, qui peut être compris comme une interface spécifiquement fournie aux fournisseurs de services ou aux développeurs qui étendent les fonctions du framework. SPI sépare l'interface de service de l'implémentation de service spécifique, dissocie l'appelant du service et l'implémenteur de service, et peut améliorer l'évolutivité et la maintenabilité du programme. La modification ou le remplacement d'une implémentation de service ne nécessite pas de modifier l'appelant. De nombreux frameworks utilisent le mécanisme SPI de Java, tels que : le framework Spring, le pilote de chargement de base de données, l'interface de journalisation et l'implémentation de l'extension Dubbo, etc. Reportez-vous au mécanisme SPI de Dubbo pour implémenter la partie SPI de ce framework RPC.
Par exemple, lorsque le client communique avec le serveur, il doit sérialiser le message. Il existe de nombreux algorithmes de sérialisation qui peuvent être utilisés lors de la sérialisation, notamment Hessian, Kryo et ProtoStuff. L'exigence du système est d'appeler la méthode dans la classe correspondant à l'algorithme de sérialisation pertinent en fonction du nom de l'algorithme de sérialisation dans le message pour effectuer la sérialisation et la désérialisation. De plus, afin de faciliter l'expansion, SPI doit être utilisé pour le découplage. .
SPI est utilisé comme suit :
- Serializer serializer = ExtensionLoader.getExtensionLoader(Serializer.class)
- .getExtension(codecName);
codecName est le nom de l'algorithme de sérialisation et la classe correspondante doit être chargée selon ce nom.
- 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;
- }
Chaque interface SPI a son propre ExtensionLoader. Lors de l'appel de getExtensionLoader, elle effectuera d'abord une série d'opérations de vérification légale, puis essaiera d'obtenir l'ExtensionLoader de l'interface. Essayez d'abord de l'obtenir à partir du cache local CHM. obtenu, créez un objet Loader.
Après cela, l'instance est obtenue via getExtension et l'instance est également mise en cache localement. Si elle n'est pas dans le cache, créez à nouveau l'instance.
- 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;
- }
Après avoir obtenu l'objet Class de la classe, vous pouvez créer cet objet par réflexion.
- // 缓存
- 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;
- }
La clé est le processus d'obtention de l'objet Class, c'est-à-dire la méthode 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
L'appel de méthode couche par couche réalise le processus de chargement du fichier de configuration SPI correspondant sous le chemin META-INF/extensions/ pour charger l'objet Class et obtenir l'instance. Veuillez vous référer aux commentaires pour les parties importantes.
Il convient de noter que le nom de fichier sous META-INF/extensions/ doit être cohérent avec le code. Le nom de fichier spécifié dans le code est le nom complet de la classe d'interface SPI. Le contenu du fichier doit également être écrit conformément à (identifiant de classe d'implémentation = nom complet de la classe d'implémentation), afin qu'il puisse être cohérent avec le code, et que le programme puisse analyser correctement le fichier et utiliser le chargeur de classe pour charger la classe correspondante. Enfin, mettez en cache selon <identifiant de classe d'implémentation, objet de classe de classe d'implémentation>.
Grâce à l'existence des trois caches (identifiant de classe, objet Classe), (objet Classe, instance d'objet) et (identifiant de classe, instance d'objet), vous pouvez directement transmettre l'identifiant pour obtenir l'instance de la classe correspondante, ce qui optimise également les performances du framework RPC.