ServiceLoader: A built-in DI framework that you may never have heard of
- 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.
First, let's see how to make a simple DI in Spring. Create a simple interface:
And the implementation of the interface:
Pay attention to
And our main class.
Note the annotation
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:
In the implementation, we “register” an instance of the service using the annotation
This file contains a list of classes that implement the service:
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:
The method
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?
Let's look at the dependency tree of our simple Spring example:
And compare with ServiceLoader:
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.
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.
Spring is not famous for saving memory. If memory usage is important to you, then consider using ServiceLoader for DI.
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).
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 !
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
@Autowired
on the combo box SimpleService
. Annotation is @SpringBootApplication
intended 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.load
is called to get ServiceLoader
, which can be used to get instances of the service. A ServiceLoader instance implements an interface Iterable
for a service type, therefore, a variable services
can 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 !