Modular Development in Java 9

Modular Development in Java 9

Modular Development and Its Impact

In software engineering, modularity is an important concept. From the point of view of performance as well as maintainability, it is important to create autonomous units called modules. These modules can be tied together to make a complete system. The modules provides encapsulation where the implementation is hidden from other modules. Each module can expose distinct APIs that can act as connectors so that other modules can communicate with it. This type of design is useful as it promotes loose coupling, helps focus on singular functionality to make it cohesive, and enables testing it in isolation. It also reduces system complexity and optimizes application development process. Improving performance of each module helps improving overall application performance. Hence, modular development is a very important concept.

I know you may be thinking, wait a minute, isn’t Java already modular? Isn’t the object-oriented nature of Java already providing modular operation? Well, object-oriented certainly imposes uniqueness along with data encapsulation. It only recommends loose coupling but does not strictly enforce it. In addition, it fails to provide identity at the object level and also does not have any versioning provision for the interfaces. Now you may be asking, what about JAR files? Aren’t they modular? Well, although JARs provide modularization to some extent, they don’t have the uniqueness that is required for modularization. They do have a provision to specify the version number, but it is rarely used and also hidden in the JAR’s manifest file.

So we need a different design from what we already have. In simple terms, we need a modular system in which each module can contain more than one package and offers robust encapsulation compared to the standard JAR files.

This is what Java 9’s modular system offers. In addition to this, it also replaces the fallible classpath mechanism by declaring dependencies explicitly. These enhancements improve the overall application performance as developers can now optimize the individual self-contained unit without affecting the overall system.

This also makes the application more scalable and provides high integrity.

Let’s look at some of the basics of the module system and how it is tied together. To start off with, you can run the following commands to see how the module system is structured:

$java –list-modules

Modules_in_Java_9_1

If you are interested in a particular module, you can simply add the module name at the end of the command, as shown in the following command:

$java –list-modules java.base

Modules_in_Java_9_2

The earlier command will show all the exports in packages from the base module. Java base is the core of the system.

This will show all the graphical user interface packages. This will also show requires which are the dependencies:

$java –list-modules java.desktop

modules
modules

So far so good, right? Now you may be wondering, I got my modules developed but how to integrate them together? Let’s look into that. Java 9’s modular system comes with a tool called JLink. I know you can guess what I am going to say now. You are right, it links a set of modules and creates a runtime image. Now imagine the possibilities it can offer. You can create your own executable system with your own custom modules. Life is going to be a lot more fun for you, I hope! Oh, and on the other hand, you will be able to control the execution and remove unnecessary dependencies.

Let’s see how to link modules together. Well, it’s very simple. Just run the following command:

$jlink –module-path $JAVA_HOME/jmods:mlib –add-modules java.desktop –output myawesomeimage

This linker command will link all the modules for you and create a runtime image. You need to provide a module path and then add the module that you want to generate a figure and give a name. Isn’t it simple?

Now, let’s check whether the previous command worked properly or not. Let’s verify the modules from the figure:

$myawesomeimage/bin/java –list-modules

The output looks like this:

With this, you will now be able to distribute a quick runtime with your application. It is awesome, isn’t it? Now you can see how we moved from a somewhat monolithic design to a self-contained cohesive one. Each module contains its own exports and dependencies and JLink allows you to create your own runtime. With this, we got our modular platform.

Note that the aim of this section is to just introduce you to the modular system. There is a lot more to explore but that is beyond the scope of this book. In this book, we will focus on the performance enhancement areas.

Quick Introduction to Modules

I am sure that after reading about the modular platform, you must be excited to dive deep into the module architecture and see how to develop one. Hold your excitement please, I will soon take you on a journey to the exciting world of modules.

As you must have guessed, every module has a property name and is organized by packages. Each module acts as a self-contained unit and may have native code, configurations, commands, resources, and so on. A module’s details are stored in a file named module-info.java, which resides in the root directory of the module source code. In that file, a module can be defined as follows:

module <name>{

}

In order to understand it better, let’s go through an example. Let’s say, our module name is PerformanceMonitor. The purpose of this module is to monitor the application performance. The input connectors will accept method names and the required parameters for that method. This method will be called from our module to monitor the module’s performance. The output connectors will provide performance feedback for the given module. Let’s create a module-info.java file in the root directory of our performance application and insert the following section:

module com.java9highperformance.PerformanceMonitor{

}

Awesome! You got your first module declaration. But wait a minute, it does not do anything yet. Don’t worry, we have just created a skeleton for this. Let’s put some flesh on the skeleton. Let’s assume that our module needs to communicate with our other (magnificent) modules, which we have already created and named–PerformanceBase, StringMonitor, PrimitiveMonitor, GenericsMonitor, and so on. In other words, our module has an external dependency. You may be wondering, how would we define this relationship in our module declaration? Ok, be patient, this is what we will see now:

module com.java9highperformance.PerformanceMonitor{

    exports com.java9highperformance.StringMonitor;

    exports com.java9highperformance.PrimitiveMonitor;

    exports com.java9highperformance.GenericsMonitor;

    requires com.java9highperformance.PerformanceBase;

    requires com.java9highperformance.PerformanceStat;

    requires com.java9highperformance.PerformanceIO;

}

Yes, I know you have spotted two clauses, that is, exports and requires. And I am sure you are curious to know what they mean and why we have them there. We’ll first talk about these clauses and what they mean when used in the module declaration:

  • exports: This clause is used when your module has a dependency on another module. It denotes that this module exposes only public types to other modules and none of the internal packages are visible. In our case, the module com.java9highperformance.PerformanceMonitor has a dependency on com.java9highperformance.StringMonitor, com.java9highperformance.PrimitiveMonitor, and com.java9highperformance.GenericsMonitor. These modules export their API packages com.java9highperformance.StringMonitor, com.java9highperformance.PrimitiveMonitor, and com.java9highperformance.GenericsMonitor, respectively.
  • requires: This clause denotes that the module depends upon the declared module at both compile and runtime. In our case, com.java9highperformance.PerformanceBase, com.java9highperformance.PerformanceStat, and com.java9highperformance.PerformanceIO modules are required by our com.java9highperformance.PerformanceMonitor module. The module system then locates all the observable modules to resolve all the dependencies recursively. This transitive closure gives us a module graph which shows a directed edge between two dependent modules.

NOTE

Note: Every module is dependent on java.base even without explicitly declaring it. As you already know, everything in Java is an object.

Now you know about the modules and their dependencies. So, let’s draw a module representation to understand it better. The following figure shows the various packages that are dependent on com.java9highperformance.PerformanceMonitor.

schema
schema

Modules at the bottom are exports modules and modules on the right are requires modules.

Now let’s explore a concept called readability relationship. Readability relationship is a relationship between two modules where one module is dependent on another module. This readability relationship is a basis for reliable configuration. So in our example, we can say com.java9highperformance.PerformanceMonitor reads com.java9highperformance.PerformanceStat.

Let’s look at com.java9highperformance.PerformanceStat module’s description file module-info.java:

module com.java9highperformance.PerformanceStat{

    requires transitive java.lang;

}

This module depends on the java.lang module. Let’s look at the PerformanceStat module in detail:

package com.java9highperformance.PerformanceStat;

import java.lang.*;

public Class StringProcessor{

    public String processString(){…}

}

In this case, com.java9highperformance.PerformanceMonitor only depends on com.java9highperformance.PerformanceStat but com.java9highperformance.PerformanceStat depends on java.lang. The com.java9highperformance.PerformanceMonitor module is not aware of the java.lang dependency from the com.java9highperformance.PerformanceStat module. This type of problem is taken care of by the module system. It has added a new modifier called transitive. If you look at com.java9highperformance.PerformanceStat, you will find it requires transitive java.lang. This means that any one depending on com.java9highperformance.PerformanceStat reads on java.lang.

See the following graph which shows the readability graph:

schema2
schema2

Now, in order to compile the com.java9highperformance.PerformanceMonitor module, the system must be able to resolve all the dependencies. These dependencies can be found from the module path. That’s obvious, isn’t that? However, don’t misunderstand the classpath with the module path. It is a completely different breed. It doesn’t have the issues that the packages have.

One Response

Leave a Reply

Your email address will not be published. Required fields are marked *