Geschreven door Ian van Nieuwkoop

The ServiceLoader and Native Dependency Injection in Java 11

Development5 minuten leestijd

In this blog, I would like to share something I recently discovered when studying for the Java 11 certification. Strangely enough, it is not a new Java 11 feature. It’s not even something introduced in Java 8 or 7. No, it’s something that's been around since the early days of version 6. It’s the ServiceLoader class. A long-forgotten feature which thanks to the introduction of modules in Java 11 has become especially relevant again.

The ServiceLoader class was first introduced in Java 6. It was used to make applications extensible, so you could add functionality without recompiling the whole application. Basically, the SeviceLoader scans the classpath looking for an implementation of any interface you may want and returns the results, which then can be used at runtime in your code. Essentially, this is a form of dependency injection.

Dependency Injection
What is dependency injection and why is it important? Quality code should adhere to the following design principle:

The separation of creation from use.

This principle is not rocket science. It simply means that an object that uses another object should not create that object, but instead receive the objects it is dependent on. This technique is called dependency injection.  

There are many ways of injecting dependencies into objects.

1 - You could use a framework. There are quite a few dependency injection frameworks Java developers can choose from. For example, Spring Framework or Google Guice are very popular.

2 - However, a framework is not necessary. There are far simpler techniques for achieving the same goal. There are several methods of dependency injection in Java without the need of leveraging third party frameworks and only rely on simple regular everyday code. The simplest are:

  • Constructor injection. The dependencies are injected via constructor parameters.
  • Setter injection. After the object has been initialized, setter-methods are used to add the dependency.
  • Factory method. Similar to the Constructor injection method, however, the constructor is called with the necessary parameters within a method belonging to a Factory class. 
  • Factory injection. This is an improvement to the Factory method. By implementing the Factory Pattern. Basically, all the logic needed to initialize the object with all the necessary dependencies are abstracted away within an independent class, a Factory class. This object internally decides which dependencies are needed.

The main advantages of these methods are that you don’t need to add any external libraries to your build path, nor do you need to use any ‘funny looking’ annotations to tell the framework what needs to be injected and where. It is done in plain old, easy to understand Java code. 

3 - “But I need to dynamically switch between implementations in my code”, I hear you say. Yes, there are some use cases where you may need to load an implementation of a class at runtime. Luckily, that’s not a problem in Java. There is the ServiceLoader class.

As previously discussed, this class has been around since version 6, and can be found in the java.util package. This class will scan through the classpath or (from version 11) through the module path of your application and retrieve the available implementations. Giving you a Java native way of dynamically loading classes and injecting them into objects. Most probably in conjunction with some kind of Factory class to abstract away some of the code.

Module path
Before we dive further into the workings of the ServiceLoader class, I need to discuss the new module system introduced in Java 11.

In layman terms, in Java 11 the classpath has been replaced by a module path. Don’t panic, you can still use a classpath, but this is no longer advised.

The biggest difference between a classpath and the newer module path is that when using a classpath Java searches through all the locations designated on the classpath looking for the class to use. Often, this works fine. However, it is possible to have multiple instances of the same class defined on your path. And because Java has no strict way of traversing through the classpath, your application will randomly use different implementations of a class. Potentially causing all kinds of unpredictable problems, colloquially called “Dependency Hell”.

This has been fixed in the module path way of working. Instead of an unorganized collection of available classes. Classes and packages are now ordered into neatly organized modules. And because modules need to declare which classes they make accessible for other modules and which modules they use; java has the ability to validate all modules loaded at compile time helping to prevent dependency hell.

The ‘exports’ keyword is used for making packages available and the keyword ‘requires’ is used for declaring which modules are used. This information is placed in the file, which can be found in the source folder.

As previously described, the ServiceLoader is able to search the classpath looking for implementations. But how does it work when using a module path? Modules no longer implicitly expose any classes. Therefore, an implementation (also called a Service in Java 11) needs to be exposed to the outside world. This is done by using the keywords ‘provides … with ...’. Also, you need to declare that a module uses an external implementation. This is done by using the ‘uses’ keyword.

Module 1 – The module exposing (exporting) the interface
Module definition

The interface

Module 2 – The module providing the service implementation (doesn't need to be exported!)
Module definition

The concrete class

 Module 3 – The module using the service:
The module definition of a module using a service.

The consuming class

If all the modules have been correctly defined, then using the ServiceLoader is easy. You only really need to know one static method ‘load’ which takes a class as a parameter and returns a collection of Classes. Because the ServiceLoader can retrieve an unknown number of classes, you will have to write some code to determine which class instance (if any) to use.

To quickly summarize. In many cases of injecting dependencies, we don’t need a framework to get the job done. Java out-of-the-box offers us the tools needed to solve the problem of dependency injection.