ServiceLoader: un framework DI intégré dont vous n'avez peut-être jamais entendu parler

Salut, mes amis. Ce vendredi sera la première leçon du nouveau groupe de cours pour développeurs Java . C'est à ce cours que sera consacrée la publication actuelle.



De nombreux développeurs java utilisent Spring pour implémenter des dépendances. Certains ont peut-être essayé Google Guice ou même les services OSGi . Mais beaucoup ne savent pas que Java a déjà une DI intégrée. Pensez-vous qu'il est apparu en Java 11 ou 12? Non, il est disponible avec Java 6.

ServiceLoader offre la possibilité de rechercher et de créer des instances enregistrées d'interfaces ou de classes abstraites. Si vous êtes familier avec Spring, cela ressemble beaucoup aux annotations Bean et Autowired . Regardons des exemples d'utilisation de Spring et ServiceLoader. Et discutez des similitudes et des différences.

Printemps


Voyons d'abord comment faire une DI simple au printemps. Créez une interface simple:

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

Et l'implémentation de l'interface:

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

Découvrez @Component . Cette annotation enregistrera notre classe en tant que bean dans un contexte Spring.

Et notre classe principale.

 @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])); } } } 

Notez l'annotation @Autowired dans la SimpleService liste déroulante SimpleService . Annotation @SpringBootApplication conçu pour rechercher automatiquement les beans dans un package. Puis au démarrage, ils sont automatiquement injectés dans SpringExample .

Serviceloader


Nous utiliserons la même interface que dans l'exemple Spring, nous ne la répéterons donc pas ici. Au lieu de cela, regardez immédiatement la mise en œuvre du service:

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

Dans l'implémentation, nous «enregistrons» une instance du service en utilisant l'annotation @AutoService . Cette annotation n'est nécessaire qu'au moment de la compilation, car javac l'utilise pour générer automatiquement un fichier d'enregistrement de service ( Note du traducteur: pour la dépendance @AutoService contenant @AutoService , spécifiez la portée - fournie) :

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

Ce fichier contient une liste de classes qui implémentent le service:

io.github.efenglu.serviceLoader.example.SimpleServiceImpl

Le nom de fichier doit être le nom complet du service (interface). Un fichier peut avoir n'importe quel nombre d'implémentations, chacune sur une ligne distincte.

Dans les implémentations, il DOIT y avoir un constructeur sans paramètres. Vous pouvez créer un tel fichier manuellement, mais l'utilisation d'annotations est beaucoup plus facile. Et la classe principale:

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

La méthode ServiceLoader.load est appelée pour obtenir un ServiceLoader , qui peut être utilisé pour obtenir des instances de service. L'instance ServiceLoader implémente l'interface Iterable pour le type de service, par conséquent, la variable services peut être utilisée dans le for each boucle.

Et alors?


Les deux méthodes sont relativement petites. Les deux peuvent être utilisés avec des annotations et sont donc assez faciles à utiliser. Alors pourquoi utiliser ServiceLoader au lieu de Spring?

Dépendances


Regardons l'arbre de dépendance de notre simple exemple Spring:

 [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 

Et comparez avec 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 

Si nous ne prêtons pas attention aux dépendances fournies, ServiceLoader n'a AUCUNE dépendance. C'est vrai, il n'a besoin que de Java.

Peu importe si vous développez votre application Spring, mais si vous écrivez quelque chose qui sera utilisé dans de nombreux frameworks différents ou si vous avez une petite application console, cela peut déjà être d'une grande importance.

La vitesse


Pour les applications console, le temps de démarrage de ServiceLoader est BEAUCOUP plus court que l'application Spring Boot. Cela est dû au plus petit nombre de codes téléchargeables, à l'absence de numérisation, à l'absence de réflexion, à l'absence de grands cadres.

La mémoire


Le printemps n'est pas réputé pour économiser de la mémoire. Si l'utilisation de la mémoire est importante pour vous, envisagez d'utiliser ServiceLoader for DI.

Modules Java


L'un des aspects clés des modules Java était la capacité de protéger complètement les classes d'un module contre le code extérieur au module. ServiceLoader est un mécanisme qui permet au code externe «d'accéder» aux implémentations internes. Les modules Java vous permettent d'enregistrer des services pour des implémentations internes, tout en préservant la frontière.

En fait, il s'agit du seul mécanisme de prise en charge de l'injection de dépendance officiellement approuvé pour les modules Java. Spring et la plupart des autres infrastructures DI utilisent la réflexion pour trouver et connecter leurs composants. Mais ce n'est pas compatible avec les modules Java. Même la réflexion ne peut pas regarder dans les modules (sauf si vous l'autorisez, mais pourquoi devez-vous l'autoriser).

Conclusion


Le printemps est une bonne chose. Il a beaucoup plus de fonctionnalités que jamais dans ServiceLoader. Mais il y a des moments où ServiceLoader est le bon choix. C'est simple, petit, rapide et toujours disponible.

Code source complet pour des exemples dans mon Git Repo .

C’est tout. Rendez-vous sur le parcours !

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


All Articles