ServiceLoader: un marco DI integrado del que quizás nunca hayas oído hablar

Saludo, amigos. Este viernes será la primera lección en el nuevo grupo de cursos Java Developer . Es a este curso que se dedicará la publicación actual.



Muchos de los desarrolladores de Java usan Spring para implementar dependencias. Algunos pueden haber probado Google Guice o incluso los Servicios OSGi . Pero muchos no saben que Java ya tiene un DI incorporado. ¿Crees que apareció en Java 11 o 12? No, está disponible con Java 6.

ServiceLoader proporciona la capacidad de buscar y crear instancias registradas de interfaces o clases abstractas. Si está familiarizado con Spring, entonces esto es muy similar a las anotaciones Bean y Autowired . Veamos ejemplos de uso de Spring y ServiceLoader. Y discuta las similitudes y diferencias.

Primavera


Primero, veamos cómo hacer una DI simple en primavera. Crea una interfaz simple:

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

Y la implementación de la interfaz:

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

Echa un vistazo a @Component . Esta anotación registrará nuestra clase como un bean en un contexto Spring.

Y nuestra clase principal.

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

Tenga en cuenta la anotación @Autowired en el SimpleService combinado SimpleService . Anotación @SpringBootApplication diseñada para buscar automáticamente beans en un paquete. Luego, al inicio, se inyectan automáticamente en SpringExample .

Cargador de servicios


Usaremos la misma interfaz que en el ejemplo de Spring, por lo que no la repetiremos aquí. En cambio, mire inmediatamente la implementación del servicio:

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

En la implementación, "registramos" una instancia del servicio utilizando la anotación @AutoService . Esta anotación solo es necesaria en el momento de la compilación, ya que javac la usa para generar automáticamente un archivo de registro del servicio ( Nota del traductor: para la dependencia de @AutoService contiene @AutoService , especifique el alcance, provisto) :

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

Este archivo contiene una lista de clases que implementan el servicio:

io.github.efenglu.serviceLoader.example.SimpleServiceImpl

El nombre del archivo debe ser el nombre completo del servicio (interfaz). Un archivo puede tener cualquier cantidad de implementaciones, cada una en una línea separada.

En implementaciones, DEBE haber un constructor sin parámetros. Puede crear dicho archivo manualmente, pero usar la anotación es mucho más fácil. Y la clase principal:

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

Se ServiceLoader.load método ServiceLoader.load para obtener un ServiceLoader , que se puede usar para obtener instancias de servicio. La instancia de ServiceLoader implementa la interfaz Iterable para el tipo de servicio, por lo tanto, la variable de services se puede usar en for each ciclo.

¿Y qué?


Ambos métodos son relativamente pequeños. Ambos se pueden usar con anotaciones y, por lo tanto, son bastante fáciles de usar. Entonces, ¿por qué usar ServiceLoader en lugar de Spring?

Dependencias


Veamos el árbol de dependencias de nuestro sencillo ejemplo de 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 

Y compare con 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 no prestamos atención a las dependencias proporcionadas, entonces ServiceLoader NO tiene dependencias. Así es, solo necesita Java.

Realmente no importa si está desarrollando su aplicación basada en Spring, pero si está escribiendo algo que se utilizará en muchos marcos diferentes o si tiene una pequeña aplicación de consola, esto ya puede ser de gran importancia.

Velocidad


Para las aplicaciones de consola, el tiempo de inicio de ServiceLoader es MUCHO más corto que la aplicación Spring Boot. Esto se debe a la menor cantidad de código descargable, la ausencia de escaneo, la ausencia de reflexión, la ausencia de grandes marcos.

El recuerdo


La primavera no es famosa por ahorrar memoria. Si el uso de la memoria es importante para usted, considere usar ServiceLoader para DI.

Módulos Java


Uno de los aspectos clave de los módulos Java fue la capacidad de proteger completamente las clases en un módulo del código externo al módulo. ServiceLoader es un mecanismo que permite que el código externo "acceda" a implementaciones internas. Los módulos Java le permiten registrar servicios para implementaciones internas, al tiempo que conservan el borde.

De hecho, este es el único mecanismo de soporte de inyección de dependencia aprobado oficialmente para módulos Java. Spring y la mayoría de los otros marcos DI utilizan la reflexión para encontrar y conectar sus componentes. Pero esto no es compatible con los módulos Java. Incluso la reflexión no puede mirar dentro de los módulos (a menos que lo permita, pero ¿por qué necesita permitirlo?).

Conclusión


La primavera es una gran cosa. Tiene mucha más funcionalidad de la que tendrá en ServiceLoader. Pero hay momentos en que ServiceLoader es la elección correcta. Es simple, pequeño, rápido y siempre disponible.

Código fuente completo para ejemplos en mi repositorio de Git .

Eso es todo. ¡Nos vemos en el curso !

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


All Articles