In the Java world, libraries are created to separate the code for cross-cutting-concerns and packaged inside jars. If a project declares these libraries as Maven
or Gradle
dependencies, it can use the functionality provided by the library as-is.
What if, the project does want to use the library functionality, but with a small change in some behavior out of full functionality?
While designing the library, programmers carefully decide what behaviors can be modified. These small behavior changes are called extensions. It can not be achieved if the library is not coded to accommodate extensions.
In this post, we will see, How to create extensible libraries
.
(SPI) Service Provider Interface Pattern.
A Service is a set of Interfaces and a ServiceProvider is the concrete implementation of a Service. The service provider can be loaded at runtime by adding them to the application’s classpath.
ServiceLoader
Java-1.6 exposed a final & Iterable class ServiceLoader
. This class has a mechanism to search & load instances (ServiceProviders)
that implement a given interface (Service)
. Extensible libraries are not the only but a highly common use case for ServiceLoader
In the below example, we use ServiceLoader to load the bean into a collection using plain java. Similar to what spring does in the spring context.
Code
1. Create a maven project
Open your favorite command line tool and run the following command.
mvn archetype:generate \
-DgroupId=com.mps.app \
-DartifactId=greeting \
-DarchetypeArtifactId=maven-archetype-quickstart \
-DarchetypeVersion=1.4 \
-DinteractiveMode=false
2. Prepare for coding
Open this project in your favorite IDE & fix POM
NOTE: maven quickstart archetype is not up to date, it defaults to older versions. So you should update the pom file to have a newer java version under <maven.compiler.source>
& <maven.compiler.target>
.
Also, if you want, for easy understanding you can delete the build/plugin sections from this autogenerated pom. in that case maven
will pick defaults.
2. Create an interface (Service)
package com.mps.app;
public interface Greeting {
String helloGreeting();
}
3. Add an extension mechanism
Create a class Greeter
with the below code
package com.mps.app;
import java.util.Iterator;
import java.util.ServiceLoader;
public class Greeter {
public static void greet() {
Greeting greetingProvider = getGreetingProvider();
String greeting = (greetingProvider != null)
? greetingProvider.helloGreeting()
: getDefaultHello();
System.out.println("Greeting = " + greeting);
}
private static Greeting getGreetingProvider() {
ServiceLoader<Greeting> loader
= ServiceLoader.load(Greeting.class);
Iterator<Greeting> iterator = loader.iterator();
if (iterator.hasNext()) {
return iterator.next();
}
return null;
}
private String getDefaultHello() {
return "Hello World!";
}
}
Take a closer look at the method getGreetingProvider
.
This method searches for all Greeting interface implementations and returns the first one. if any.
4. Main class
For clarity's sake, rename the App.java file created by maven to GreetApp.java
Use the below code to call Greeter
package com.mps.app;
public class GreetApp {
public static void main(String[] args) {
new GreetingInvoker().invokeGreeting();
}
}
When you run GreetApp
, it prints Greeting = Hello world!
5. Install the library
Let’s package this code into a library. And to make this library available for other projects on our local machines, let’s install it in our local m2.
Run below one command for doing both.
mvn clean install
Close your IDE, we are done with this project.
Now we’ve completed creating our extensible library. let's see how to use it.
Create another maven project.
mvn archetype:generate \
-DgroupId=com.mps.app \
-DartifactId=special-greetings \
-DarchetypeArtifactId=maven-archetype-quickstart \
-DarchetypeVersion=1.4 \
-DinteractiveMode=false
2. Open this in your favorite IDE and fix the POM file as previously stated.
3. Add the library as a dependency
Add the below line under the <dependencies>
section in pom
<dependency>
<groupId>com.mps.app</groupId>
<artifactId>greeting</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
4. Use library functionality
Add this piece of code in App.java
created by Maven
package com.mps.app;
public class App {
public static void main(String[] args) {
new Greeter().greet();
}
}
When you run this program, it prints Greeting = Hello world!
This is a default functionality provided by this library.
However. we envisioned, that different applications might want to greet in different manners. So we provided an extension point in our library.
5. Create out ServiceProvider
To use the extensibility of our library, Create a class SpecialGreeting
implementing the Greeting
interface as below.
package com.mps.app;
public class SpecialGreeting implements Greeting{
@Override
public String helloGreeting() {
return “Special Hello !";
}
}
Now when you run App.java what happens?
It prints Greeting = Hello world !
not Greeting = Special Hello!
6. Special Handling for ServiceLoader
Unlike Spring ContextLoader, java ServiceLoader doesn’t scan the whole classpath without instructions. And the instructions are,
- The file that is placed inside the folder META-INF/services/
- The filename should be the fully qualified name of the Service (interface)
- The file content should be the fully qualified name of ServiceProvider/s.
As we are working on a maven project, everything inside the resources
folder is packaged under the classpath. So let's create folder resources
and add a file com.mps.app.Greeting
in META-INF/services/.
Refer below picture
Now when you run App.java It prints, Greeting = Special Hello!
This is how we can use the ServiceLoader class to create Extensible libraries.
With spring this can be achieved in a lot easier fashion, but in that case, you will be tied to a framework. In my SpringBoot Magic post, I’ve explained the custom starter projects which achieve similar functionality. but code is highly coupled to spring.
If you learned something new, feel free to show your response as a comment or clap on this post.
Comments