Developers Club geek daily blog

1 year, 2 months ago
Good morning, Habr!

Since the book "Java. New generation of development" we monitor development of the announced new opportunities of this language integrated under the general name "Project Jigsaw" long ago. Today we offer transfer of article of November 24 instilling sufficient confidence that in the Java 9 Jigsaw version after all will take place.



There passed eight years after origin of the Jigsaw project which problem consists in a modularization of the Java platform and is reduced to implementation of the general system of modules. It is supposed that Jigsaw for the first time will appear in the Java 9 version. This release was planned also to Java 7 output earlier, and to Java 8. Jigsaw scope also repeatedly changed. Now there are all bases to believe that Jigsaw is almost ready as much attention in the plenary report of Oracle at the JavaOne 2015 conference, and also at once several performances on this subject was paid to it. What does it mean for you? What is the Jigsaw project and as to work with it?

It is the first of two publications in which I want to make short introduction to system of modules and on numerous code samples to show behavior of Jigsaw. In the first part we will discuss that the system of modules represents as was modulyarizovan JDK, and also we will consider behavior of the compiler and the performing environment in certain situations.

What is the module?

It is simple to describe the module: it is unit as a part of the program, and each module contains answers to three questions at once. These answers are written in the file module-info.java , which each module has.

  • How the module is called?
  • What does he export?
  • What for this purpose is required?


The first steps about Java 9 and the Jigsaw project – part one

Simple module

The answer to the first question is simple. (Almost) each module has a name. It has to correspond to agreements on naming of packets, for example de.codecentric.mymodule , in order to avoid the conflicts.

The list of all packets of this specific module which are considered as public API is provided for the answer to the second question in the module and, therefore, can be used by other modules. If the class is not the exported packet, nobody can get access to it from the outside of your module — even if it is public.

The answer to the third question is a list of those modules on which this module depends. All public types exported by these modules are available to dependent module. The Jigsaw command tries to enter into use the term "read out" other module.

This serious change of the status quo. Up to Java 8 inclusive any public type in way to your classes was available to any other type. With receipt of Jigsaw the system of availability of the Java types changes with

  • public
  • private
  • default
  • protected


on

  • public for all who read this module (exports)
  • public for some modules reading this (exports to, about it will go the speech in the second part)
  • public for any other class within this module
  • private
  • default
  • protected


Modulyarizovanny JDK

Dependences of modules have to form the acyclic graph, without allowing, thus, cyclic dependences. To implement such principle, the Jigsaw command should solve the following big problem: to break the performing Java environment which as it was reported, is complete of cyclic and illogical dependences into modules. Such graph turned out:

The first steps about Java 9 and the Jigsaw project – part one

In the basis of the graph there is java.base. It is the only module which has only entering edges. Each module created by you reads out java.base irrespective of, you declare it or not – as well as in case of implied expansion java.lang.Object . java.base exports such packets as java.lang , java.util , java.math etc.

The modularization of JDK means that now you can specify what modules of the performing Java environment you, which want to use. So, your application should not involve the Wednesday supporting Swing or Corba if you do not read modules java.desktop or java.corba . Creation of such cut-down environment will be described in the second part.
But quite dry theory …

Pokhimichim

All code given in article is available here, including shell scripts to compilation, packaging and start of an example.

The practical case considered here is very simple. I have a module de.codecentric.zipvalidator , executing a certain validation of a zip-code. This module is read out by the module de.codecentric.addresschecker (which could check by no means not only zip-codes, but here we do not do it not to complicate).
The Zip-validator is described in the following file module-info.java :

module de.codecentric.zipvalidator {
exports de.codecentric.zipvalidator.api;
}

So, this module exports a packet de.codecentric.zipvalidator.api also does not read any other modules (except java.base ). This module is read out by addresschecker'om:

module de.codecentric.addresschecker{
    exports de.codecentric.addresschecker.api;
    requires de.codecentric.zipvalidator;
}


General structure of file system is as follows:

two-modules-ok/
├── de.codecentric.addresschecker
│   ├── de
│   │   └── codecentric
│   │       └── addresschecker
│   │           ├── api
│   │           │   ├── AddressChecker.java
│   │           │   └── Run.java
│   │           └── internal
│   │               └── AddressCheckerImpl.java
│   └── module-info.java
├── de.codecentric.zipvalidator
│   ├── de
│   │   └── codecentric
│   │       └── zipvalidator
│   │           ├── api
│   │           │   ├── ZipCodeValidator.java
│   │           │   └── ZipCodeValidatorFactory.java
│   │           ├── internal
│   │           │   └── ZipCodeValidatorImpl.java
│   │           └── model
│   └── module-info.java


The agreement according to which the module is located in the directory, of the same name to this module works.
In the first example everything looks perfectly: we work strictly by rules and in our class AddressCheckerImpl we address only to ZipCodeValidator and ZipCodeValidatorFactory from the exported packet:

public class AddressCheckerImpl implements AddressChecker {
    @Override
    public boolean checkZipCode(String zipCode) {
        return ZipCodeValidatorFactory.getInstance().zipCodeIsValid(zipCode);
    }
}


Now we will start javac also we will generate byte code. To compile zipvalidator (that we, certainly, need to make first of all as addresschecker reads out zipvalidator), we do

javac -d de.codecentric.zipvalidator \
$(find de.codecentric.zipvalidator -name "*.java")


Looks familiarly – yet there is no speech about modules as zipvalidator does not depend on one user module. Command find just helps us to make the file list .java in the specified directory.
But as we will report javac about structure of our modules when we reach compilation? For this purpose the switch is entered into Jigsaw - modulepath or -mp .

To compile addresschecker, we use the following command:

javac - modulepath. - d de.codecentric.addresschecker \
$ (find de.codecentric.addresschecker - name "*.java")

By means of modulepath we report javac where to find the compiled modules (in this case, it.), something similar turns out on the switch of a way to classes (classpath switch).

However compilation of a set of modules separately seems some trouble – better to use other switch - modulesourcepath to compile at once several modules:

javac -d . -modulesourcepath . $(find . -name "*.java")


This code looks for among all subdirectories. directories of modules also compiles all java-files which are contained in them.
Having compiled, we, naturally, want to try that turned out:

java -mp . -m de.codecentric.addresschecker/de.codecentric.addresschecker.api.Run 76185


Besides, we specify a way to modules, reporting JVM where there are compiled modules. Also we set the main class (and parameter).

Hurrah, here and output:

76185 is a valid zip code


Modular Jar

It is known that in the world of Java we got used to receive and send our byte code in jar-files. The concept of modular jar.modulny jar is entered into Jigsaw it is very similar on normal, but it also contains compiled module-info.class. Provided that such files are compiled for the necessary target version, these archives will be backward compatible. module-info.java – not the valid name of type, therefore compiled module-info.class it will be ignored by older JVM.

To collect jar for zipvalidator, we write:

jar --create --file bin/zipvalidator.jar \
--module-version=1.0 -C de.codecentric.zipvalidator 
.

We specify the file of an output, the version (though use of several versions of the module in Jigsaw separately does not make a reservation during execution) and the module which should be packed.
As addresschecker also have the main class, we can specify also it:

jar --create --file=bin/addresschecker.jar --module-version=1.0 \
--main-class=de.codecentric.addresschecker.api.Run \
-C de.codecentric.addresschecker .


The main class is not specified in module-info.java , as it would be possible to expect (initially the Jigsaw command and was going to arrive), and usually registers in the manifesto.

If to start this example with

java -mp bin -m de.codecentric.addresschecker 76185


let's receive the same answer, as well as in the previous case. We specify again a way to modules which in this case conducts to the bin directory where we wrote our jars. We should not specify the main class as in the manifesto addresschecker.jar there is already this information. It is enough to report a module name to the switch -m .

Still everything was easy and pleasant. Further let's a little tinker with modules and we will look how Jigsaw behaves in compilation time and executions if you begin to misbehave.

Use of not exported types

In this example we will look what occurs if we address such type from other module which should not be used.

As we were tired of this factory piece in AddressCheckerImpl , we change implementation on

return new ZipCodeValidatorImpl().zipCodeIsValid(zipCode);


In attempt to compile it is received expected

error: ZipCodeValidatorImpl is not visible because 
package de.codecentric.zipvalidator.internal is not visible


So, use of not exported types in compilation time does not work.
But we are smart children therefore we will a little use cunning and we use a reflection.

ClassLoader classLoader = AddressCheckerImpl.class.getClassLoader();
try {
    Class aClass = classLoader.loadClass("de.[..].internal.ZipCodeValidatorImpl");
    return ((ZipCodeValidator)aClass.newInstance()).zipCodeIsValid(zipCode);
} catch (Exception e) {
    throw new  RuntimeException(e);
}


It was compiled perfectly, let's start. On the contrary, it is not so simple to fool Jigsaw:

java.lang.IllegalAccessException:
class de.codecentric.addresschecker.internal.AddressCheckerImpl 
(in module de.codecentric.addresschecker) cannot access class [..].internal.ZipCodeValidatorImpl 
(in module de.codecentric.zipvalidator) because module
de.codecentric.zipvalidator does not export package
de.codecentric.zipvalidator.internal to module
de.codecentric.addresschecker


So, Jigsaw includes check not only in compilation time, but also at the runtime! And extremely accurately tells us what we made incorrectly.

Cyclic dependences

In the following case we suddenly realized that in the API addresschecker module the class which zipvalidator quite could use contains. As we are lazy, instead of refactoring of a class in other module we declare dependence for addresschecker:

module de.codecentric.zipvalidator{
        requires de.codecentric.addresschecker;
        exports de.codecentric.zipvalidator.api;

}


As cyclic dependences are prohibited by determination, on our way (for the sake of general welfare) there is a compiler:

./de.codecentric.zipvalidator/module-info.java:2: 
error: cyclic dependence involving de.codecentric.addresschecker


So it is impossible to do, and we about it are warned in advance, in compilation time.

Implied readability

To expand functionality, we decide to inherit zipvalidator, having entered the new module de.codecentric.zipvalidator.model , containing a certain model of result of validation, but not just banal bulean. The new structure of the file is shown here:

three-modules-ok/
├── de.codecentric.addresschecker
│   ├── de
│   │   └── codecentric
│   │       └── addresschecker
│   │           ├── api
│   │           │   ├── AddressChecker.java
│   │           │   └── Run.java
│   │           └── internal
│   │               └── AddressCheckerImpl.java
│   └── module-info.java
├── de.codecentric.zipvalidator
│   ├── de
│   │   └── codecentric
│   │       └── zipvalidator
│   │           ├── api
│   │           │   ├── ZipCodeValidator.java
│   │           │   └── ZipCodeValidatorFactory.java
│   │           └── internal
│   │               └── ZipCodeValidatorImpl.java
│   └── module-info.java
├── de.codecentric.zipvalidator.model
│   ├── de
│   │   └── codecentric
│   │       └── zipvalidator
│   │           └── model
│   │               └── api
│   │                   └── ZipCodeValidationResult.java
│   └── module-info.java


Class ZipCodeValidationResult – the simple transfer having copies of a type of "too short", "too long", etc.
Class module-info.java it is inherited thus:

module de.codecentric.zipvalidator{
       exports de.codecentric.zipvalidator.api;
       requires de.codecentric.zipvalidator.model;
}


Now our implementation of ZipCodeValidator looks so:

@Override
public <strong>ZipCodeValidationResult</strong> zipCodeIsValid(String zipCode) {
   if (zipCode == null) {
       return ZipCodeValidationResult.ZIP_CODE_NULL;
[snip]
   } else {
       return ZipCodeValidationResult.OK;
   }
}


The addresschecker module is adapted so now what can accept as the returned type and transfer so it is possible to start, truly? No! Compilation gives:

./de.codecentric.addresschecker/de/[..]/internal/AddressCheckerImpl.java:5: 
error: ZipCodeValidationResult is not visible because package
de.codecentric.zipvalidator.model.api is not visible


There was an error at compilation of addresschecker – zipvalidator uses the exported types from the zipvalidator model model in the public API. As addresschecker does not read this module, he cannot address this type.

There are two solutions of such problem. Obvious: to add a reading edge from addresschecker to the zipvalidator model. However it is a slippery track: why to us to declare this dependence if it is necessary only for work with zipvalidator? Unless zipvalidator should not guarantee that we will be able to get access to all necessary modules? Has to and can – here we approach implied readability. Having added a key word of public to required determination, we report to all client modules that they also have to read out other module. As an example we will consider the updated class module-info.java zipvalidator'a:

module de.codecentric.zipvalidator{
       exports de.codecentric.zipvalidator.api;
       requires public de.codecentric.zipvalidator.model;
}


Key word public reports to all modules reading zipvalidator that they also have to read its model. It was necessary to work with way to classes differently: so, you could not rely on Maven POM if it was required to guarantee that all your dependences were also available to any client; to achieve it, it was obviously necessary to specify them if they were a part of your public API. It is very beautiful model: if you use dependences only in a class, then what business to them to your clients? And if you use them out of a class, then also have to report about it directly.

Summary

Here also the first part came to an end. We discussed three questions which it is necessary to answer for each module, and also about a modularization of the performing Java environment. Further we reviewed an example where we compiled, started and packed the simple application on Java consisting of two modules. Then on a working example studied how the system of modules reacts to violation of the set rules. Further, having expanded functionality, we studied the third module and talked about the concept of implied readability.

In the following part the following questions will be considered:

  • How Jigsaw works if the way to modules contains several modules of the same name?
  • What occurs if in way to modules there are heteronymic modules which, however, export the same packets?
  • What to do with legacy dependences which not modulyarizovana?
  • How to create own cut-down option of the performing environment?

This article is a translation of the original post at habrahabr.ru/post/271941/
If you have any questions regarding the material covered in the article above, please, contact the original author of the post.
If you have any complaints about this article or you want this article to be deleted, please, drop an email here: sysmagazine.com@gmail.com.

We believe that the knowledge, which is available at the most popular Russian IT blog habrahabr.ru, should be accessed by everyone, even though it is poorly translated.
Shared knowledge makes the world better.
Best wishes.

comments powered by Disqus