ServiceLoader: Ein integriertes DI-Framework, von dem Sie vielleicht noch nie gehört haben

Gruß, Freunde. Dieser Freitag ist die erste Lektion in der neuen Java Developer- Kursgruppe. Diesem Kurs wird die aktuelle Publikation gewidmet.



Viele der Java-Entwickler verwenden Spring , um Abhängigkeiten zu implementieren. Einige haben möglicherweise Google Guice oder sogar OSGi Services ausprobiert. Viele wissen jedoch nicht, dass Java bereits über eine integrierte DI verfügt. Glaubst du, es ist in Java 11 oder 12 erschienen? Nein, es ist mit Java 6 verfügbar.

ServiceLoader bietet die Möglichkeit, registrierte Instanzen von Schnittstellen oder abstrakten Klassen zu suchen und zu erstellen. Wenn Sie mit Spring vertraut sind, ist dies den Anmerkungen zu Bean und Autowired sehr ähnlich. Schauen wir uns Beispiele für die Verwendung von Spring und ServiceLoader an. Und diskutieren Sie die Ähnlichkeiten und Unterschiede.

Frühling


Lassen Sie uns zunächst sehen, wie Sie im Frühjahr eine einfache DI erstellen. Erstellen Sie eine einfache Schnittstelle:

public interface SimpleService { String echo(String value); } 

Und die Implementierung der Schnittstelle:

 import org.springframework.stereotype.Component; @Component public class SimpleServiceImpl implements SimpleService { public String echo(final String value) { return value; } } 

Schauen Sie sich @Component . Diese Anmerkung registriert unsere Klasse als Bean in einem Spring-Kontext.

Und unsere Hauptklasse.

 @SpringBootApplication public class SpringExample implements CommandLineRunner { private static final Logger log = LoggerFactory.getLogger(SpringExample.class); @Autowired List<SimpleService> simpleServices; public static void main(String[] args) { SpringApplication.run(SpringExample.class, args); } public void run(final String... strings) throws Exception { for (SimpleService simpleService : simpleServices) { log.info("Echo: " + simpleService.echo(strings[0])); } } } 

Beachten Sie die Annotation SimpleService Kombinationsfeld SimpleService . Anmerkung @SpringBootApplication zur automatischen Suche nach Beans in einem Paket. Beim Start werden sie dann automatisch in SpringExample .

Serviceloader


Wir werden dieselbe Schnittstelle wie im Spring-Beispiel verwenden, daher werden wir sie hier nicht wiederholen. Schauen Sie sich stattdessen sofort die Implementierung des Dienstes an:

 import com.google.auto.service.AutoService; @AutoService(SimpleService.class) public class SimpleServiceImpl implements SimpleService { public String echo(final String value) { return value; } } 

In der Implementierung „registrieren“ wir eine Instanz des Dienstes mithilfe der Annotation @AutoService . Diese Anmerkung wird nur zur Kompilierungszeit benötigt, da javac sie verwendet, um automatisch eine Dienstregistrierungsdatei zu generieren ( Anmerkung des Übersetzers: Geben Sie für die Maven-Abhängigkeit, die @AutoService , den angegebenen Bereich an) :

META-INF/services/io.github.efenglu.serviceLoader.example.SimpleService

Diese Datei enthält eine Liste von Klassen, die den Dienst implementieren:

io.github.efenglu.serviceLoader.example.SimpleServiceImpl

Der Dateiname muss der vollständige Name des Dienstes (Schnittstelle) sein. Eine Datei kann beliebig viele Implementierungen in einer separaten Zeile enthalten.

In Implementierungen MUSS es einen Konstruktor ohne Parameter geben. Sie können eine solche Datei manuell erstellen, die Verwendung von Anmerkungen ist jedoch viel einfacher. Und die Hauptklasse:

 public class ServiceLoaderExample { public static void main(String [] args) { final ServiceLoader<SimpleService> services = ServiceLoader.load(SimpleService.class); for (SimpleService service : services) { System.out.println("Echo: " + service.echo(args[0])); } } } 

Die ServiceLoader.load Methode wird aufgerufen, um einen ServiceLoader , mit dem Service-Instanzen abgerufen werden können. Die ServiceLoader-Instanz implementiert die Iterable Schnittstelle für den Iterable Daher kann die Iterable in der for each Schleife verwendet werden.

Na und?


Beide Methoden sind relativ klein. Beide können mit Anmerkungen verwendet werden und sind daher recht einfach zu verwenden. Warum also ServiceLoader anstelle von Spring verwenden?

Abhängigkeiten


Schauen wir uns den Abhängigkeitsbaum unseres einfachen Spring-Beispiels an:

 [INFO] -----------< io.github.efenglu.serviceLoader:spring-example >----------- [INFO] Building spring-example 1.0.X-SNAPSHOT [INFO] --------------------------------[ jar ]--------------------------------- [INFO] [INFO] --- maven-dependency-plugin:3.1.1:tree (default-cli) @ spring-example --- [INFO] io.github.efenglu.serviceLoader:spring-example:jar:1.0.X-SNAPSHOT [INFO] +- org.slf4j:slf4j-api:jar:1.7.25:compile [INFO] +- org.springframework:spring-context:jar:4.3.22.RELEASE:compile [INFO] | +- org.springframework:spring-aop:jar:4.3.22.RELEASE:compile [INFO] | +- org.springframework:spring-core:jar:4.3.22.RELEASE:compile [INFO] | | \- commons-logging:commons-logging:jar:1.2:compile [INFO] | \- org.springframework:spring-expression:jar:4.3.22.RELEASE:compile [INFO] +- org.springframework.boot:spring-boot-autoconfigure:jar:1.5.19.RELEASE:compile [INFO] +- org.springframework.boot:spring-boot:jar:1.5.19.RELEASE:compile [INFO] \- org.springframework:spring-beans:jar:4.3.22.RELEASE:compile 

Und vergleichen Sie mit ServiceLoader:

 [INFO] io.github.efenglu.serviceLoader:serviceLoader-example:jar:1.0.X-SNAPSHOT ## Only provided dependencies for the auto service annotation [INFO] \- com.google.auto.service:auto-service:jar:1.0-rc4:provided [INFO] +- com.google.auto:auto-common:jar:0.8:provided [INFO] \- com.google.guava:guava:jar:23.5-jre:provided [INFO] +- com.google.code.findbugs:jsr305:jar:1.3.9:provided [INFO] +- org.checkerframework:checker-qual:jar:2.0.0:provided [INFO] +- com.google.errorprone:error_prone_annotations:jar:2.0.18:provided [INFO] +- com.google.j2objc:j2objc-annotations:jar:1.1:provided [INFO] \- org.codehaus.mojo:animal-sniffer-annotations:jar:1.14:provided 

Wenn wir die bereitgestellten Abhängigkeiten nicht berücksichtigen, hat ServiceLoader KEINE Abhängigkeiten. Das stimmt, er braucht nur Java.

Es spielt keine Rolle, ob Sie Ihre Spring-basierte Anwendung entwickeln, aber wenn Sie etwas schreiben, das in vielen verschiedenen Frameworks verwendet wird, oder wenn Sie eine kleine Konsolenanwendung haben, kann dies bereits von großer Bedeutung sein.

Geschwindigkeit


Für Konsolenanwendungen ist die Startzeit von ServiceLoader VIEL kürzer als die von Spring Boot App. Dies ist auf die geringere Anzahl an herunterladbarem Code, das Fehlen von Scannen, das Fehlen von Reflexionen und das Fehlen großer Frameworks zurückzuführen.

Die Erinnerung


Der Frühling ist nicht dafür bekannt, Speicher zu sparen. Wenn Ihnen die Speichernutzung wichtig ist, sollten Sie ServiceLoader für DI verwenden.

Java-Module


Einer der Schlüsselaspekte von Java-Modulen war die Fähigkeit, Klassen in einem Modul vollständig vor Code außerhalb des Moduls zu schützen. ServiceLoader ist ein Mechanismus, mit dem externer Code auf interne Implementierungen zugreifen kann. Mit Java-Modulen können Sie Dienste für interne Implementierungen registrieren und dabei die Grenze beibehalten.

Tatsächlich ist dies der einzige offiziell genehmigte Mechanismus zur Unterstützung der Abhängigkeitsinjektion für Java-Module. Spring und die meisten anderen DI-Frameworks verwenden Reflexion, um ihre Komponenten zu finden und zu verbinden. Dies ist jedoch nicht mit Java-Modulen kompatibel. Selbst Reflexion kann nicht in die Module schauen (es sei denn, Sie erlauben es, aber warum müssen Sie es zulassen).

Fazit


Frühling ist eine tolle Sache. Es hat viel mehr Funktionen als jemals zuvor in ServiceLoader. Es gibt jedoch Situationen, in denen ServiceLoader die richtige Wahl ist. Es ist einfach, klein, schnell und immer verfügbar.

Vollständiger Quellcode für Beispiele in meinem Git Repo .

Das ist alles. Wir sehen uns auf dem Kurs !

Source: https://habr.com/ru/post/de457440/


All Articles