Maison >Java >javaDidacticiel >Qu'est-ce que le mécanisme SPI en Java

Qu'est-ce que le mécanisme SPI en Java

王林
王林avant
2023-05-17 23:40:553289parcourir

1 : Introduction au mécanisme SPI

SPI 全称是 Service Provider Interface,是一种 JDK 内置的动态加载实现扩展点的机制,通过 SPITechnologie 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.

2 : Principe SPI

Quest-ce que le mécanisme SPI en Java

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.

3 : Scénarios d'utilisation

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 : Démonstration du code source

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
        }

5 : Combat pratique

É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

6 : Avantages et inconvénients

6.1 Avantages

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

6.2 Inconvénients

  • 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!

Déclaration:
Cet article est reproduit dans:. en cas de violation, veuillez contacter admin@php.cn Supprimer