As a software developer, it's not always possible to dive deep into the language you're working with, and sometimes it's difficult to find situations where you need to use advanced or non-trivial features. It's easy to use a magic framework, the hard part is having the necessary knowledge to create it. For this reason, I created the tag #javaunderthehood to gather this content and help other people understand how this magic happens. Feel free to join me with an interesting new topic.
What is the Service Provider Interface (SPI)?
Introduced in Java 6, the Service Provider Interface (SPI) provides the ability to extend application functionality by adding service implementations that can modify, replace or extend the original behavior of an application.
What is ServiceLoader java class?
The ServiceLoader class is the heart of this feature. What does the documentation say about the ServiceLoader class?
A facility to load implementations of a service.
A service is a well-known interface or class for which zero, one, or many service providers exist. A service provider (or just provider) is a class that implements or subclasses the well-known interface or class. A ServiceLoader is an object that locates and loads service providers deployed in the run time environment at a time of an application's choosing. Application code refers only to the service, not to service providers, and is assumed to be capable of choosing between multiple service providers (based on the functionality they expose through the service), and handling the possibility that no service providers are located.
In general, the service is an interface that you can use to implement your own provider. If this service is part of a dependency of your project and this dependency gives you the possibility, you can write your own provider that fits your needs and the library will be able to use it.
How to use it?
Let's start by creating our service, which will be an interface that will allow us to create implementations to proccess different types of images.
public interface ImageProcessorService {
String getType();
String process();
}
Now we will create two implementations of it, which will be responsible for proccessing PNG and JPG images.
PNG
public final class PNGImageProcessorProvider implements ImageProcessorService {
@Override
public String getType() {
return BasicImageType.PNG.name();
}
@Override
public void process() {
System.out.println("Processing PNG image");
}
}
JPG
public class JPGImageProcessorProvider implements ImageProcessorService {
@Override
public String getType() {
return BasicImageType.JPG.name();
}
@Override
public void process() {
System.out.println("Processing JPG image");
}
}
In order for ServiceLoader to load our provider, we need to create a package called META-INF in our resource repository. This folder must contain a subfolder called services where we need to create a file with the reference to our service.
The appearance of our project looks something like this at the moment:
.
├── java
│ └── com
│ └── github
│ └── c4rlosmonteiro
│ └── imageprocessorservicespi
│ ├── BasicImageType.java
│ ├── provider
│ │ ├── JPGImageProcessorProvider.java
│ │ └── PNGImageProcessorProvider.java
│ └── service
│ └── ImageProcessorService.java
└── resources
└── META-INF
└── services
└── com.github.c4rlosmonteiro.imageprocessorservicespi.service.ImageProcessorService
Now you need to modify the file com.github.c4rlosmonteiro.imageprocessorservicespi.service.ImageProcessorService and add the references to all the providers you want to load.
com.github.c4rlosmonteiro.imageprocessorservicespi.provider.JPGImageProcessorProvider
com.github.c4rlosmonteiro.imageprocessorservicespi.provider.PNGImageProcessorProvider
To see the result of our implementation, we need to finally use the ServiceLoader class to load our providers.
public class ImageProcessorApp {
private final List<ImageProcessorService> imageProcessorProviders;
public ImageProcessorApp() {
final ServiceLoader<ImageProcessorService> imageProcessorServiceLoader = ServiceLoader.load(ImageProcessorService.class);
final Iterator<ImageProcessorService> imageProcessorServiceIterator = imageProcessorServiceLoader.iterator();
this.imageProcessorProviders = new ArrayList<>();
while (imageProcessorServiceIterator.hasNext()) {
imageProcessorProviders.add(imageProcessorServiceIterator.next());
}
}
public void processImages(final String type) {
for (final ImageProcessorService imageProcessorProvider : imageProcessorProviders) {
if (Objects.equals(imageProcessorProvider.getType(), type)) {
imageProcessorProvider.process();
return;
}
}
throw new RuntimeException("No provider found to process image type: " + type + ". Create a new provider for it!");
}
}
Finally, we create a simple main class and execute our code.
public class Main {
public static void main(final String[] args) {
final ImageProcessorApp imageProcessorApp = new ImageProcessorApp();
imageProcessorApp.processImages(BasicImageType.PNG.name());
imageProcessorApp.processImages(BasicImageType.JPG.name());
imageProcessorApp.processImages("MY_CUSTOM_OR_NOT_SUPPORTED_IMAGE_TYPE");
}
}
Run and check the logs.
Processing PNG image
Processing JPG image
Exception in thread "main" java.lang.RuntimeException: No provider found to process image type: MY_CUSTOM_OR_NOT_SUPPORTED_IMAGE_TYPE. Create a new provider for it!
at com.github.c4rlosmonteiro.imageprocessorservicespi.ImageProcessorApp.processImages(ImageProcessorApp.java:30)
at com.github.c4rlosmonteiro.imageprocessorservicespi.Main.main(Main.java:8)
Check the source code on github.com.
Before you go!
Follow for more ;)
Suggest new topics
Use the comments section to discuss the feature and >comment on real-world use cases to help others understand >its usage
#javaunderthehood
Top comments (0)