Meet Java Modules

As a warm up let's see how JDK itself was modularised.

List module files

New folder with JDK modules. jmod is currently internal jdk format for module file (jar on steroids)

ls -al  /usr/lib/jvm/jdk-9/jmods/
total 134544
drwxr-xr-x 2 root root     4096 wrz  2 15:17 .
drwxr-xr-x 8 root root     4096 wrz 24 16:35 ..
-rw-r--r-- 1 uucp  143    60992 sie  3 06:24 java.activation.jmod
-rw-r--r-- 1 uucp  143 19179351 sie  3 06:24 java.base.jmod
-rw-r--r-- 1 uucp  143   111302 sie  3 06:24 java.compiler.jmod
-rw-r--r-- 1 uucp  143  2680154 sie  3 06:24 java.corba.jmod
-rw-r--r-- 1 uucp  143    51795 sie  3 06:24 java.datatransfer.jmod
-rw-r--r-- 1 uucp  143 15766124 sie  3 06:24 java.desktop.jmod
-rw-r--r-- 1 uucp  143   765862 sie  3 06:24 javafx.base.jmod
-rw-r--r-- 1 uucp  143  2511426 sie  3 06:24 javafx.controls.jmod
-rw-r--r-- 1 uucp  143   238532 sie  3 06:24 javafx.deploy.jmod
(...)
-rw-r--r-- 1 uucp  143   119985 sie  3 06:24 java.logging.jmod
-rw-r--r-- 1 uucp  143   880650 sie  3 06:24 java.management.jmod
-rw-r--r-- 1 uucp  143    90550 sie  3 06:24 java.management.rmi.jmod
-rw-r--r-- 1 uucp  143   443764 sie  3 06:24 java.naming.jmod
-rw-r--r-- 1 uucp  143    62499 sie  3 06:24 java.prefs.jmod

jmod

With java 9 a new option to describe module was added.

  • try also: java -d jdk.unsupported
jmod describe  /usr/lib/jvm/jdk-9/jmods/java.base.jmod 
java.base@9
exports java.io
exports java.lang
exports java.lang.annotation
exports java.lang.invoke
exports java.lang.module
exports java.lang.ref
exports java.lang.reflect
exports java.math
...

list modules

java --list-modules | column

java -d java.sql
java.sql@9
exports java.sql
exports javax.sql
exports javax.transaction.xa
requires java.base mandated
requires java.logging transitive
requires java.xml transitive
uses java.sql.Driver

module system

Intro

We are going to use recent version of Intellij (2018 or newer) . In this workshop perform actions step by step.

Create New Project

File -> New -> Project -> Empty Project

Name : modulesjug

Create New Java Module

  • Name : com.jug.modules.intro.math

  • Press F4 and in project settings choose language level Java 9

  • Alt + Insert on src -> new module.info.java

Create New Package

  • Name : com.jug.modules.intro.math

  • Create New Class MathLib.java

package com.jug.modules.intro.math;

import java.util.Optional;

public class MathLib {

    public static Optional<Integer> add(Integer i1, Integer i2){
        return Optional.of(i1+i2);
    }

}
  • Create Main class in math package
package com.jug.modules.intro.math;

public class MathMain {
    public static void main(String[] args) {
        System.out.println("adding in math lib : "+MathLib.add(1,2));
    }
}
  • create empty folder mods in the project folder on modules level

    mkdir mods
    
    >ls
    com.jug.modules.intro.math  mods
    
  • compile module

you can create bash file -> gedit compilemod.sh &
#!/bin/bash

chmod 700 compilemod.sh
javac -d mods/com.jug.modules.intro.math  com.jug.modules.intro.math/src/module-info.java /
com.jug.modules.intro.math/src/com/jug/modules/intro/math/MathLib.java /
com.jug.modules.intro.math/src/com/jug/modules/intro/math/MathMain.java
tree mods
mods
└── com.jug.modules.intro.math
    ├── com
    │   └── jug
    │       └── modules
    │           └── intro
    │               └── math
    │                   ├── MathLib.class
    │                   └── MathMain.class
    └── module-info.class
  • Run Module
java --module-path mods -m com.jug.modules.intro.math/com.jug.modules.intro.math.MathMain
adding in math lib : Optional[3]

Module Encapsulation

  • add internal package
package com.jug.modules.intro.math.internal
  • Put there utility class OverflowDetector
public class OverflowDetector {
    public static Optional<Integer> add(int a, int b){
        try{
            return Optional.of(java.lang.Math.addExact(a,b));
        }catch(Exception e){
            return Optional.empty();
        }
    }
}
  • export main package
module com.jug.modules.intro.math {
    exports com.jug.modules.intro.math;
}
  • use OverflowDetector

    • public class MathLib {
      
          public static Optional<Integer> add(Integer i1, Integer i2){
              return OverflowDetector.add(i1,i2);
          }
      
      }
      
  • compile full module

javac -d mods/com.jug.modules.intro.math $(find com.jug.modules.intro.math/src/ -name *.java)
  • show modules description
java --module-path mods  --describe-module com.jug.modules.intro.math 
file:///home/pawel/projects/workshops/modulesjug/mods/com.jug.modules.intro.math/
exports com.jug.modules.intro.math
  • show cyclic dependency by requiring itself

Second Module - Displayer

  • create new module com.jug.modules.intro.displayer in the same parent folder as math module.

  • add module-info.java

    • requires com.jug.modules.intro.math;
  • add Displayer class

package com.jug.modules.intro.displayer;

import com.jug.modules.intro.math.MathLib;

import java.util.Optional;
import java.util.Scanner;
import java.util.function.Consumer;

public class Displayer {

    private static Consumer<Integer> printResult=(i) -> System.out.println("result is : "+i);
    private static Runnable errorProcedure = () -> System.out.println("unable to add integeres, overflow");

    public static void displayMath(){
        Scanner scanner = new Scanner(System.in);
        System.out.print("i1 : ");
        Integer i1=scanner.nextInt();
        System.out.print("i2 : ");
        Integer i2=scanner.nextInt();


        Optional<Integer> result = MathLib.add(i1, i2);

        result.ifPresentOrElse(printResult,errorProcedure);
    }

    public static void main(String[] args) {
        displayMath();
    }
}
  • try to compile
javac -d mods/com.jug.modules.intro.displayer $(find com.jug.modules.intro.displayer/src/ -name *.java)

com.jug.modules.intro.displayer/src/module-info.java:2: error: module not found: com.jug.modules.intro.math
    requires com.jug.modules.intro.math;
  • add module path
javac -d mods/com.jug.modules.intro.displayer --module-path mods  $(find com.jug.modules.intro.displayer/src/ -name *.java)
  • test displayer with input : MAX_INT = 2147483647
java --module-path mods -m com.jug.modules.intro.displayer/com.jug.modules.intro.displayer.Displayer

i1 : 2147483647
i2 : 2147483647
unable to add integeres, overflow
  • try to access internal math package
import com.jug.modules.intro.math.MathLib.internal;

com.jug.modules.intro.displayer/src/com/jug/modules/intro/displayer/Displayer.java:4: error: cannot find symbol
import com.jug.modules.intro.math.MathLib.internal;
  • remove math module and try to compile displayer
java --module-path mods -m com.jug.modules.intro.displayer/com.jug.modules.intro.displayer.Displayer
Error occurred during initialization of boot layer
java.lang.module.FindException: Module com.jug.modules.intro.math not found, required by com.jug.modules.intro.displayer
  • compile math once again
javac -d mods/com.jug.modules.intro.math  $(find com.jug.modules.intro.math/src/ -name *.java)
  • java --show-module-resolution
java --show-module-resolution --limit-modules com.jug.modules.intro.math  --module-path mods -m com.jug.modules.intro.displayer
root com.jug.modules.intro.displayer file:///home/pawel/projects/workshops/modulesjug/mods/com.jug.modules.intro.displayer/
com.jug.modules.intro.displayer requires com.jug.modules.intro.math file:///home/pawel/projects/workshops/modulesjug/mods/com.jug.modules.intro.math/
module com.jug.modules.intro.displayer does not have a MainClass attribute, use -m <module>/<main-class>
  • decompile
Compiled from "module-info.java"
module com.jug.modules.intro.displayer {
  requires com.jug.modules.intro.math;
  requires java.base;
}

Packaging to jars

jar --create --file modsjars/modules.math.jar -C mods/com.jug.modules.intro.math/ .
  • try to recompile displayer. Because we have math packed in jar so technically we have two modules with the same name
javac -d mods/com.jug.modules.intro.displayer --module-path mods  $(find com.jug.modules.intro.displayer/src/ -name *.java)
error: duplicate module on application module path
  module in com.jug.modules.intro.math
1 error
  • move Jar to different folder
javac -d mods/com.jug.modules.intro.displayer --module-path modsjars  $(find com.jug.modules.intro.displayer/src/ -name *.java)
jar --create --file modsjars/modules.displayer.jar --main-class com.jug.modules.intro.displayer.Displayer -C mods/com.jug.modules.intro.displayer/ .

java --module-path modsjars --module com.jug.modules.intro.displayer
i1 : 4
i2 : 7
result is : 11

//  --show-module-resultion
//  jar --list --file mods/modules.displayer.jar
//  jar --describe-module --file mods/monitor.observer.jar
// -Xlog:module*=debug:stdout:tid
  • Keep jar and folder to show ambigous modules

Automatic Modules

jar --file=/home/pawel/.m2/repository/commons-lang/commons-lang/2.5/commons-lang-2.5.jar --describe-module
No module descriptor found. Derived automatic module.

[email protected] automatic
requires java.base mandated
contains org.apache.commons.lang
contains org.apache.commons.lang.builder
contains org.apache.commons.lang.enums
contains org.apache.commons.lang.exception
contains org.apache.commons.lang.math
contains org.apache.commons.lang.mutable
contains org.apache.commons.lang.reflect
contains org.apache.commons.lang.text
contains org.apache.commons.lang.time

JLink

jlink --module-path mods:/usr/lib/jvm/jdk-9/jmods --add-modules com.jug.modules.intro.displayer --output hellojre

modulesjug$ hellojre/bin/java --list-modules 
com.jug.modules.intro.displayer
com.jug.modules.intro.math
java.base@9

Run :

hellojre/bin/java  -m com.jug.modules.intro.displayer/com.jug.modules.intro.displayer.Displayer

EXERCISE PART 1

  1. Create new module - com.exercise.consumer
  2. Set Java level in IDE to Java 9
  3. Create - empty for now - module-info.java
  4. Create new module - com.exercise.producer
  5. Set Java Level to Java 9
  6. Create module-info.java
  7. In producer module create package : com.exercise.producer
  8. In com.exercise.producer create new class which produces List of Strings according to

    class Producer{
        public static List<String> produce(){....}
    }
    
    Producer.produce mustBe List("one","two","three") //test assertion in pseudo code
    
  9. Add proper export to module.info

  10. Add dependency to producer in consumer

    1. you may need to add dependency in IDE properties
  11. Create packages the same as module name

  12. Add Consumer class + imports

    public class Consumer { 
        public static void main(String[] args) { 
            String result=Producer.produce().stream().collect(Collectors.joining("|")); 
            System.out.println(result); 
        } 
    }
  1. When you run it the result should be

    1. one|two|three
      
  2. Create another module com.exercise.Printer

    1. create module

    2. create package

    3. create module-info.java

    4. in com.exercise.Printer add an interface

    5. public interface Printer {
          void print(String s);
      }
      
    6. create another package which will be hidden in the module. - com.exercise.printer.console

    7. create printer implementation

    8. package com.exercise.printer.console;
      
      import com.exercise.printer.Printer;
      
      public class ConsolePrinter implements Printer {
          @Override
          public void print(String s) {
              System.out.println("PRINTING: "+s);
          }
      }
      
    9. create provider in the exported package com.exercise.printer

    10. package com.exercise.printer;
      import com.exercise.printer.console.ConsolePrinter;
      public abstract class AbstractFactoryPrinterProviderBean {
          public static Printer providePrinter(){
              return new ConsolePrinter();
          }
      }
      
  3. Use Printer in consumer module

    1. update module-info

    2. package com.exercise.consumer;
      
      import com.exercise.printer.AbstractFactoryPrinterProviderBean;
      import com.exercise.printer.Printer;
      import com.exercise.producer.Producer;
      
      import java.util.stream.Collectors;
      
      public class Consumer {
          public static void main(String[] args) {
              String result=Producer.produce().stream().collect(Collectors.joining("|"));
      
              Printer printer=AbstractFactoryPrinterProviderBean.providePrinter();
      
              printer.print(result);
          }
      }
      
    3. Result should be : PRINTING: one|two|three

    4. It should be forbidden to access ConsolePrinter directly

    5. //THIS IS FORBIDDEN!!
      import com.exercise.printer.console.ConsolePrinter;
      Printer printer=new ConsolePrinter();
      
  4. Compile everything into mods folder

    1. First displayer

    2. javac --module-path mods  -d mods/com.exercise.printer $(find com.exercise.printer/src/ -name *.java)
      
    3. Then producer

    4. Finally consumer

  5. Run Consumer

    1. java --module-path mods -m com.exercise.consumer/com.exercise.consumer.Consumer
      
  6. Create Consumer Jar

  7. Create Custom JRE for our Jar with JLink

  8. Run Consumer on Customer JRE

Maven

First clone repo : https://github.com/PawelWlodarski/mvnmodules

parent pom

  • configure Java9 in the main pom.xml
<properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <maven.compiler.target>1.9</maven.compiler.target>
        <maven.compiler.source>1.9</maven.compiler.source>
</properties>
  • configure modules in the main pom.xml.
<modules>
        <module>math</module>
        <module>calculator</module>
        <module>gui</module>
    </modules>
  • configure build plugin in the main pom.xml
  <plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-compiler-plugin</artifactId>
    <version>3.7.0</version>
    <configuration>
        <source>9</source>
        <target>9</target>
        <showWarnings>true</showWarnings>
        <showDeprecation>true</showDeprecation>
    </configuration>
</plugin>

module pom

  • gui modules pom.xml and check dependency on other module
  <dependencies>
        <dependency>
            <groupId>lodz.jug</groupId>
            <artifactId>calculator</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>
   </dependencies>
  • module-info.java in gui/src/main/java
module com.jug.modules.intro.gui {
    requires com.jug.modules.intro.calculator;
}

Decoupling

Show EngineProvider. How it connects with services?

Compile Modules

mvn clean install

all jars will be saved in mods folder because of setting in the main pom.xml

<outputDirectory>../mods</outputDirectory>

Now try to run gui.

java --module-path mods --module com.jug.modules.intro.gui/com.jug.modules.intro.gui.GUI

Services

Services allows you to decouple provider of a given service implementation from its consumer

You can find three modules in the main pom.xml

<module>displayerAPI</module>
<module>displayerProvider1</module>
<module>displayerConsumer</module>

Those three modules use displayer API but consumer and provider are not aware of itself.

First let's look at the API.

package com.jug.modules.displayer.api;

public interface Displayer {
    boolean supports(DisplayerPlatform p);

    void display(String s);
}

It's an normal interface yet purpose of support method may be not clear at first. Generally Each implementation can support different features and because consumer will not be aware of implementation so it can not choose one specific. We will see more in consumer module.

Also nothing specific in module-info

module com.jug.modules.displayer.api{
    exports com.jug.modules.displayer.api;
}

No we will see something new in producer module. Producer has to say that it is able to provides specific implementation of a service

module displayerProvider1 {
    requires com.jug.modules.displayer.api;


    provides com.jug.modules.displayer.api.Displayer
            with com.jug.modules.displayer.console.ConsoleDisplayer;
}

No consumer has to declare usage of this service

module displayerConsumer {
    requires com.jug.modules.displayer.api;

    uses com.jug.modules.displayer.api.Displayer;
}

And finally we have to use service locator to choose proper implementation. Here is when support method may become useful.

ServiceLoader<Displayer> displayers = ServiceLoader.load(Displayer.class);

Stream<ServiceLoader.Provider<Displayer>> providerStream = displayers.stream();


Optional<Displayer> firstPotentialDisplayer = providerStream
        .map(ServiceLoader.Provider::get)
        .filter(d -> d.supports(DisplayerPlatform.LINUX))
        .findFirst();


Displayer displayer = firstPotentialDisplayer.orElseThrow(() -> new RuntimeException("No Displayer her"));


displayer.display("Displayer Found Successfully : "+displayer.getClass());

Services Exercise (Together)

  • Create new module in main pom.xml
  • Add another implementation of Displayer
  • Declare in module-info that you are providing implementation
  • in consumer - just for - print all provided implementations

Dependencies Configuration

requires transitive

opens

opens <package-name>
opens <package-name> to <package_in_other_module>
  • --add-opens

qualified exports

* Persistence Framework

exports javamodularity.easytext.gui to javafx.graphics;

unnamed module

  • classpath emulation
  • Code in the unnamed module (the class path) can access code in any named modules using reflection
    • --illegal-access flag is set by default for this module

Compile myUnnamed and runt he following

projects/workshops/mvnmodules$ jar --file mods/myUnnamed-1.0-SNAPSHOT.jar --describe-module

No module descriptor found. Derived automatic module.

[email protected] automatic
requires java.base mandated
contains com.jug.intro.unnamed

jdeps

deps math-1.0-SNAPSHOT.jar 
com.jug.modules.intro.math
 [file:///home/pawel/projects/workshops/mvnmodules/mods/math-1.0-SNAPSHOT.jar]
   requires mandated java.base (@9)
com.jug.modules.intro.math -> java.base
   com.jug.modules.intro.math                         -> com.jug.modules.intro.math.internal                com.jug.modules.intro.math
   com.jug.modules.intro.math                         -> java.io                                            java.base
   com.jug.modules.intro.math                         -> java.lang                                          java.base
   com.jug.modules.intro.math                         -> java.lang.invoke                                   java.base
   com.jug.modules.intro.math                         -> java.util                                          java.base
   com.jug.modules.intro.math.internal                -> java.lang                                          java.base
   com.jug.modules.intro.math.internal                -> java.util                                          java.base
  • with modules dependencies
jdeps --module-path mods mods/gui-1.0-SNAPSHOT.jar 

com.jug.modules.intro.gui
 [file:///home/pawel/projects/workshops/mvnmodules/mods/gui-1.0-SNAPSHOT.jar]
   requires com.jug.modules.intro.calculator
   requires mandated java.base (@9)
com.jug.modules.intro.gui -> com.jug.modules.intro.calculator
com.jug.modules.intro.gui -> java.base
   com.jug.modules.intro.gui                          -> com.jug.modules.intro.calculator                   com.jug.modules.intro.calculator

list modules + custom modules

java --module-path out --list-modules

Qualified exports

exports    moduleb.privateA    to    A;    //    Exported    only    to    module    A    
exports    moduleb.privateC    to    C;    //    Exported    only    to    module    C

java    -d    java.base    
        module    java.base@9
        ...    
        exports    jdk.internal.ref    to    java.desktop,    javafx.media    
        exports    jdk.internal.math    to    java.desktop    
        exports    sun.net.ext    to    jdk.net    
        exports    jdk.internal.loader    to    java.desktop,    java.logging

Optionality

require static

Links

results matching ""

    No results matching ""