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

Original author: Erik Englund
  • Transfer
Salute, friends. This Friday will be the first lesson in the new Java Developer course group . It is to this course that the current publication will be devoted.



Many of the java developers use Spring 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, then this is very similar to 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 implementation of the interface:

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 bean in a Spring context.

And our main class.

@SpringBootApplication
public class SpringExample implements CommandLineRunner {
    private static final Logger log = 
            LoggerFactory.getLogger(SpringExample.class);
   @Autowired
   List 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]));
       }
   }
}

Note the annotation @Autowiredon the combo box SimpleService. Annotation is @SpringBootApplicationintended for automatic search for beans 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” an instance of the service using the annotation @AutoService. This annotation is needed only during compilation, since javac uses it to automatically generate a service registration file ( Translator's note: for a maven dependency containing @AutoService, specify 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). A file can have any number of implementations, each on a separate line.

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

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

The method ServiceLoader.loadis called to get ServiceLoader, which can be used to get instances of the service. A ServiceLoader instance implements an interface Iterablefor a service type, therefore, a variable servicescan be used in a loop for each.

So what?


Both methods are relatively small. Both can be used with annotations and are therefore fairly 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 compare with 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 ServiceLoader has NO dependencies. That's right, he only needs Java.

It doesn't really matter if you are developing your Spring-based application, but if you are writing something that will be used in many different frameworks or if you have a small console application, this can already be of great importance.

Speed


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

Memory


Spring is not famous for saving memory. If memory usage is important to you, then consider using 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. ServiceLoader is a mechanism that allows external code to “access” internal implementations. Java modules allow you to register services for internal implementations, while preserving the border.

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 this is not compatible with Java modules. Even reflection cannot look into the modules (unless you allow it, but why do you need to allow it).

Conclusion


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

Full source code for examples in my Git Repo .

That's all. See you on the course !

Also popular now: