Get Even More Visitors To Your Blog, Upgrade To A Business Listing >>

First steps with the Java 9 and Maven modules

Tags: module modules

First steps with the Java 9 and Maven modules – Jigsaw project, JSR 376

1. Introduction

The Jigsaw project ( JSR 376 – Java Platform Module System ), is born with the following objectives in mind:

  • Make the Java SE platform, and the JDK, can be broken down into smaller pieces to be used in small devices.
  • Improve the security and general maintenance of the Java SE platform implementations, and in particular of the JDK.
  • Enable the improvement of application performance.
  • Facilitate developers building and maintaining large libraries and applications, both for the Java SE and EE platform.

All this basically means that we will be able to break our application into pieces, which we will call modules , where each of these modules has its dependencies perfectly defined with other modules. And these dependencies are going to have a direct impact on the visibility of our classes.

Someone might think that we have already solved this with the JAR files and the dependency management of Maven (or Gradle ), but nothing else. Indeed, a JAR is a grouping of classes and with Maven we can define the dependencies both at the compilation and execution levels that these JARs have among them, but it has a big problem and that is that any class

public

is visible anywhere in the system, and this It goes against the basic principles of application design breaking with low coupling and high cohesion .

This happens because, for many JAR that we have, basically what Java does is load all the classes of all the JARs of our application in the same class loader , with which all the classes

public

are visible for the rest of the classes independently of the JAR or of the package in which they are defined.

At the level of Maven does not improve too much since we can express dependencies between JARs, for example

A → B

, but once this dependency is established, from

A

we can access all the classes

public

that are inB. For this reason, to improve the encapsulation it is usual to find names of packages of the style

xxxx.internal.yyyyorxxxx.impl.yyyy

where that

internal

and that

impl

are indicating us: beware! Beyond there are dragons! But it is simply that, a warning, because nothing prevents us from accessing the classes that are behind these “barriers”.

Even it is quite common to find libraries divided into several JARs (API and implementation) to ensure that, at least at the level of compilation, we do not access the classes that we do not owe (protection that will not exist at runtime). For example to get a dependency scheme with Maven similar to the initial drawing, we could do something like:

That is to say, practically every JAR we have to split it in two and specify the dependency in compilation with the API and in execution with the implementation. Also remember again that this will only affect us while compiling, but in runtime we can access any class, for example using reflection, so all this racking of JARs does not really guarantee the encapsulation of our classes.

2. Environment

The tutorial is written using the following environment:

  • Hardware: Notebook MacBook Pro 15 “(2.5 GHz Intel i7, 16GB 1600 Mhz DDR3, 500GB Flash Storage).
  • AMD Radeon R9 M370X
  • Operating System: macOS Sierra 10.12.4
  • Oracle Java 9-ea167
  • Maven 3.5.0

3. Definition of a module

We return here the graphic that we put at the beginning of the tutorial and that represents what we want to achieve in this example.

We see that we have a module

App

that depends on a service

Logging

and a module

Dictionary

. The dependencies to these two modules are the only direct ones that it has

App

, however in a transitive way it will also be able to access the module

Words

. And in runtime you will access the module

ConsoleLogging

that is the implementation of the service. The good thing about this is that the application does not really know who the service provider is, that is, we are getting control investment(not injection of dependencies).

To define a module we must add a file

module-inform.java

in the root directory of our source code (in the directory where we would see our root package

com.

). For example, in a Maven project it would be in the directory

src/main/java.

  • By convention the root directory where the module code is located should have the name of the module itself, ie

    src/main/com.autentia.logging/…​…​

    . For now we are going to skip this convention to maintain the

    src/main/java/…​…​

    and thus simplify the configuration of Maven.

For our simplest case, which is the moduleLogging, this file would look like this:

module com.autentia.logging {

exports com.autentia.logging;

}

We see how after the reserved word

module

comes the module ID , in our case it is

como.autentia.logging

. This ID is in the form of a package name, but it is only a convention, since it is simply a string, so we could put what we wanted. When we want to refer to this module from another module, we will always use this ID.

Then between braces we see the definition of the modules. In this case we only see the reserved word

exports

and then the name of the package that we want to expose outward. That is, from this module only the public classes that are in the package will be visible from other modules

como.autentia.logging

. The rest of the public classes that are in the module but in other packages will be hidden both in compile time and in execution time.

4. Giving more visibility to the friendly modules

There are times where we can have several packages that are related. Among these packages we may want to export more classes than we expose to the rest of the world. So in our example we are going to assume that the module

Words

wants to share with the module

Dictionary

certain classes that will not be visible to the rest of the world:

module com.autentia.words {

exports com.autentia.words;

exports com.autentia.words.friends to com.autentia.dictionary;

}

We see how we have a second

exports

with a clause

to

. This indicates that the packet to the left of the

to

will only be visible by the module whose ID we have placed to the right of the

to

(important to emphasize that on the left we have put a package name while on the right we have put an ID of a module ). To the right of the

to

we can put a list of module IDs, separated by commas “,”.

5. Transitive dependence of a module

By default there are no transitive dependencies, that is, if our

App

module wants to use it

Dictionary

and it returns module classes in its public API

Words

, the module

App

would be obliged to also declare the dependency with the module

Words

. This does not seem practical or comfortable, since this double dependency (the module

Dictionary

and the module

Words

) we would have to declare in all the other modules that want to use the module

Dictionary.

To avoid this redundancy of having to always declare the module dependency,

Words

it can be avoided by using the reserved word

transitive

. For example:

module com.autentia.dictionary {

exports com.autentia.dictionary;

requires transitive com.autentia.words;

}

Here, in line 5 we are indicating that everyone who requires the module as a dependency

Dictionary

, automatically transitively will also have the module classes available

Words.

6. Definition of a service

In our example we have already seen how we have a module from

Logging

which we are going to define a service, which is simply a

Interface

Java service. This service (interface) will be used in other parts of the system, but the funny thing is that you do not know who implements the service (interface). In this way we make our system easier to maintain since we can change the implementation of this service without touching a single line of code from the rest of the system.

To do this in the module

Logging

we saw that you simply exported a package. In this package is the interface that defines the service (this interface has nothing special, it is the implementation of a Java interface of a lifetime).

Now we are going to provide an implementation where the messages are simply written to the console. We will do this in the module

ConsoleLogging:

module com.autentia.logging.console {

requires com.autentia.logging;

provides com.autentia.logging.Logger with com.autentia.logging.console.ConsoleLogger;

}

Here on line 5 we can see how with the reserved word

provides

we indicate the name of the interface (which is defined in the module

Logging

), and with the reserved word

with

we indicate the class, within the module

ConsoleLogging

, that will implement this interface.

7. Location of a service

In point 3 we have seen how we define the service, and in point 6 we have seen how to indicate which class implements the service. Now we just need to locate this service to use it. So in the module

App

that represents our application we will declare:

module com.autentia.app {

requires com.autentia.dictionary;
requires com.autentia.logging;

uses com.autentia.logging.Logger;

}

Here we highlight how in line 5 we use the reserved word

uses

to indicate that in this module we are going to use the service. Note especially that in this module

App

we do not have any

requires

to the module

ConsoleLogging

but we simply have it to the module

Logging

that is where the interface is defined. In this way the code of our application is totally decoupled from the implementation of the service, since there is no dependency, neither in compilation nor in execution, between both modules.

The code of our application will look like this:

public class App {

private final Logger log = ServiceLoader.load(Logger.class).findFirst().get();
private final Dictionary dictionary = new Dictionary();

public static void main(String… args) {
new App().execute();
}

private void execute() {
final Word word1 = dictionary.getWord();
final Word word2 = dictionary.getWord();
final Word word3 = dictionary.getWord();

log.error(word1.toString());
log.info(word2.toString());
log.debug(word3.toString());
}

}

Where we see how on line 3 we are locating the service implementation without knowing what it is.

In the example we see how we locate the first implementation of the service with

findFirst

, but here we could have all the logic we wanted, it would not even be complicated to change from a Service Locator pattern to a Dependency injection container .

8. And what about the old code? How do we migrate it to the module system?

When we try to load a class that is not found in any module, it will be searched in the normal Java class path (the class path of all life). If it is in the class path, this class is automatically added to a special module called “module without name” ( unnamed module ). In this way, this unnamed module will contain all the classes that are not explicitly defined in any module.

The unnamed module can access all the public classes that are exported in the rest of the named modules (explicit modules). On the contrary, the named modules can not access any class that is in the module without a name, in fact you can not even specify a dependency to the unnamed module. This restriction is set on purpose to force the developers to make a correct configuration of the modules.

All this allows us to make a gradual migration of our applications, since the new modules with JAR that do not yet have defined modules can coexist perfectly.

Taking into account the aforementioned restrictions, we will have to do this migration from bottom to top . That is a dependency graph would begin to migrate leaves (JARs that do not depend on others, in our example would be

Words,ConsoleLogging

…), and we’d go up in the dependency graph (in our example would end by the module

App

).

The problem with this migration from the bottom up is that we can touch our code, but we will always depend on third parties that, if they are not migrated to the new module system, can not be accessed from our modules. In these cases what we can do is treat these JARs as an “automatic module” ( automatic module ), simply by placing the JAR in the module pathinstead of the class path . In this way a module is generated implicitly, where its name will be determined by the name of the file. And so we can use it as if it were any other module with a name.

10. Conclusions

The Java 9 modules are presented as a powerful tool for encapsulation. We have seen how we can declare dependencies, do these transitive, define implement and use services, …

However, what about Maven / Gradle? In the complete example of code you can see how both can coexist today, there being a small duplication of concepts, since we are forced to define the dependencies in two points: Java 9 modules and Maven modules for compilation . We will see if in later versions of Maven this information is read directly from the Java 9 modules, or is it time for a new packaging system?

And if we can define and inject services, what about Spring? Is it his end? Will there be a link between the two? Of course there will be movement because with the new restrictions that the module path Spring imposes, it will not be so easy to do its “automagies”.

In general there are many uncertainties (for example, as we have said before, migrations from the bottom up are not going to be simple because it implies that everyone has to do it), and in fact there are a few detractors, for example here you have a good Concerns Regarding Jigsaw article (JSR-376, Java Platform Module System) . Eye that is long, but it is worth to see the corners that still remain to be polished.

With all this, what remains is to study, practice and observe where things are moving, to see if the Java 9 modules are finally imposed or, on the contrary, they continue to opt for third-party solutions such as OSGi , which on the other hand are much more powerful.

The post First steps with the Java 9 and Maven modules appeared first on Target Veb.



This post first appeared on Targetveb, please read the originial post: here

Share the post

First steps with the Java 9 and Maven modules

×

Subscribe to Targetveb

Get updates delivered right to your inbox!

Thank you for your subscription

×