πŸ“œ ⬆️ ⬇️

Java 9 First Steps and the Jigsaw Project - Part Two

Hello, Habr.

After some delay, publish the second part of the article on the project Jigsaw and Java 9, published in the blog Codecentric. The translation of the first part is here .


This is the second part of the article for those who want to learn more about the Jigsaw project. In the first part, we briefly discussed what a module is and how the Java Runtime was modularized. Then we looked at a simple example of compiling, packaging, and running a modular application.
')
Here we will try to answer the following questions:



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

Granting read permission to specific modules.

In the first part, we talked about how Java accessibility is developing within the framework of Jigsaw. One of the levels of accessibility that was mentioned, but not clarified as it should be, is: β€œpublic for some modules, those that read this module”. So we can limit the range of modules that will be allowed to read our exported packages. Suppose the developers of de.codecentric.zipvalidator
de.codecentric.zipvalidator
hate developers de.codecentric.nastymodule
de.codecentric.nastymodule
, therefore can change the module-info.java
module-info.java
like this:

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


Thus, only the addresschecker
addresschecker
can access the zipvalidator
API zipvalidator
. This instruction is carried out at the packet level, so nothing prevents you from restricting access to some packages, while at the same time giving full access to others. This practice is referred to as β€œ qualified export ”. If the module is de.codecentric.nastymodule
de.codecentric.nastymodule
will try to access any type from de.codecentric.zipvalidator.api
de.codecentric.zipvalidator.api
, then a compilation error will occur:
 ./de.cc.nastymodule/de/cc/nastymodule/internal/AddressCheckerImpl.java:4: error: ZipCodeValidatorFactory is not visible because package de.cc.zipvalidator.api is not visible 


Please note: the program does not swear on module-info.java
module-info.java
since zipvalidator
zipvalidator
in principle could export visible packages to nastymodule
nastymodule
. For example, qualified export can be applied when you want to modularize the internal structure of your application, but do not want to share exported internal module packages with clients.

Conflicts between module versions

It often happens that through transitive dependencies different versions of a library fall into the same application β€” that is, the same module can appear twice in the path to the modules. Two scenarios immediately come to mind:



Let's try to compile the application in the first scenario. Copied zipvalidator
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 


Duplicate modules are in different directories, but the module name remains unchanged. How does jigsaw respond to this during compilation?

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


So, here we can not easily get off. Jigsaw gives a compilation error when there are two modules of the same name on the module path.

What about the second case? The directory structure remains the same, but now both zipvalidators get different names ( de.codecentric.zipvalidator.v{1|2}
de.codecentric.zipvalidator.v{1|2}
), and the addresschecker reads both of these names.

 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? Read two modules exporting the same packages? It turns out to be compiled. I myself was surprised: the compiler recognizes the situation, but is limited 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 a warning and start the application. But Jigsaw clearly doesn’t like what he sees at 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 seems to me incomprehensible, in my opinion, a compile-time error could be made more accurate. I wondered in the mailing list why this option was chosen, but at the time of writing this article I haven’t received an answer.

Automatic modules and unnamed module

Until now, we have worked in a fully modularized environment. But what to do in such highly probable cases when you have to deal with non-modular Jar files? This is where automatic modules and a nameless module come into play.

Let's start with automatic modules . An automatic module is a jar file supplied in a modulepath. After you write it there, you can answer the following three questions about this module:

Q: What is his name?
A: This is the name of the jar file. If you put the guava.jar file in the module path, you will get the automatic guava module.

This also means that you cannot use Jar directly from the Maven repository, since guava-18.0 is not a valid Java identifier.

Q: What does it export?
A: Automatic module exports all its packages. So, all public types will be available to any module that reads an automatic module.

Q: What does he require?
A: The automatic module reads all (* all *) other available modules (including unnamed, more on this below). It is important! From the automatic module, you can access all exported types of any module. This point is nowhere to indicate, it is implied.

Consider an example. We are starting to use com.google.common.base.Strings in zipvalidator. To allow such access, we must define a read edge for the automatic Guava module:

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


To compile, you will need to specify the guava.jar file in the module path (it is located in the ../jars directory):
 javac -d . -modulepath ../jars -modulesourcepath . $(find . -name "*.java") 


Everything compiles perfectly and runs.

(By the way, it was not so easy to run this example. While working with the Jigsaw 86 build, I ran into some problems: the system cursed dependencies on the jdk.management.resource
module jdk.management.resource
. I asked about this in the ezine, the discussion is here .

I must say that in my decision I did not use the β€œearly access” build (early access build), but I collected the JDK myself. When working with OSX Mavericks, there were still some problems, as described in the thread, I had to change the makefile, but in the end I fixed everything. You may have to deal with other problems when working with the following releases).

Now is the time to introduce you to the magic wand, which is indispensable when moving to Jigsaw. This tool is called jdeps
jdeps
. It scans your unmodularized code and tells you about dependencies.

Consider guava:

jdeps -s ../jars/guava.jar
We have the following conclusion:
guava.jar -> java.base
guava.jar -> java.logging
guava.jar -> not found

This means that the automatic guava module requires java.base
java.base
java.logging
java.logging
and ... β€œnot foundβ€œ ?! What? If you remove the switch -s
-s
then jdeps
jdeps
goes from the level of modules and goes down a step to the level of packages (the list is a bit reduced, since there are quite a lot of packages from guava):

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


Here you can see that the com.google.common.xml
package com.google.common.xml
depends on com.google.common.escape
com.google.common.escape
which is located in the module itself, java.lang
java.lang
which is well known and from the javax.annotation
annotation javax.annotation
which is not found. We conclude that we need a jar with JSR-305 types, because it contains javax.annotation
javax.annotation
(By the way, I get along without them - in my examples I don’t need any type of these packages, and neither the compiler nor the runtime object object).

Unnamed module

So, what is a nameless module? Again, answer three questions:

Q: What is his name?
A: You guessed it, he has no name

Q: What does it export?
A: The unnamed module exports all its packages to any other module. This does not mean that it can be read from any other module - it does not have a name and you cannot demand it! Team requires unnamed; will not work.

Q: What does he require?
A: The unnamed module reads all other available modules.

So, if you cannot read a nameless module from any of your modules, then what is the point? Our old friend helps answer this question - the path to the classes. Any type read from the classpath (and not from the modulepath) is automatically placed in the unnamed module β€” or, in other words, any type in the unnamed module is loaded via the classpath. Because the nameless module reads all other modules, we can access all exported types from any type loaded through the class path. In Java 9, the use of the class path and module paths will be supported both separately and together, to ensure backward compatibility. Consider a few examples.

Suppose we have a neat zipvalidator module, but the addresschecker is still not modularized, it does not have module-info.java
module-info.java
. The structure of our source will be as follows:

 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 to zipvalidator access, as well as the modulepath directory containing the zipvalidator module. We can compile our modules as usual. To compile the inherited code, we will need to provide information about the modular code. Just write it in the class path:
 javac -d classpath/de.codecentric.legacy.addresschecker -classpath modulepath/de.codecentric.zipvalidator/ $(find classpath -name "*.java") 


Everything works as usual.

During the performance, two possibilities open up before us. Namely:


Choosing the first option, we, in fact, abandon the modular system. All types that we write to the nameless module can now freely communicate with each other.

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


works exactly like the java application you use today.

On the other hand, mixed use of the module path and the class path works like this:

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


Use two switches at the same time: -classpath
-classpath
and -modulepath
-modulepath
. Added -addmods
switch -addmods
- when mixing the class path and the path to the modules, we cannot simply access any module in the modulepath directories, but should specifically indicate which of them should be available.

This approach also works fine, but there is a snag here! Remember, the answer to the question β€œwhat an unnamed module requires” is β€œall other modules”. If we use the zipvalidator module via modulepath, we can only work with its exported packages. Everything else will result in an IllegalAccessError at runtime. Therefore, in this case, you will have to follow the rules of the module system.

Creating Runtime Images with jlink

Enough examples with modules; There is another new tool that deserves our attention. jlink
Is a Java 9 utility for creating your own JVM distributions. The most interesting thing is that, thanks to the new modular architecture of the JDK, you can choose which modules you want to include in this distribution! Consider an example. If we want to create an image of the execution environment containing our addresschecker, then we give the command:

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

We specify only three things:



The team creates the following structure:

linkedjdk /
Bin── bin
β”‚ β”œβ”€β”€ java
β”‚ └── keytool
Conf── conf
β”‚ β”œβ”€β”€ net.properties
β”‚ └── security
β”‚ β”œβ”€β”€ java.policy
β”‚ └── java.security
Lib── lib
Class── classlist
J── 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 OSX Mavericks, all this takes about 47 MB. We can also enable archiving and delete some debugging features that we still will not need in production. The most compact distribution that I managed to create turned out with the help of this 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 the distribution is reduced to about 18 MB, in my opinion - just great. In Linux, you can probably shrink down to 13.

When calling

 /bin/java --listmods 


Displays a list of modules contained in this distribution

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


So, all applications depending on the maximum number of these modules can run on this JVM. But I was not able to get our main class to run this script. For this, I went the other way.

The attentive reader may have noticed that the second call is being made to jlink, and the path to the modules there is different from the first call. In the second case, we specify the path to the bin directory. This directory contains modular jar files, and the jar for addresschecker also contains in its manifest information about the main class. The jlink utility 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 call our application directly. Beauty!

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


displays

76185 is a valid zip code


Conclusion

This is the end of our encounter with Jigsaw. We looked at a number of examples that illustrate what can and cannot be done using Jigsaw and Java 9. Jigsaw introduces fundamental changes that cannot be so easily compensated for using lambda expressions or resources try-with
try-with
. Our entire toolchain from build tools like Maven or Gradle to IDE will have to be adapted to the modular system. At the JavaOne conference, Hans Docter from Gradle Inc. I read a report on how to start writing modular code, even in Java 8 and below. Gradle performs a check at compile time and fails if the integrity of the module is compromised. This (experimental) feature was included in the latest release of Gradle 2.9 . Interesting times are definitely waiting for us!

For a more detailed introduction to the Jigsaw, I once again recommend the Jigsaw Project home page, especially the slides and videos of Jigsaw reports from the latest JavaOne conference.

Source: https://habr.com/ru/post/272861/


All Articles