Heim  >  Artikel  >  Java  >  Was ist der SPI-Mechanismus in Java?

Was ist der SPI-Mechanismus in Java?

王林
王林nach vorne
2023-05-17 23:40:553157Durchsuche

1: Einführung in den SPI-Mechanismus

SPI 全称是 Service Provider Interface,是一种 JDK 内置的动态加载实现扩展点的机制,通过 SPITechnologie Wir können die Implementierungsklasse der Schnittstelle dynamisch erhalten, ohne sie selbst zu erstellen. Dabei handelt es sich nicht um eine spezielle Technologie, sondern lediglich um ein Designkonzept.

2: SPI-Prinzip

Was ist der SPI-Mechanismus in Java?

Java SPI ist eigentlich ein dynamischer Lademechanismus, der durch eine Kombination aus schnittstellenbasierter Programmierung + Strategiemodus + Konfigurationsdateien implementiert wird.

Für jede Abstraktion des Systemdesigns gibt es oft viele verschiedene Implementierungslösungen. Beim objektorientierten Design wird im Allgemeinen empfohlen, Module basierend auf Schnittstellen zu programmieren, und Implementierungsklassen sollten nicht zwischen Modulen fest codiert werden. Wenn im Code auf eine bestimmte Implementierungsklasse verwiesen wird, wird das Prinzip der Steckbarkeit verletzt. Um die Ersetzung zu implementieren, muss der Code geändert werden. Ein Diensterkennungsmechanismus ist erforderlich, um die Modulassemblierung zu ermöglichen, ohne sie dynamisch im Programm anzugeben.

Java SPI bietet einen Mechanismus zum Finden von Dienstimplementierungen, die sich auf eine bestimmte Schnittstelle beziehen. Im modularen Design wird häufig ein Mechanismus verwendet, der der IOC-Idee ähnelt, d. h. die Montagesteuerung von Komponenten wird außerhalb des Programms übertragen. Die Kernidee von SPI ist also die Entkopplung.

3: Nutzungsszenarien

Der Aufrufer aktiviert, erweitert oder ersetzt die Implementierungsstrategie des Frameworks entsprechend den tatsächlichen Anforderungen

Im Folgenden sind einige Szenarien aufgeführt, in denen dieser Mechanismus verwendet wird: JDBC-Treiber, der Treiberklassen für verschiedene Datenbanken lädt

  • Spring verwendet viele SPI, wie zum Beispiel: die Implementierung von ServletContainerInitializer in der Servlet3.0-Spezifikation, automatische Typkonvertierung, Typkonvertierung SPI (Converter SPI, Formatter SPI) usw.

  • Dubbo verwendet SPI auch häufig, um Implementieren Sie das Framework. Erweiterung, aber es kapselt das von Java bereitgestellte native SPI, sodass Benutzer die Implementierung der Filterschnittstelle erweitern können. Tomcat lädt die Klasse, die unter META-INF/services geladen werden muss Mithilfe der Annotation @SpringBootApplication im SpringBoot-Projekt wird die automatische Konfiguration gestartet und die Startkonfiguration scannt die Konfigurationsklasse unter META-INF/spring.factories

  • 4: Quellcode-Demonstration

    4.1 Die Anwendung ruft den ServiceLoader auf. Lademethode
  • Erstellen Sie zuerst in der ServiceLoader.load-Methode einen neuen ServiceLoader und instanziieren Sie die Mitgliedsvariablen in der Klasse

        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: Praktischer Kampf
  • Schritt 1

    Erstellen Sie die folgende Klasse
  • 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粒/盒";
        }
    }

Schritt 2

, erstellen / Fügen Sie mit META-INF im Verzeichnis src/main/resources/ /services eine neue Datei mit dem Namen org.example.IService.txt hinzu, die nach der Schnittstelle benannt ist. Der Inhalt ist die anzuwendende Implementierungsklasse. Die Daten, die ich eingeben muss, lauten wie folgt:

org.example.GoodServiceImpl

org.example.MedicalServiceImpl

Schritt 3: Verwenden Sie ServiceLoader, um die in angegebene Implementierung zu laden die Konfigurationsdatei.

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());
        });
    }
}

Ausgabe:

2000,00 Yuan = 200g/Stück
3022,12 Yuan = 30 Kapseln/Karton


6: Vor- und Nachteile

6.1 Vorteile Durch die Entkopplung wird die Montage logisch gesteuert des Drittanbieter-Servicemoduls und Der Aufrufer Durch die Trennung von Geschäftscode statt dessen Kopplung können Anwendungen Framework-Erweiterungen ermöglichen oder Framework-Komponenten basierend auf tatsächlichen Geschäftsbedingungen ersetzen. Verglichen mit der Methode, ein Schnittstellen-JAR-Paket für Servicemodule von Drittanbietern zur Implementierung der Schnittstelle bereitzustellen, ermöglicht die SPI-Methode, dass sich das Quell-Framework nicht um den Pfad der Schnittstellenimplementierungsklasse kümmern muss

6.2 Nachteile


Allerdings ServiceLoader kann auch als verzögertes Laden angesehen werden. Grundsätzlich kann es jedoch nur durch Durchqueren erhalten werden, dh alle Implementierungsklassen der Schnittstelle werden geladen und instanziiert. Wenn einige Implementierungsklassen geladen und instanziiert werden, Sie sie aber nicht verwenden müssen, werden Ressourcen verschwendet. Die Möglichkeit, eine bestimmte Implementierungsklasse abzurufen, ist zu begrenzt. Sie kann nur in Form eines Iterators abgerufen werden, und die entsprechende Implementierungsklasse kann nicht basierend auf bestimmten Parametern abgerufen werden. Die Verwendung von Instanzen von mehreren gleichzeitigen Threads ist unsicher die ServiceLoader-Klasse.

Das obige ist der detaillierte Inhalt vonWas ist der SPI-Mechanismus in Java?. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!

Stellungnahme:
Dieser Artikel ist reproduziert unter:yisu.com. Bei Verstößen wenden Sie sich bitte an admin@php.cn löschen