Sunday, 16 June 2019

Java9: Adding services to the application


In this post, I am going to explain how to decouple the implementation with interfaces using ServiceLoader class and module system.

In a typical application, there is a well-defined interface and multiple implementations of the interface available. Instead of tightly coupling the application with both interfaces and implementation classes, we can decouple them like below.

a.   Interfaces are defined in a module ‘app.interfaces’.
b.   All the respective implementations of interfaces defined in their respective modules ‘app.interfaces.implementation1’, ‘app.interfaces.implementation2’,  etc.
c.    All the implementations register themselves in service registery.
d.   Main application asks service registry to give the implementation classes of given interfaces. Service registry gives the implementation classes of this interface.


With this approach, main application only needs to be aware of interface module, it no need to worry about from where the implementation come from. It is the responsibility of ServiceLoader class to load give an implemented object of this interface.

Let’s see it with an example.

Step 1: Create ‘app.interfaces’ module by following below directory structure.

$tree services_example/
services_example/
└── src
    └── app.interfaces
        ├── com
        │   └── sample
        │       └── app
        │           └── interfaces
        │               └── Circle.java
        └── module-info.java

6 directories, 2 files


Circle.java
package com.sample.app;

import com.sample.util.ArithmeticUtil;

public class App {

    public static void main(String args[]) {

        System.out.println("Sum of 10 and 20 is : " + ArithmeticUtil.add(10, 20));
        System.out.println("Subtraction of 10 and 20 is : " + ArithmeticUtil.sub(10, 20));
        System.out.println("Multiplication of 10 and 20 is : " + ArithmeticUtil.mul(10, 20));
        System.out.println("Division of 10 and 20 is : " + ArithmeticUtil.div(10, 20));

    }
}


module-info.java
module app.interfaces{
   exports com.sample.app.interfaces;
}

Step 2: Create ‘app.interfaces.impl’ module by following below directory structure.    
$tree services_example/
services_example/
└── src
    ├── app.interfaces
    │   ├── com
    │   │   └── sample
    │   │       └── app
    │   │           └── interfaces
    │   │               └── Circle.java
    │   └── module-info.java
    └── app.interfaces.impl
        ├── com
        │   └── sample
        │       └── app
        │           └── interfaces
        │               └── impl
        │                   └── CircleImpl.java
        └── module-info.java


CircleImpl.java
package com.sample.app.interfaces.impl;

import com.sample.app.interfaces.Circle;

public class CircleImpl implements Circle {

 public static final double PI = 3.14;

 @Override
 public double perimeter(double radius) {
  return 2 * PI * radius;
 }

 @Override
 public double area(double radius) {
  return PI * radius * radius;
 }

}


module-info.java
module app.interfaces.impl{
  requires app.interfaces;

  provides com.sample.app.interfaces.Circle
   with com.sample.app.interfaces.impl.CircleImpl;
}


Provides ‘interface name’ with ‘implementation class’ means that I am providing service to Circle interface via CircleImpl class.

Step 3: Create ‘app.main’ module by following below directory structure.
$tree services_example/
services_example/
└── src
    ├── app.interfaces
    │   ├── com
    │   │   └── sample
    │   │       └── app
    │   │           └── interfaces
    │   │               └── Circle.java
    │   └── module-info.java
    ├── app.interfaces.impl
    │   ├── com
    │   │   └── sample
    │   │       └── app
    │   │           └── interfaces
    │   │               └── impl
    │   │                   └── CircleImpl.java
    │   └── module-info.java
    └── app.main
        ├── com
        │   └── sample
        │       └── app
        │           └── App.java
        └── module-info.java

16 directories, 6 files


App.java
package com.sample.app;

import java.util.ServiceLoader;

import com.sample.app.interfaces.Circle;

public class App {

 public static void main(String args[]) {

  Iterable<Circle> iterable = ServiceLoader.load(Circle.class);
 
  System.out.println("Going to get instances"); 
  for(Circle circle : iterable) {
   double area = circle.area(10);
   double perimeter = circle.perimeter(10);
   
   System.out.println("Area : " + area);
   System.out.println("Perimeter : " + perimeter);
  }
 }
}


module-info.java
module app.main{
   requires app.interfaces;

   uses com.sample.app.interfaces.Circle;
}


‘uses’ tells that this application use Circle interface.

Step 4: Compile App.java and CircleImpl.java files.
javac --module-source-path src -d out src/app.main/com/sample/app/App.java

javac --module-source-path src -d out src/app.interfaces.impl/com/sample/app/interfaces/impl/CircleImpl.java

Total directory structure will change like below.
$tree
.
├── out
│   ├── app.interfaces
│   │   ├── com
│   │   │   └── sample
│   │   │       └── app
│   │   │           └── interfaces
│   │   │               └── Circle.class
│   │   └── module-info.class
│   ├── app.interfaces.impl
│   │   ├── com
│   │   │   └── sample
│   │   │       └── app
│   │   │           └── interfaces
│   │   │               └── impl
│   │   │                   └── CircleImpl.class
│   │   └── module-info.class
│   └── app.main
│       ├── com
│       │   └── sample
│       │       └── app
│       │           └── App.class
│       └── module-info.class
└── src
    ├── app.interfaces
    │   ├── com
    │   │   └── sample
    │   │       └── app
    │   │           └── interfaces
    │   │               └── Circle.java
    │   └── module-info.java
    ├── app.interfaces.impl
    │   ├── com
    │   │   └── sample
    │   │       └── app
    │   │           └── interfaces
    │   │               └── impl
    │   │                   └── CircleImpl.java
    │   └── module-info.java
    └── app.main
        ├── com
        │   └── sample
        │       └── app
        │           └── App.java
        └── module-info.java

32 directories, 12 files


Step 5: Run App.java by executing below command.
$java --module-path out -m app.main/com.sample.app.App
Going to get instances
Area : 314.0
Perimeter : 62.800000000000004


Adding another implementation
You can add other implementation to existing project. ServiceLoader will pick this implementation also in run time.

Create new module ‘app.interfaces.otherimpl’ like below.
$tree src/app.interfaces.otherimpl/
src/app.interfaces.otherimpl/
├── com
│   └── sample
│       └── app
│           └── interfaces
│               └── otherimpl
│                   └── CircleImpl.java
└── module-info.java

5 directories, 2 files


CircleInfo.java
package com.sample.app.interfaces.otherimpl;

import com.sample.app.interfaces.Circle;

public class CircleImpl implements Circle {

    public static final double PI = 3.14285714285;

    @Override
    public double perimeter(double radius) {
        return 2 * PI * radius;
    }

    @Override
    public double area(double radius) {
        return PI * radius * radius;
    }

}


Module-info.java
module app.interfaces.otherimpl{
  requires app.interfaces;

  provides com.sample.app.interfaces.Circle
   with com.sample.app.interfaces.otherimpl.CircleImpl;
}


Compile CircleImpl.java using below command.
javac --module-source-path src -d out src/app.interfaces.otherimpl/com/sample/app/interfaces/otherimpl/CircleImpl.java

Total project structure looks like below.    
$tree services_example/
services_example/
├── out
│   ├── app.interfaces
│   │   ├── com
│   │   │   └── sample
│   │   │       └── app
│   │   │           └── interfaces
│   │   │               └── Circle.class
│   │   └── module-info.class
│   ├── app.interfaces.impl
│   │   ├── com
│   │   │   └── sample
│   │   │       └── app
│   │   │           └── interfaces
│   │   │               └── impl
│   │   │                   └── CircleImpl.class
│   │   └── module-info.class
│   ├── app.interfaces.otherimpl
│   │   ├── com
│   │   │   └── sample
│   │   │       └── app
│   │   │           └── interfaces
│   │   │               └── otherimpl
│   │   │                   └── CircleImpl.class
│   │   └── module-info.class
│   └── app.main
│       ├── com
│       │   └── sample
│       │       └── app
│       │           └── App.class
│       └── module-info.class
└── src
    ├── app.interfaces
    │   ├── com
    │   │   └── sample
    │   │       └── app
    │   │           └── interfaces
    │   │               └── Circle.java
    │   └── module-info.java
    ├── app.interfaces.impl
    │   ├── com
    │   │   └── sample
    │   │       └── app
    │   │           └── interfaces
    │   │               └── impl
    │   │                   └── CircleImpl.java
    │   └── module-info.java
    ├── app.interfaces.otherimpl
    │   ├── com
    │   │   └── sample
    │   │       └── app
    │   │           └── interfaces
    │   │               └── otherimpl
    │   │                   └── CircleImpl.java
    │   └── module-info.java
    └── app.main
        ├── com
        │   └── sample
        │       └── app
        │           └── App.java
        └── module-info.java

44 directories, 16 files


Execute App.java.
$java --module-path out -m app.main/com.sample.app.App
Going to get instances
Area : 314.285714285
Perimeter : 62.857142857
Area : 314.0
Perimeter : 62.800000000000004


You can download complete project source code from below git location.

Previous                                                 Next                                                 Home

No comments:

Post a Comment