SPI
全称是 Service Provider Interface
,是一种 JDK
内置的动态加载实现扩展点的机制,通过 SPI
Technologie Nous pouvons obtenir dynamiquement la classe d'implémentation de l'interface sans la créer nous-mêmes. Il ne s’agit pas d’une technologie particulière, mais simplement d’un concept de design.
Java SPI est en fait un mécanisme de chargement dynamique implémenté par une combinaison de programmation basée sur l'interface + mode stratégie + fichiers de configuration.
Il existe souvent de nombreuses solutions d'implémentation différentes pour chaque abstraction de la conception de systèmes. Dans la conception orientée objet, il est généralement recommandé que les modules soient programmés en fonction des interfaces et que les classes d'implémentation ne soient pas codées en dur entre les modules. Si une classe d'implémentation spécifique est référencée dans le code, le principe de connectabilité est violé. Afin de mettre en œuvre le remplacement, le code doit être modifié. Un mécanisme de découverte de services est nécessaire pour activer l'assemblage de modules sans le spécifier dynamiquement dans le programme.
Java SPI fournit un mécanisme pour trouver des implémentations de services liées à une certaine interface. Dans la conception modulaire, un mécanisme similaire à l'idée du CIO est largement utilisé, c'est-à-dire que le contrôle de l'assemblage des composants est transféré en dehors du programme. L’idée centrale du SPI est donc le découplage.
L'appelant active, étend ou remplace la stratégie d'implémentation du framework en fonction des besoins réels
Voici quelques scénarios utilisant ce mécanisme
Pilote JDBC, chargement des classes de pilote pour différentes bases de données
Spring utilise beaucoup de SPI, tels que : l'implémentation de ServletContainerInitializer dans la spécification servlet3.0, la conversion automatique de type Type Conversion SPI (Converter SPI, Formatter SPI), etc.
Dubbo utilise également largement SPI pour implémente le framework Extension, mais il encapsule le SPI natif fourni par Java, permettant aux utilisateurs d'étendre l'implémentation de l'interface Filter
Tomcat charge la classe qui doit être chargée sous META-INF/services
Quand en utilisant l'annotation @SpringBootApplication dans le projet SpringBoot, la configuration automatique démarrera et la configuration de démarrage analysera la classe de configuration sous META-INF/spring.factories
4.1 L'application appelle le ServiceLoader. méthode de chargement
Créez d'abord dans la méthode ServiceLoader.load Un nouveau ServiceLoader et instanciez les variables membres dans la classe
private static final String PREFIX = "META-INF/services/"; private ServiceLoader(Class<S> svc, ClassLoader cl) { service = Objects.requireNonNull(svc, "Service interface cannot be null"); loader = (cl == null) ? ClassLoader.getSystemClassLoader() : cl; acc = (System.getSecurityManager() != null) ? AccessController.getContext() : null; reload(); } /** * * 在调用该方法之后,迭代器方法的后续调用将延迟地从头开始查找和实例化提供程序,就像新创建的加载程序所做的 那样 */ public void reload() { providers.clear(); //清除此加载程序的提供程序缓存,以便重新加载所有提供程序。 lookupIterator = new LazyIterator(service, loader); } private class LazyIterator implements Iterator<S>{ Class<S> service; ClassLoader loader; Enumeration<URL> configs = null; Iterator<String> pending = null; String nextName = null; private boolean hasNextService() { if (nextName != null) { return true; } if (configs == null) { try { //找到配置文件 String fullName = PREFIX + service.getName(); //加载配置文件中的内容 if (loader == null) configs = ClassLoader.getSystemResources(fullName); else configs = loader.getResources(fullName); } catch (IOException x) { fail(service, "Error locating configuration files", x); } } while ((pending == null) || !pending.hasNext()) { if (!configs.hasMoreElements()) { return false; } //解析配置文件 pending = parse(service, configs.nextElement()); } //获取配置文件中内容 nextName = pending.next(); return true; } } /** * * 通过反射 实例化配置文件中的具体实现类 */ private S nextService() { if (!hasNextService()) throw new NoSuchElementException(); String cn = nextName; nextName = null; Class<?> c = null; try { c = Class.forName(cn, false, loader); } catch (ClassNotFoundException x) { fail(service, "Provider " + cn + " not found"); } if (!service.isAssignableFrom(c)) { fail(service, "Provider " + cn + " not a subtype"); } try { S p = service.cast(c.newInstance()); providers.put(cn, p); return p; } catch (Throwable x) { fail(service, "Provider " + cn + " could not be instantiated", x); } throw new Error(); // This cannot happen }
Étape 1 Créez la classe suivante
public interface IService { /** * 获取价格 * @return */ String getPrice(); /** * 获取规格信息 * @return */ String getSpecifications(); }
public class GoodServiceImpl implements IService { @Override public String getPrice() { return "2000.00元"; } @Override public String getSpecifications() { return "200g/件"; } }
public class MedicalServiceImpl implements IService { @Override public String getPrice() { return "3022.12元"; } @Override public String getSpecifications() { return "30粒/盒"; } }
Étape 2, créez / META-INF sous le répertoire src/main/resources/ /services, ajoutez un nouveau fichier nommé org.example.IService.txt nommé d'après l'interface. Le contenu est la classe d'implémentation à appliquer. Les données que je dois saisir sont les suivantes
org.example.GoodServiceImpl
org.example.MedicalServiceImpl
Étape 3, utilisez ServiceLoader pour charger l'implémentation spécifiée dans le fichier de configuration.
public class Main { public static void main(String[] args) { final ServiceLoader<IService> serviceLoader = ServiceLoader.load(IService.class); serviceLoader.forEach(service -> { System.out.println(service.getPrice() + "=" + service.getSpecifications()); }); } }
Rendement :
2000,00 yuans = 200 g/pièce
3022,12 yuans = 30 capsules/boîte
Le découplage rend la logique de contrôle d'assemblage du troisième -module de service de fête et l'appelant En séparant le code métier plutôt qu'en le couplant, les applications peuvent activer des extensions de framework ou remplacer des composants de framework en fonction des conditions métier réelles. Par rapport à la méthode consistant à fournir un package jar d'interface pour les modules de service tiers afin d'implémenter l'interface, la méthode SPI permet au framework source de ne pas avoir à se soucier du chemin de la classe d'implémentation de l'interface
Bien que ServiceLoader peut également être considéré comme un chargement paresseux, mais fondamentalement, il ne peut être obtenu que par traversée, c'est-à-dire que toutes les classes d'implémentation de l'interface sont chargées et instanciées. Si certaines classes d'implémentation sont chargées et instanciées mais que vous n'avez pas besoin de les utiliser, des ressources sont gaspillées. La manière d'obtenir une classe d'implémentation spécifique est trop limitée. Elle ne peut être obtenue que sous la forme d'un itérateur, et la classe d'implémentation correspondante ne peut pas être obtenue sur la base de paramètres spécifiques. Il est dangereux pour plusieurs multithreads simultanés d'utiliser des instances de. la classe ServiceLoader.
Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!