📜 ⬆️ ⬇️

ServiceLoader: a built-in DI framework you may have never heard of

Salute, friends. Already this Friday will be the first lesson in the new group of the Java Developer course. The current publication will be devoted to this course.



Many of the java developers use dependencies to implement dependencies. Some may have tried Google Guice or even OSGi Services . But many do not know that Java already has a built-in DI. Do you think it appeared in Java 11 or 12? No, it is available with Java 6.
')
ServiceLoader provides the ability to search and create registered instances of interfaces or abstract classes. If you are familiar with Spring, this is very similar to the Bean and Autowired annotations . Let's look at examples of using Spring and ServiceLoader. And discuss the similarities and differences.

Spring


First, let's see how to make a simple DI in Spring. Create a simple interface:

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

And the interface implementation:

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

Pay attention to @Component . This annotation will register our class as a bin in the Spring context.

And our main class.

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

Notice the @Autowired annotation on the SimpleService combo SimpleService . The @SpringBootApplication annotation @SpringBootApplication designed to automatically search for bins in a package. Then at startup they are automatically injected into SpringExample .

Serviceloader


We will use the same interface as in the Spring example, so we will not repeat it here. Instead, immediately look at the implementation of the service:

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

In the implementation, we “register” the service instance using the @AutoService annotation. This annotation is needed only during compilation, since javac uses it to automatically generate the service registration file ( Translator’s note: for a maven dependency containing @AutoService , specify the scope - provided) :

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

This file contains a list of classes that implement the service:

io.github.efenglu.serviceLoader.example.SimpleServiceImpl

The file name must be the full name of the service (interface). The file may contain any number of implementations, each on a separate line.

In implementations MUST be a constructor without parameters. You can create such a file manually, but using annotation is much easier. And main class:

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

The ServiceLoader.load method is called to get a ServiceLoader , which you can use to get service instances. The ServiceLoader instance implements the Iterable interface for the type of service, therefore, the services variable can be used in a for each loop.

So what?


Both methods are relatively small. Both can be used with annotations and are therefore quite easy to use. So why use ServiceLoader instead of Spring?

Dependencies


Let's look at the dependency tree of our simple Spring example:

 [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 

And comparable to 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 

If we do not pay attention to the provided-dependencies, then the ServiceLoader has NO dependencies. That's right, he only needs java.

This is not a big deal if you are developing your application based on Spring, but if you are writing something that will be used in many different frameworks or you have a small console application, this may already be of great importance.

Speed


For console applications, the startup time of the ServiceLoader is MUCH less than the Spring Boot App. This is due to the smaller amount of downloadable code, the lack of scanning, the lack of reflection, the lack of large frameworks.

Memory


Spring is not famous for saving memory. If it is important for you to use memory, then you should consider using the ServiceLoader for DI.

Java modules


One of the key aspects of Java modules was the ability to fully protect classes in a module from code outside the module. A ServiceLoader is a mechanism that allows an external code to “access” internal implementations. Java modules allow you to register services for internal implementations while maintaining the boundary.

In fact, this is the only officially approved dependency injection support mechanism for Java modules. Spring and most other DI frameworks use reflection to find and connect their components. But it is not compatible with Java modules. Even reflection cannot look into the modules (if you don’t allow it, but why would you allow it).

Conclusion


Spring is a great thing. It has much more functionality than it ever will be in the ServiceLoader. But there are times when ServiceLoader will be the right choice. It is simple, small, fast and always available.

The full source code for the examples in my Git Repo .

That's all. See you on the course !

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


All Articles