Developers Club geek daily blog

1 year, 9 months ago
Hello, Habr.

After some delay we publish the second part of article about the Jigsaw and Java 9 project which left in the blog Codecentric. Transfer of the first part is here.


It is the second part of article for those who want to get acquainted with the Jigsaw project closer. In the first part we briefly discussed what is the module and as there was a modulyarizovana the performing Java Runtime environment. Then we reviewed a simple example of compilation, packaging and start of the building block application.

Here we will try to answer the following questions:

  • Whether it is possible to introduce restriction for what modules will be able to read the exported packet?
  • What to do with the different versions of the same module which are present at a way to modules?
  • How Jigsaw interacts with not modular legacy code?
  • How to collect own image of the performing Java environment?


Let's take as a basis an example from part 1 and we will continue to work with it. The code still is here.

Providing right to reading to specific modules

In the first part we said about how availability of Java within Jigsaw develops. One of availability levels which was mentioned, but is not explained properly, is as follows: "public for some modules, those that read this module". So we can limit a circle of modules, it will be authorized to them to read our exported packets. Let's say developers de.codecentric.zipvalidator hate developers de.codecentric.nastymodule , therefore can change the module-info.java here so:

module de.codecentric.zipvalidator{

    exports de.codecentric.zipvalidator.api 
        to de.codecentric.addresschecker;
}


Thus, only addresschecker can get access to API zipvalidator . This instruction is followed at the level of packets therefore nothing to you prevents to limit access for one packets, at the same time having provided full access for others. Such practice is named "the qualified export". If module de.codecentric.nastymodule will try to address any type from de.codecentric.zipvalidator.api , there will be a compilation error:

./de.cc.nastymodule/de/cc/nastymodule/internal/AddressCheckerImpl.java:4: 
error: ZipCodeValidatorFactory is not visible 
       because package de.cc.zipvalidator.api is not visible


Pay attention: the program does not swear on module-info.java , as zipvalidator in principle could export visible packets in nastymodule . For example, the qualified export can be applied when you want to modulyarizovat an inner pattern of your application, but you do not want to share the exported packets of internal modules with clients.

The conflicts between versions of modules

Often it happens so that through transitive dependences different versions of library are included in the same application — that is, the same module can appear in a way to modules twice. At once two scenarios occur:

  • Modules are available in compilation time in different directories or modular jar, but a name at them all the same identical
  • Different versions of the same module are heteronymic


Let's try to compile the application according to the first scenario. Copied zipvalidator :

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


The duplicated modules are in different directories, but the name of the module remains invariable. How Jigsaw reacts to it in compilation time?

./de.codecentric.zipvalidator.v2/module-info.java:1: 
error: duplicate module: de.codecentric.zipvalidator


So, here to us not to get off light. Jigsaw gives a compilation error when at a way to modules there are two modules of the same name.

What about the second case? The structure of directories remains former, but now both zipvalidator'a receive different names ( de.codecentric.zipvalidator.v{1|2} ), and addresschecker reads both these to a name.

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


Most likely, and here it will not be compiled? To read two modules exporting the same packets? It appears, will be compiled. I was surprised: the compiler will recognize the arisen situation, but is limited only to such warnings:

./de.cc.zipvalidator.v1/de/codecentric/zipvalidator/api/ZipCodeValidator.java:1: 
warning: package exists in another module: de.codecentric.zipvalidator.v2


The developer will readily ignore such warning and will start the application. But Jigsaw is obviously not pleasant what he will see at the runtime:

java.lang.module.ResolutionException: 
Modules de.codecentric.zipvalidator.v2 and de.codecentric.zipvalidator.v1 export 
package de.codecentric.zipvalidator.api to module de.codecentric.addresschecker


It seemed to me obscure, in my opinion, the compile-time error could be issued more accurately. I took an interest in mailing why such option was selected, but at the time of writing of article of the answer did not receive yet.

Automatic modules and unnamed module

Still we worked in completely modulyarizovanny environment. But what to do in such very probable cases when it is necessary to deal with not modular Jar-files? Here automatic modules and the unnamed module enter game.

Let's begin with automatic modules. The automatic module is the jar-file put in modulepath. After you will write it there, it will be possible to answer three following questions of this module:

В: What his name?
О: this name of the jar-file. If you put the guava.jar file at way to modules, then receive the automatic guava module.

It also means that you will not be able to use Jar directly from Maven repository as guava-18.0 is not the admissible Java identifier.

В: What does he export?
О: The automatic module exports all the packets. So, all public types will be available to any module reading the automatic module.

В: What does he demand?
О: The automatic module reads everything (*all *) other available modules (including unnamed, is more detailed about it below). It is important! From the automatic module it is possible to get access to all exported types of any module. This moment does not need to be specified anywhere, it is meant.

Let's review an example. We begin to use com.google.common.base.Strings in a zipvalidator-a. To permit such access, we have to define a reading edge for the automatic Guava module:

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

 }


For compilation it will be required to specify the guava.jar file in a way to modules (it is in the./jars directory):

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


Everything is perfectly compiled and started.

(By the way, it was not so simple to start this an example. Working with assembly of Jigsaw 86, I faced some problems: the system swore concerning dependences on the module jdk.management.resource . I asked about it in mailing, discussion is here.

It is necessary to tell that in my solution I did not use "early" assembly (early access build), and collected JDK. During the work with OSX Mavericks there were still some problems about what it is written in rub, it was necessary to change makefile, but as a result I adjusted everything. Perhaps, you during the work with the following releases should face already other problems).

Now it is a high time to acquaint you with a lifesaver which is irreplaceable upon transition to Jigsaw. This tool is called jdeps . He browses your nemodulyarizovanny code and reports to you about dependences.

Let's consider guava:

jdeps - s./jars/guava.jar
We have such output:
guava.jar-> java.base
guava.jar-> java.logging
guava.jar-> not found

It means that the automatic guava module demands java.base , java.logging and … "it is not found"?! What is? If to remove the switch -s , jdeps leaves the level of modules and goes down on a step, on the level of packets (the list is a little reduced as there is a lot of packets at guava):

   com.google.common.xml (guava.jar)
      -> com.google.common.escape               guava.jar
      -> java.lang
      -> javax.annotation                                   not found


Here it is visible that a packet com.google.common.xml depends from com.google.common.escape , which is located in the module, java.lang , which is well-known, and from the summary javax.annotation , which is not found. We draw a conclusion that we need jar with the JSR-305 types as it contains there javax.annotation (by the way, I do without them – in my examples I do not need any type from these packets, and neither the compiler, nor the performing environment at the same time object).

Unnamed module

So, what is the unnamed module? Again we will answer three questions:

В: What his name?
О: As you already guessed, it has no name

В: What does he export?
О: The unnamed module exports all the packets to any other module. It does not mean that it can be read from any other module – it has no name, and you cannot demand it! Requires unnamed command; will not work.

В: What does he demand?
О: The unnamed module reads all other available modules.

So, if you cannot read the unnamed module from any your modules, then in what an essence? Our old acquaintance — a way to classes helps to answer this question. Any type, read of a way to classes (but not from a way to modules), automatically is located in the unnamed module — or, otherwise, any type in the unnamed module is loaded in a way to classes. As the unnamed module reads all other modules, we can address all exported types from any type loaded in a way to classes. In Java 9 use of a way to classes and a way to modules will be supported both separately, and jointly, for ensuring backward compatibility. Let's review several examples.

Let's assume, we have an accurate zipvalidator module, but addresschecker still not modulyarizovan, at it is not present module-info.java . The structure of our source codes will be such:

one-module-with-unnamed-ok/
├── classpath
│   └── de.codecentric.legacy.addresschecker
│       └── de
│           └── codecentric
│               └── legacy
│                   └── addresschecker
│                       ├── api
│                       │   ├── AddressChecker.java
│                       │   └── Run.java
│                       └── internal
│                           └── AddressCheckerImpl.java
├── modulepath
│   └── de.codecentric.zipvalidator
│       ├── de
│       │   └── codecentric
│       │       └── zipvalidator
│       │           ├── api
│       │           │   ├── ZipCodeValidator.java
│       │           │   └── ZipCodeValidatorFactory.java
│       │           └── internal
│       │               └── ZipCodeValidatorImpl.java
│       └── module-info.java


Now there is one classpath directory which contains the legacy code tied on access to zipvalidator, and also the modulepath directory containing the zipvalidator module. We can compile our modules as usual. To compile a legacy code, we will need to provide information on a modular code. Let's just write it at way to classes:

javac -d classpath/de.codecentric.legacy.addresschecker  
  -classpath modulepath/de.codecentric.zipvalidator/ $(find classpath -name "*.java")


Everything works as usual.

During execution before us two opportunities open. Namely:
  • To write the module at way to classes
  • To mix a way to classes and a way to modules


Selecting the first option, we, actually, refuse building block system. All types which we will write in the unnamed module will be able to address freely to each other now.

java -cp modulepath/de.cc.zipvalidator/:classpath/de.cc.legacy.addresschecker/
    de.codecentric.legacy.addresschecker.api.Run 76185


works precisely as the Java application used by you today.

On the other hand, the mixed use of a way to modules and ways to classes works so:

java -modulepath modulepath  -addmods de.codecentric.zipvalidator 
    -classpath classpath/de.codecentric.legacy.addresschecker/ 
    de.codecentric.legacy.addresschecker.api.Run


We use at the same time two switches: -classpath and -modulepath . The switch is added -addmods – when mixing a way to classes and a way to modules, we cannot get just like that access to any module in the modulepath directories, and have to specify specifically what of them have to be available.

This approach also works normally, but there is a hitch! You remember, the answer to the question "what is demanded by the unnamed module" is "all other modules". If we use the zipvalidator module through modulepath, then we will be able to work only with its exported packets. All the rest will lead to IllegalAccessError during execution. Therefore in that case you should follow the rules of system of modules.

Creation of images of the performing environment by means of jlink

There are enough examples with modules; there was one more new tool deserving our attention. jlink – it is the utility of Java 9 for creation of own JVM distribution kits. The most interesting that, thanks to new modular architecture of JDK, you can select what modules want to turn on in this distribution kit! Let's review an example. If we want to create the image of the performing environment containing our addresschecker, then we give command:

jlink --modulepath $JAVA9_BIN/../../images/jmods/:two-modules-ok/ 
    --addmods de.codecentric.addresschecker --output linkedjdk


We specify only three things:

  • Way to modules (including, our special modules and a catalog path of jmods – here are in your JDK standard java modules)
  • Modules which you want to turn on in your distribution kit
  • Directory of an output


The command creates the following structure:

linkedjdk/
├── bin
│ ├── java
│ └── keytool
├── conf
│ ├── net.properties
│ └── security
│ ├── java.policy
│ └── java.security
└── lib
├── classlist
├── jli
    │   └── libjli.dylib
    ├── jspawnhelper
    ├── jvm.cfg
    ├── libjava.dylib
    ├── libjimage.dylib
    ├── libjsig.diz
    ├── libjsig.dylib
    ├── libnet.dylib
    ├── libnio.dylib
    ├── libosxsecurity.dylib
    ├── libverify.dylib
    ├── libzip.dylib
    ├── modules
    │   └── bootmodules.jimage
    ├── security
    │   ├── US_export_policy.jar
    │   ├── blacklisted.certs
    │   ├── cacerts
    │   └── local_policy.jar
    ├── server
    │   ├── Xusage.txt
    │   ├── libjsig.diz
    │   ├── libjsig.dylib
    │   ├── libjvm.diz
    │   └── libjvm.dylib
    └── tzdb.dat


That's all. In all this OSX Mavericks occupies about 47 MB. We can also involve archiving and delete some debug opportunities which all the same will not be necessary for us in the prodakshena. The most compact distribution kit which I managed to create turned out by means of such command:


jlink --modulepath $JAVA9_BIN/../../images/jmods/:two-modules-ok/bin 
    --addmods de.codecentric.addresschecker --output linkedjdk --exclude-files *.diz 
    --compress-resources on --strip-java-debug on --compress-resources-level 2


The size of a distribution kit decreases approximately to 18 MB, in my opinion – it is just magnificent. In Linux it is probably possible to be pressed down also to 13.

By a challenge

/bin/java --listmods


The module list, contained in this distribution kit is output

de.codecentric.addresschecker
de.codecentric.zipvalidator
java.base@9.0


So, all applications depending on the maximum number of these modules can work at this JVM. But I did not manage to receive our main class for start of this scenario. For this purpose I went some other way.

The attentive reader could notice that the second challenge becomes to jlink, and a way to modules other there, than by the first challenge. We specify bin catalog path in the second case. This directory contains modular jar-files, and jar for addresschecker also contains information on the main class in the manifesto. The utility of jlink uses this information to add additional information to the bin-directory of our JVM:

linkedjdk/
├── bin
│   ├── de.codecentric.addresschecker
│   ├── java
│   └── keytool
...


So, now we can cause our application directly. Beauty!

./linkedjdk/bin/de.codecentric.addresschecker 76185


displays

76185 is a valid zip code

Conclusion

Here also our acquaintance to Jigsaw came to an end. We reviewed a number of the examples illustrating that is possible and what cannot be made by means of Jigsaw and Java 9. Jigsaw introduces basic changes which cannot be compensated so easily with the help a lambda expressions or resources try-with . All our tulcheyn from assembly tools like Maven or Gradle to IDE it is necessary to adapt to building block system. At the JavaOne conference Hans Docter from Gradle Inc. read the report on how it is possible to start writing of a modular code even on Java 8 below. Gradle executes check in compilation time and makes refusal if integrity of the module is broken. This (experimental) opportunity was included in the last release of Gradle 2.9. We are definitely waited by interesting times!

For more detailed acquaintance to Jigsaw I recommend you the home page Jigsaw Project, especially slides and video of reports on Jigsaw from the last JavaOne conference again.

This article is a translation of the original post at habrahabr.ru/post/272861/
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