How to use services in Java Modules?

Java 9 came up with the module system.

This post gives a minimalist explanation of how to create modules in java.

Let’s say you have decided to use modules in your application.

And you are going to buy and sell coconuts using these modules as done in the post highlighted.

And so you have created a buyer module and a seller module.

The seller module exports its API to sell coconuts.

The buyer module uses the seller module’s API to buy coconuts.

Now , consider a requirement change.

The seller module doesn’t directly sell coconuts.

It buys from agents and then sells them to the buyers.

To achieve this it exposes a contract (interface) to the agents.

The agents need to implement this contract to sell coconuts to the seller.

These are done through services.

The seller now becomes a service consumer and the agent becomes a service provider.

The buyer module remains the same , it still buys from the seller and has no idea about the agents.

To implement this in modules :

  • Create an interface in the service consumer module.
  • Create the implementation of the interface in the service provider module
  • Mention the service details in module-info.java of both the modules.

Let’s get into action.

The Seller Module

The Seller Module has three files:

  • module-info.java which contains meta data about the module (which service it wants the agent to implement etc)
  • CoconutAgent.java – this contains the contract exposed to the agents
  • SellCoconuts.java – this exposes a method to the buyer module, through which it locates the service provider ( the agent ) and executes the implementation provided by it, and finally returns the response to the client)

Here is the module-info.java:

module seller{

   exports seller;
   uses seller.CoconutAgent;

}

exports seller – this statement exports the seller API (package) as it needs to be consumed by the buyer module.

uses seller.CoconutAgent – this statement specifies the service it is going to consume from another service provider. The service provider will be loaded dynamically by Java

Here is the service consumer contract:


package seller;


public interface CoconutAgent{

      public String sellCoconuts();

}

As you see it has a single method sellCoconuts() which will be implemented by an agent module (service provider)

Here is the SellCoconuts.java class which is used by the buyer module :


package seller;

import java.util.Iterator;
import java.util.ServiceLoader;

import seller.CoconutAgent;

public class SellCoconuts{


    public String sellCoconuts(){


 ServiceLoader<CoconutAgent> sl
                = ServiceLoader.load(CoconutAgent.class);
            Iterator<CoconutAgent> iter = sl.iterator();
            if (!iter.hasNext())
                throw new RuntimeException("No service providers found!");
CoconutAgent provider = iter.next();
        return provider.sellCoconuts();

	}

}

The method sellCoconuts() will be called by buyer module which has no idea about the agent which is actually going to sell the coconuts.

The class ServiceLoader is used to locate the agent service implementation dynamically.

Here is the piece of code in the above class which fetches the service provider at run time:

 ServiceLoader<CoconutAgent> sl
                = ServiceLoader.load(CoconutAgent.class);
 Iterator<CoconutAgent> iter = sl.iterator();
 if (!iter.hasNext())
                throw new RuntimeException("No service providers found!");
  CoconutAgent provider = iter.next();

Now let’s look at the agent module:

The Agent Module:

This is the service provider.

Here is the module-info.java class:

module agent{

  requires seller;

  provides seller.CoconutAgent with agent.CoconutAgentImpl;

}

As you see it requires seller package from seller module as it contains the contract(interface) to sell coconuts.

In the second statement it provides a service for the contract through provides..with keywords.

Java will dynamically load this service and supply it to the seller module.

If the seller wants another service provider instead of this one , you just remove this module , add another one with the provider details in module-info.java! No code change required in seller module!.

(Instead of deleting this module you could just remove the provides..with statement in module-info.java too)

Now let’s look at the service implementation provided by agent module:


package agent;

import seller.CoconutAgent;

public class CoconutAgentImpl implements CoconutAgent{


    @Override	
    public String sellCoconuts(){

return "Selling Coconuts --( oOOOOoooo)";
}

}

Our seller and agent are ready.

Now let’s look at the buyer who is going to ultimately consume the service

The Buyer Module:

Here is module-info.java file:

module buyer{

    requires seller;

}

As you see it requires only seller package of seller module. It doesn’t need to know anything about the agent module!

Here is the CoconutBuyer.class with the main method:



package buyer;
import seller.SellCoconuts;

public class CoconutBuyer{


   public static void main(String a[]){

         System.out.println(SellCoconuts.sellCoconuts());

}

}

As you see a static method in SellCoconuts.java of seller module is invoked to get the coconuts.

Let’s compile all the modules:

The seller module:

javac -d modules\seller src\seller\module-info.java src\seller\CoconutAgent.java src\seller\SellCoconuts.java

The agent module:

javac --module-path modules -d modules\agent src\agent\module-info.java src\agent\CoconutAgentImpl.java

As you see the module path is included while compiling agent modules as it is dependent on seller module.

Finally , the buyer module:

javac --module-path modules -d modules\buyer src\buyer\module-info.java src\buyer\CoconutBuyer.java

The buyer module is also compiled using the module path included as it is dependent on the seller module .

Now , let’s run the main method in buyer module:

java --module-path modules -m buyer/buyer.CoconutBuyer

I got the below output:

That’s it!

We created a service and consumed it through Java modules.

In real world scenarios each module will likely be exported as a jar file .

So let’s package them as jars and run the application as we did in the post highlighted at the start of the post:

Creating the seller jar:

jar --create --file=jars\seller.jar -C modules\seller .

Creating the agent jar:

jar --create --file=jars\agent.jar -C modules\agent .

Creating the buyer jar (need to specify main class as this jar needs to be executed):

jar --create --file=jars\buyer.jar --main-class=buyer.CoconutBuyer -C modules\buyer .

Now to run the application use the below command:

java -p jars -m buyer

Here is the output I got:

Now if you want a different service implementation for the seller , just remove the agent jar and put another one in the class path with the service details mentioned in module-info.jar of the new agent.

That looks efficient!

Here is the github link:

https://github.com/vijaysrj/javamoduleservices


Posted

in

by

Comments

Leave a Reply

Discover more from The Full Stack Developer

Subscribe now to keep reading and get access to the full archive.

Continue reading