├── README.adoc ├── images └── RebelLabs-Java-9-modules-cheat-sheet.png ├── javafx ├── .gitignore ├── README.adoc ├── dot │ ├── com.example.javafx.dot │ ├── summary.dot │ └── summary.png └── src │ ├── com │ └── example │ │ └── javafx │ │ ├── Account.java │ │ ├── ButtonController.java │ │ ├── CheckingAccount.java │ │ ├── NewFXMain.java │ │ └── SavingsAccount.java │ └── module-info.java ├── modi ├── .gitignore ├── README.adoc ├── arguments.args ├── jar │ └── modi.jar └── src │ └── com │ └── example │ └── modi │ ├── entrypoint │ └── Main.java │ └── query │ └── Modi.java ├── modim ├── .gitignore ├── Dockerfile ├── Dockerfile.jdk9-alpine ├── README.adoc ├── dot │ ├── com.example.modi.dot │ └── summary.dot ├── jar │ └── modi.jar └── src │ ├── com │ └── example │ │ └── modi │ │ ├── entrypoint │ │ └── Main.java │ │ └── query │ │ └── Modi.java │ └── module-info.java ├── modims-exports-requires ├── .gitignore ├── Dockerfile ├── README.adoc ├── dot │ ├── modi.api.dot │ ├── modi.entrypoint.dot │ ├── modi.mod.dot │ ├── modi.pack.dot │ ├── modi.provider.dot │ ├── summary-requires-exposes.dot │ ├── summary-requires-exposes.png │ ├── summary.dot │ └── summary.jpeg ├── jar │ ├── modi.api@1.1.0.jar │ ├── modi.entrypoint@1.1.0.jar │ ├── modi.mod@1.1.0.jar │ ├── modi.pack@1.1.0.jar │ └── modi.provider@1.1.0.jar ├── modi.api │ └── src │ │ ├── com │ │ └── modi │ │ │ └── api │ │ │ └── query │ │ │ └── Query.java │ │ └── module-info.java ├── modi.entrypoint │ └── src │ │ ├── com │ │ └── modi │ │ │ └── entrypoint │ │ │ └── Main.java │ │ └── module-info.java ├── modi.mod.impl │ └── src │ │ ├── com │ │ └── mod │ │ │ └── impl │ │ │ └── ModuleQuery.java │ │ └── module-info.java ├── modi.pack.impl │ └── src │ │ ├── com │ │ └── pack │ │ │ └── impl │ │ │ └── PackageQuery.java │ │ └── module-info.java └── modi.provider │ └── src │ ├── com │ └── modi │ │ └── provider │ │ ├── query │ │ └── QueryProvider.java │ │ └── util │ │ └── Flags.java │ └── module-info.java ├── modims-provides-uses ├── .gitignore ├── Dockerfile ├── README.adoc ├── dot │ ├── api │ │ ├── modi.api.dot │ │ └── summary.dot │ ├── mod │ │ ├── modi.api.dot │ │ ├── modi.mod.dot │ │ └── summary.dot │ ├── modi.api.dot │ ├── modi.entrypoint.dot │ ├── pack │ │ ├── modi.api.dot │ │ ├── modi.pack.dot │ │ └── summary.dot │ ├── summary-exports-requires.dot │ ├── summary-exports-requires.png │ ├── summary-w-provider.dot │ ├── summary-w-provider.png │ ├── summary.dot │ └── summary.jpeg ├── jar │ ├── modi.api@1.2.0.jar │ ├── modi.entrypoint@1.2.0.jar │ ├── modi.mod@1.2.0.jar │ └── modi.pack@1.2.0.jar ├── modi.api │ └── src │ │ ├── com │ │ └── modi │ │ │ └── api │ │ │ └── query │ │ │ ├── Query.java │ │ │ └── annotation │ │ │ └── Search.java │ │ └── module-info.java ├── modi.entrypoint │ └── src │ │ ├── com │ │ └── modi │ │ │ └── entrypoint │ │ │ └── Main.java │ │ └── module-info.java ├── modi.mod.impl │ └── src │ │ ├── com │ │ └── mod │ │ │ └── impl │ │ │ ├── mod │ │ │ └── ModuleQuery.java │ │ │ └── util │ │ │ └── Flags.java │ │ └── module-info.java └── modi.pack.impl │ └── src │ ├── com │ └── pack │ │ └── impl │ │ ├── pack │ │ └── PackageQuery.java │ │ └── utils │ │ └── Flags.java │ └── module-info.java ├── reflect ├── .gitignore ├── README.adoc ├── reflect.deep │ └── src │ │ ├── com │ │ └── reflect │ │ │ └── deep │ │ │ └── Gear.java │ │ └── module-info.java ├── reflect.entrypoint │ └── src │ │ ├── com │ │ └── reflect │ │ │ └── entrypoint │ │ │ └── Main.java │ │ └── module-info.java └── reflect.normal │ └── src │ ├── com │ └── reflect │ │ └── normal │ │ └── Machine.java │ └── module-info.java └── requires-transitive ├── .gitignore ├── Dockerfile ├── README.adoc ├── dot ├── entrypoint │ ├── employee.entrypoint.dot │ ├── employee.model.dot │ ├── employee.service.dot │ ├── summary-without-transitive.png │ ├── summary.dot │ └── summary.png ├── model │ ├── employee.model.dot │ └── summary.dot └── service │ ├── employee.model.dot │ ├── employee.service.dot │ └── summary.dot ├── employee.entrypoint ├── pom.xml └── src │ └── main │ └── java │ ├── employee │ └── entrypoint │ │ └── Main.java │ └── module-info.java ├── employee.model ├── pom.xml └── src │ └── main │ └── java │ ├── employee │ └── model │ │ └── Employee.java │ └── module-info.java ├── employee.service ├── pom.xml └── src │ └── main │ └── java │ ├── employee │ └── service │ │ ├── api │ │ └── EmployeeService.java │ │ └── impl │ │ └── EmployeeServiceImpl.java │ └── module-info.java ├── jar ├── employee.entrypoint-1.0.jar ├── employee.model-1.0.jar └── employee.service-1.0.jar └── pom.xml /README.adoc: -------------------------------------------------------------------------------- 1 | = Java 9 Module Examples 2 | 3 | What I have learned from the Resources list is that Module System in Java 9 is an optional decision. Your project could rely on the classpath as before whereas the module system brings several benefits to your applications such as modular thinking, encapsulation, dependency management and services. To this end, in this repository you'll find several practical Java 9 module examples that could give you some brief ideas. Feel free to open up https://github.com/ozlerhakan/java9-module-examples/issues/new[an issue], if you find any typos or mistakes that I have made. 4 | 5 | === Modi 6 | 7 | Shows how to compile and run an unnamed module. 8 | 9 | === ModiM 10 | 11 | Demonstrates how to compile and run an application module. 12 | 13 | === ModiMs-exports-requires 14 | 15 | Demonstrates how to compile and run multiple application modules using the `exports` and `requires` clauses. 16 | 17 | === ModiMs-provides-uses 18 | 19 | Demonstrates how to compile and run multiple application modules using the `provides ... with` and `uses` clauses. 20 | 21 | === ReT (Requires-Transitive) 22 | 23 | Shows the way of handling transitive dependencies on a module system. 24 | 25 | === Javafx 26 | 27 | Shows how to solve the issues while converting a javafx project into an application module. 28 | 29 | === Reflect 30 | 31 | Demostrates how to use the `opens` clause when using the reflection API in an application module. 32 | 33 | === Java Platform Module Cheat Sheet 34 | 35 | image:images/RebelLabs-Java-9-modules-cheat-sheet.png[link=https://zeroturnaround.com/rebellabs/java-9-modules-cheat-sheet/] 36 | 37 | === OS Projects 38 | 39 | * https://github.com/jodastephen/jpms-module-names[JPMS Module Names] 40 | 41 | === Resources 42 | 43 | * https://blog.codefx.org/java/[Nicolai Parlog Blog] 44 | * https://dzone.com/articles/jdk9-howto-create-a-java-run-time-image-with-maven[JDK 9: Creating a Java Runtime Image With Maven] 45 | * https://github.com/java9-modularity[Java 9 Modularity Samples] 46 | * https://github.com/ConSol/java9-modules-maven-junit-example[Java 9 Modules Example with Maven and JUnit] 47 | * https://github.com/eh3rrera/getting-started-jpms[Java 9 module system tutorials] 48 | * https://github.com/cfdobber/maven-java9-jigsaw[Example project with Maven, Java 9 and Jigsaw] 49 | * http://openjdk.java.net/jeps/282[Jlink: The Java Linker] 50 | * http://openjdk.java.net/jeps/220[Modular Run-Time Images] 51 | * https://stackoverflow.com/questions/46502453/[Readability Recap] 52 | * https://www.voxxed.com/2016/11/problem-modules-reflective-access/[How do you solve a problem like Java 9 modules and reflective access?] 53 | * https://labs.consol.de/development/2017/02/13/getting-started-with-java9-modules.html[Getting Started with Java 9 Modules] 54 | * https://docs.oracle.com/javase/9/migrate/toc.htm[JDK 9 Migration Guide] 55 | -------------------------------------------------------------------------------- /images/RebelLabs-Java-9-modules-cheat-sheet.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ozlerhakan/java9-module-examples/2f8bf4d44fbb6ea5c6599cf5ad7899d6956c5451/images/RebelLabs-Java-9-modules-cheat-sheet.png -------------------------------------------------------------------------------- /javafx/.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | out 3 | -------------------------------------------------------------------------------- /javafx/README.adoc: -------------------------------------------------------------------------------- 1 | = javafx 2 | 3 | The JavaFX project has been taken from the lesson 4 of the https://www.oracle.com/goto/JavaGame2[Java Puzzle Ball MOOC] in order to transform it to an application module. Don't feel that you need to first understand the codebase to make the project modular. This example is all about how likely to fix some problems you might face when migrating an javafx project. The graph below is generated by the following jdeps command: 4 | 5 | ---- 6 | $ jdeps --module-path out -dotoutput dot/ -recursive -m com.example.javafx 7 | ---- 8 | 9 | image:dot/summary.png[] 10 | 11 | It looks like we have 4 dependencies from the platform module. However if you look at the module description (i.e. `module-info.java`) of the project, you only see one dependency instead of three (we don't count the `java.base` module since it is an implicit dependency). Try to compile the application with an empty module descriptor and examine what the errors tell us. 12 | 13 | ---- 14 | $ javac -d out --source-path src/ $(find src/ -name '*.java') 15 | (package javafx.scene.control is declared in module javafx.controls, but module com.example.javafx does not read it) 16 | src/com/example/javafx/NewFXMain.java:7: error: package javafx.application is not visible 17 | import javafx.application.Application; 18 | ... 19 | ... 20 | (package javafx.application is declared in module javafx.graphics, but module com.example.javafx does not read it) 21 | src/com/example/javafx/NewFXMain.java:8: error: package javafx.scene is not visible 22 | import javafx.scene.Group; 23 | ... 24 | ... 25 | ---- 26 | 27 | The module could't read the types from the `javafx.controls` and `javafx.graphics` modules at compile time. We need to require the modules mentioned in the output whereas how the compilation is fixed by adding just one module must be known. Let's take a deep look at the mentioned modules's descriptors. The snippet below shows all we need about why the module only requires the `javafx.controls` module itself. If we add a dependency from the `javafx.controls` module, all two modules namely `javafx.graphics` and `javafx.base` are *transitively* resolved! thanks to the `transitive` keyword on the `requires` clause. Our application is able to read directly all the exported implementations of the three modules. In a way that we kill three birds with one stone! 28 | 29 | ---- 30 | module javafx.base { 31 | ... 32 | } 33 | 34 | module javafx.graphics { 35 | requires transitive javafx.base; 36 | ... 37 | } 38 | 39 | module javafx.controls { 40 | requires transitive javafx.base; 41 | requires transitive javafx.graphics; 42 | ... 43 | } 44 | ---- 45 | 46 | Adding the `requires javafx.controls` module, this time we run the same command and the result is shown with success. In this case, ignore the note appearing on the output. It's because collections are used without type specifiers in the `ButtonController` class. 47 | 48 | ---- 49 | $ javac -d out --source-path src/ $(find src/ -name '*.java') 50 | Note: src/com/example/javafx/ButtonController.java uses unchecked or unsafe operations. 51 | Note: Recompile with -Xlint:unchecked for details. 52 | ---- 53 | 54 | The module is now in the ready state: 55 | 56 | ---- 57 | $ java -p out/ -m com.example.javafx/com.example.javafx.NewFXMain 58 | Caused by: java.lang.IllegalAccessException: class com.sun.javafx.application.LauncherImpl (in module javafx.graphics) cannot access class com.example.javafx.NewFXMain (in module com.example.javafx) because module com.example.javafx does not export com.example.javafx to module javafx.graphics 59 | ---- 60 | 61 | Ups... it seems like It doesn't!. Why it didn't run is obvious that the module `javafx.graphics` could't access the `com.example.javafx.NewFXMain` class to launch the application. The `NewFXMain` class extends to the `Application` class. What's more, if we follow the workflow, we realise that the `Application` class uses `LauncherImpl.launchApplication(appClass, args);` to launch the application. It must access the class itself at runtime. Since the class itself (i.e. `NewFXMain`) isn't allowed to outside, the application fails. So be aware that compiling a project with success doesn't mean that the project will up and run at runtime. 62 | 63 | There are two steps I know to handle this issue. Either export the package to all other modules or export the package only to one particular module. 64 | 65 | .exports to the world 66 | ---- 67 | module com.example.javafx { 68 | 69 | ... 70 | 71 | exports com.example.javafx; 72 | } 73 | ---- 74 | 75 | .exports only to the `javafx.graphics` module 76 | ---- 77 | module com.example.javafx { 78 | 79 | ... 80 | 81 | exports com.example.javafx to javafx.graphics; 82 | } 83 | ---- 84 | 85 | The second one is more reliable because we explicitly say that only the module `javafx.graphics` will read the types from the `com.example.javafx` package at runtime. -------------------------------------------------------------------------------- /javafx/dot/com.example.javafx.dot: -------------------------------------------------------------------------------- 1 | digraph "com.example.javafx" { 2 | // Path: /Users/hakan/IdeaProjects/jigsaw/javafx/out 3 | "com.example.javafx" -> "com.example.javafx (com.example.javafx)"; 4 | "com.example.javafx" -> "java.io (java.base)"; 5 | "com.example.javafx" -> "java.lang (java.base)"; 6 | "com.example.javafx" -> "java.lang.invoke (java.base)"; 7 | "com.example.javafx" -> "java.util (java.base)"; 8 | "com.example.javafx" -> "java.util.function (java.base)"; 9 | "com.example.javafx" -> "java.util.stream (java.base)"; 10 | "com.example.javafx" -> "javafx.application (javafx.graphics)"; 11 | "com.example.javafx" -> "javafx.collections (javafx.base)"; 12 | "com.example.javafx" -> "javafx.event (javafx.base)"; 13 | "com.example.javafx" -> "javafx.scene (javafx.graphics)"; 14 | "com.example.javafx" -> "javafx.scene.control (javafx.controls)"; 15 | "com.example.javafx" -> "javafx.scene.input (javafx.graphics)"; 16 | "com.example.javafx" -> "javafx.scene.layout (javafx.graphics)"; 17 | "com.example.javafx" -> "javafx.scene.paint (javafx.graphics)"; 18 | "com.example.javafx" -> "javafx.scene.shape (javafx.graphics)"; 19 | "com.example.javafx" -> "javafx.scene.text (javafx.graphics)"; 20 | "com.example.javafx" -> "javafx.stage (javafx.graphics)"; 21 | } 22 | -------------------------------------------------------------------------------- /javafx/dot/summary.dot: -------------------------------------------------------------------------------- 1 | digraph "summary" { 2 | "com.example.javafx" -> "java.base (java.base)"; 3 | "com.example.javafx" -> "javafx.base (javafx.base)"; 4 | "com.example.javafx" -> "javafx.controls (javafx.controls)"; 5 | "com.example.javafx" -> "javafx.graphics (javafx.graphics)"; 6 | } 7 | -------------------------------------------------------------------------------- /javafx/dot/summary.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ozlerhakan/java9-module-examples/2f8bf4d44fbb6ea5c6599cf5ad7899d6956c5451/javafx/dot/summary.png -------------------------------------------------------------------------------- /javafx/src/com/example/javafx/Account.java: -------------------------------------------------------------------------------- 1 | /* Copyright © 2017 Oracle and/or its affiliates. All rights reserved. */ 2 | package com.example.javafx; 3 | 4 | public abstract class Account { 5 | //Fields 6 | protected String accountOwner; 7 | protected double balance; 8 | protected int accountNum; 9 | protected int transactions = 0; 10 | protected static int nextAccountNum = 0; 11 | 12 | //Constructor 13 | public Account(String o, double b) { 14 | accountOwner = o; 15 | balance = b; 16 | setAccountNumber(); 17 | System.out.println("New Account:"); 18 | printDetails(); 19 | } 20 | 21 | 22 | //Methods 23 | public void printDetails() { 24 | System.out.println("Account Owner: " + accountOwner); 25 | System.out.println("Balance: $" + balance); 26 | System.out.println("Transactions: " + transactions); 27 | } 28 | 29 | public void deposit(double x) { 30 | if (x > 0) { 31 | balance = balance + x; 32 | System.out.println("Deposit: $" + x); 33 | transactions++; 34 | } else { 35 | System.out.println("Cannot deposit negative amount: " + x); 36 | } 37 | printDetails(); 38 | } 39 | 40 | public void withdraw(double x) { 41 | if (x > 0 && (balance - x > 0)) { 42 | balance = balance - x; 43 | System.out.println("Withdraw: $" + x); 44 | transactions++; 45 | } else if (x < 0) { 46 | System.out.println("Cannot withdraw negative amount: " + x); 47 | } else if (balance - x < 0) { 48 | System.out.println("Cannot withdraw $" + x + " from $" + balance + " account"); 49 | System.out.println("Balance cannot go negative."); 50 | } 51 | printDetails(); 52 | } 53 | 54 | public void resetTransactions() { 55 | transactions = 0; 56 | System.out.println("Resetting Transactions to 0"); 57 | printDetails(); 58 | } 59 | 60 | private void setAccountNumber() { 61 | accountNum = nextAccountNum; 62 | nextAccountNum++; 63 | } 64 | 65 | //Getter Methods, used to get the values of fields 66 | public String getAccountOwner() { 67 | return accountOwner; 68 | } 69 | 70 | public double getBalance() { 71 | return balance; 72 | } 73 | 74 | public int getAccountNumber() { 75 | return accountNum; 76 | } 77 | 78 | public int getTransactions() { 79 | return transactions; 80 | } 81 | 82 | public abstract String getAccountType(); 83 | } 84 | -------------------------------------------------------------------------------- /javafx/src/com/example/javafx/ButtonController.java: -------------------------------------------------------------------------------- 1 | /* Copyright © 2017 Oracle and/or its affiliates. All rights reserved. */ 2 | package com.example.javafx; 3 | 4 | import java.util.ArrayList; 5 | import java.util.function.Predicate; 6 | 7 | import javafx.scene.control.Button; 8 | import javafx.scene.control.TextField; 9 | 10 | public class ButtonController { 11 | 12 | ArrayList accountList = new ArrayList<>(); 13 | TextField ownerSearchBar; 14 | TextField numberSearchBar; 15 | Button printDetail; 16 | Button closeAccount; 17 | Button deposit200; 18 | Button withdraw200; 19 | Button giveBonus; 20 | Button earnInterest; 21 | Button chargeFee; 22 | Button resetTransactions; 23 | Predicate matchAccountOwner = null; //Replace null 24 | Predicate matchAccountNumber = null; //Replace null 25 | 26 | 27 | public ButtonController(ArrayList list, TextField o, TextField n, Button b1, Button b2, Button b3, Button b4, Button b5, Button b6, Button b7, Button b8) { 28 | accountList = list; 29 | ownerSearchBar = o; 30 | numberSearchBar = n; 31 | printDetail = b1; 32 | closeAccount = b2; 33 | deposit200 = b3; 34 | withdraw200 = b4; 35 | giveBonus = b5; 36 | earnInterest = b6; 37 | chargeFee = b7; 38 | resetTransactions = b8; 39 | interactions(); 40 | } 41 | 42 | //Which method do you call when a button is pressed? 43 | private void interactions() { 44 | 45 | closeAccount.setOnMousePressed(e -> button2Pressed()); 46 | deposit200.setOnMousePressed(e -> button3Pressed()); 47 | 48 | 49 | earnInterest.setOnMousePressed(e -> button6Pressed()); 50 | 51 | 52 | } 53 | 54 | //Print details for each account belonging to the specified owner. 55 | private void button1Pressed() { 56 | 57 | 58 | } 59 | 60 | //Close each account belonging to the specified owner. 61 | private void button2Pressed() { 62 | accountList.stream() 63 | .filter(a -> a.getAccountOwner().equals(ownerSearchBar.getText())) 64 | .forEach(a -> { 65 | System.out.println("CLOSING"); 66 | a.printDetails(); 67 | }); 68 | accountList.removeIf(a -> a.getAccountOwner().equals(ownerSearchBar.getText())); 69 | } 70 | 71 | //Deposit $200 into the specified account. 72 | private void button3Pressed() { 73 | accountList.stream() 74 | .filter(a -> String.valueOf(a.getAccountNumber()).equals(numberSearchBar.getText())) 75 | .forEach(a -> a.deposit(200)); 76 | } 77 | 78 | //Withdraw $200 from the specified account. 79 | private void button4Pressed() { 80 | 81 | 82 | } 83 | 84 | //Deposit a bonus $20 for each savings account with a balance of at least $20,000. 85 | private void button5Pressed() { 86 | 87 | 88 | } 89 | 90 | //Earn interest for each savings account. 91 | private void button6Pressed() { 92 | accountList.stream() 93 | .filter(a -> a.getAccountType().equals("Savings Account")) 94 | .forEach(a -> ((SavingsAccount) a).earnInterest()); 95 | } 96 | 97 | //Charge a $35 fee for each checking account with fewer than 1 transactions. 98 | private void button7Pressed() { 99 | 100 | 101 | } 102 | 103 | //Reset all transactions to 0. 104 | private void button8Pressed() { 105 | 106 | 107 | } 108 | 109 | } 110 | -------------------------------------------------------------------------------- /javafx/src/com/example/javafx/CheckingAccount.java: -------------------------------------------------------------------------------- 1 | /* Copyright © 2017 Oracle and/or its affiliates. All rights reserved. */ 2 | package com.example.javafx; 3 | 4 | public class CheckingAccount extends Account { 5 | //Fields 6 | private static String accountType = "Checking Account"; 7 | 8 | 9 | //Constructor 10 | public CheckingAccount(String o, double b) { 11 | super(o, b); 12 | } 13 | 14 | 15 | //Methods 16 | public void printDetails() { 17 | System.out.println(accountType + " #" + accountNum); 18 | super.printDetails(); 19 | System.out.println(""); 20 | } 21 | 22 | public String getAccountType() { 23 | return accountType; 24 | } 25 | 26 | } 27 | -------------------------------------------------------------------------------- /javafx/src/com/example/javafx/NewFXMain.java: -------------------------------------------------------------------------------- 1 | /* Copyright © 2017 Oracle and/or its affiliates. All rights reserved. */ 2 | 3 | package com.example.javafx; 4 | 5 | import java.util.ArrayList; 6 | 7 | import javafx.application.Application; 8 | import javafx.scene.Group; 9 | import javafx.scene.Scene; 10 | import javafx.scene.control.Button; 11 | import javafx.scene.control.TextField; 12 | import javafx.scene.layout.HBox; 13 | import javafx.scene.paint.Color; 14 | import javafx.scene.paint.CycleMethod; 15 | import javafx.scene.paint.LinearGradient; 16 | import javafx.scene.paint.Stop; 17 | import javafx.scene.shape.Rectangle; 18 | import javafx.scene.text.Font; 19 | import javafx.scene.text.FontWeight; 20 | import javafx.scene.text.Text; 21 | import javafx.stage.Stage; 22 | 23 | public class NewFXMain extends Application { 24 | 25 | @Override 26 | public void start(Stage primaryStage) { 27 | final int APP_HEIGHT = 600; 28 | final int APP_WIDTH = 860; 29 | final int UI_Height = 28; 30 | final Font buttonFont = new Font(UI_Height); 31 | 32 | //Create the background 33 | Rectangle rect1 = new Rectangle(0, 0, APP_WIDTH, APP_HEIGHT); 34 | LinearGradient gradient1 = new LinearGradient(0, 1, 0, 0, true, CycleMethod.NO_CYCLE, new Stop[]{ 35 | new Stop(0, Color.BLACK), 36 | new Stop(.825, Color.LIME), 37 | new Stop(.875, Color.BLACK), 38 | }); 39 | rect1.setFill(gradient1); 40 | 41 | //Setup tool name and tagline 42 | Text toolName = new Text(); 43 | toolName.setFill(Color.WHITE); 44 | toolName.setFont(Font.font(null, FontWeight.BOLD, 48)); 45 | toolName.setX(0); 46 | toolName.setY(50); 47 | toolName.setText("BankTeller2000"); 48 | 49 | Text marketingJibberish = new Text(); 50 | marketingJibberish.setFill(Color.LIGHTGRAY); 51 | marketingJibberish.setFont(Font.font(null, FontWeight.BOLD, 24)); 52 | marketingJibberish.setX(2); 53 | marketingJibberish.setY(75); 54 | marketingJibberish.setText("Helping you bring home the bacon"); 55 | 56 | //Create the first row of buttons 57 | HBox buttonContainer1 = new HBox(10); 58 | buttonContainer1.setLayoutY(APP_HEIGHT * 0.5); 59 | buttonContainer1.setLayoutX(60); 60 | 61 | TextField ownerSearchBar = new TextField("Account Owner"); 62 | ownerSearchBar.setMinHeight(60); 63 | ownerSearchBar.setMinWidth(300); 64 | ownerSearchBar.setFont(buttonFont); 65 | ownerSearchBar.setMaxSize(140, 20); 66 | 67 | Button btn1 = new Button(); 68 | btn1.setFont(buttonFont); 69 | btn1.setText("Print Details"); 70 | 71 | Button btn2 = new Button(); 72 | btn2.setFont(buttonFont); 73 | btn2.setText("Close Accounts"); 74 | 75 | buttonContainer1.getChildren().addAll(ownerSearchBar, btn1, btn2); 76 | 77 | //Create the second row of buttons 78 | HBox buttonContainer2 = new HBox(10); 79 | buttonContainer2.setLayoutY(APP_HEIGHT * 0.65); 80 | buttonContainer2.setLayoutX(60); 81 | 82 | TextField numberSearchBar = new TextField("Account Number"); 83 | numberSearchBar.setMinHeight(60); 84 | numberSearchBar.setMinWidth(300); 85 | numberSearchBar.setFont(buttonFont); 86 | numberSearchBar.setMaxSize(140, 20); 87 | 88 | Button btn3 = new Button(); 89 | btn3.setFont(buttonFont); 90 | btn3.setText("Deposit $200"); 91 | 92 | Button btn4 = new Button(); 93 | btn4.setFont(buttonFont); 94 | btn4.setText("Withdraw $200"); 95 | 96 | buttonContainer2.getChildren().addAll(numberSearchBar, btn3, btn4); 97 | 98 | //Create the third row of buttons 99 | HBox buttonContainer3 = new HBox(10); 100 | buttonContainer3.setLayoutY(APP_HEIGHT * 0.8); 101 | buttonContainer3.setLayoutX(10); 102 | buttonContainer3.setMaxWidth(APP_WIDTH); 103 | 104 | Button btn5 = new Button(); 105 | btn5.setFont(buttonFont); 106 | btn5.setText("Give Bonus"); 107 | 108 | Button btn6 = new Button(); 109 | btn6.setFont(buttonFont); 110 | btn6.setText("Earn Interest"); 111 | 112 | Button btn7 = new Button(); 113 | btn7.setFont(buttonFont); 114 | btn7.setText("Charge Fee"); 115 | 116 | Button btn8 = new Button(); 117 | btn8.setFont(buttonFont); 118 | btn8.setText("Reset Transactions"); 119 | 120 | buttonContainer3.getChildren().addAll(btn5, btn6, btn7, btn8); 121 | 122 | //Other setup and display stuff 123 | Group root = new Group(); 124 | root.getChildren().addAll(rect1, toolName, marketingJibberish, buttonContainer1, buttonContainer2, buttonContainer3); 125 | 126 | primaryStage.setTitle("BankTeller2000"); 127 | primaryStage.setScene(new Scene(root, APP_WIDTH, APP_HEIGHT, Color.BLACK)); 128 | primaryStage.show(); 129 | 130 | //Create accounts 131 | ArrayList accountList = new ArrayList<>(); 132 | accountList.add(new SavingsAccount("Duke", 100)); 133 | accountList.add(new SavingsAccount("Damien", 20000)); 134 | accountList.add(new SavingsAccount("Jessica", 50000)); 135 | accountList.add(new SavingsAccount("Herbert", 500)); 136 | accountList.add(new CheckingAccount("Duke", 0)); 137 | accountList.add(new CheckingAccount("Jessica", 500)); 138 | 139 | //create button controller 140 | ButtonController buttonController = new ButtonController(accountList, ownerSearchBar, numberSearchBar, btn1, btn2, btn3, btn4, btn5, btn6, btn7, btn8); 141 | } 142 | 143 | public static void main(String[] args) { 144 | launch(args); 145 | } 146 | 147 | } 148 | -------------------------------------------------------------------------------- /javafx/src/com/example/javafx/SavingsAccount.java: -------------------------------------------------------------------------------- 1 | /* Copyright © 2017 Oracle and/or its affiliates. All rights reserved. */ 2 | package com.example.javafx; 3 | 4 | public class SavingsAccount extends Account { 5 | //Fields 6 | private static String accountType = "Savings Account"; 7 | private static double interestRate = 0.02; 8 | 9 | 10 | //Constructor 11 | public SavingsAccount(String o, double b) { 12 | super(o, b); 13 | } 14 | 15 | //Methods 16 | public void printDetails() { 17 | System.out.println(accountType + " #" + accountNum); 18 | super.printDetails(); 19 | System.out.println("Interest Rate: " + interestRate); 20 | System.out.println(""); 21 | } 22 | 23 | public void earnInterest() { 24 | balance = balance * (1 + interestRate / 12); 25 | System.out.println("Interest: $" + balance * (interestRate / 12)); 26 | printDetails(); 27 | } 28 | 29 | public String getAccountType() { 30 | return accountType; 31 | } 32 | 33 | } 34 | -------------------------------------------------------------------------------- /javafx/src/module-info.java: -------------------------------------------------------------------------------- 1 | /** 2 | * we can give a description to a module 3 | * 4 | * @since 1.1.1 5 | */ 6 | module com.example.javafx { 7 | 8 | requires javafx.controls; 9 | 10 | exports com.example.javafx to javafx.graphics; 11 | } 12 | 13 | // exports com.example.javafx ; 14 | 15 | // or 16 | 17 | // exports com.example.javafx to javafx.graphics; 18 | 19 | // or 20 | 21 | // opens com.example.javafx to javafx.graphics; -------------------------------------------------------------------------------- /modi/.gitignore: -------------------------------------------------------------------------------- 1 | # Created by .ignore support plugin (hsz.mobi) 2 | .idea 3 | target/ 4 | *.iml 5 | out/ -------------------------------------------------------------------------------- /modi/README.adoc: -------------------------------------------------------------------------------- 1 | = Modi 2 | :experimental: 3 | :toc: macro 4 | :toc-title: Contents 5 | 6 | This is an example project that shows how to compile and run an unnamed module. In essence, we migrate the project without transforming the codebase to a module. 7 | 8 | .the final project tree view after completing the sections. 9 | ---- 10 | . 11 | ├── README.adoc 12 | ├── arguments.args 13 | ├── jar <1> 14 | │   └── modi.jar 15 | ├── out <2> 16 | │   └── production 17 | │   └── modi 18 | │   └── com 19 | │   └── example 20 | │   └── modi 21 | │   ├── entrypoint 22 | │   │   └── Main.class 23 | │   └── query 24 | │   └── Modi.class 25 | └── src 26 | └── com 27 | └── example 28 | └── modi 29 | ├── entrypoint 30 | │   └── Main.java 31 | └── query 32 | └── Modi.java 33 | ---- 34 | 1. If you follow the steps over the section 4, you should see the related JAR file on the `jar` directory. 35 | 2. After compiling the project using the commands below, the `out` directory will be created along with the compiled sources. 36 | 37 | toc::[] 38 | 39 | === #{counter:index} Compiling the project 40 | 41 | The Java compiler tool first compiles the `Main` and the `Modi` files and places their compiled classes into the `out/production/modi` directory. 42 | 43 | .arguments.args 44 | ---- 45 | src/com/example/modi/entrypoint/Main.java 46 | src/com/example/modi/query/Modi.java 47 | -d out/production/modi 48 | ---- 49 | 50 | ---- 51 | $ javac --version 52 | javac 9.0.1 53 | $ javac @arguments.args 54 | ---- 55 | 56 | === #{counter:index} Running the project 57 | 58 | This is the regular exploded directory containing the application class files. The exploded directory can refer to the unnamed module. You can run the project using two approaches: 59 | 60 | ---- 61 | $ java --version 62 | java 9.0.1 63 | Java(TM) SE Runtime Environment (build 9.0.1+11) 64 | Java HotSpot(TM) 64-Bit Server VM (build 9.0.1+11, mixed mode) 65 | 66 | $ cd out/production/modi 67 | $ java com.example.modi.entrypoint.Main java.base 68 | :) 69 | 70 | $ #or 71 | 72 | $ java -cp out/production/modi com.example.modi.entrypoint.Main java.base <1> 73 | :) 74 | ---- 75 | 1. `-cp` is the abbreviation for `--classpath` and `--class-path`. 76 | 77 | === #{counter:index} Generating a JAR file from the exploded unnamed module 78 | 79 | We can generate a corresponding JAR file with the following command: 80 | 81 | ---- 82 | $ jar -c \ <1> 83 | -f jar/modi.jar \ <2> 84 | -e com.example.modi.entrypoint.Main \ <3> 85 | -C out/production/modi/ . <4> 86 | ---- 87 | 1. A new archive file we want to create. Either `--create` or `-c` operation flags can be used. 88 | 2. The new archive file name will be `modi.jar` the `--file` flag is the equivalent of the `-f` flag. 89 | 3. We specify the entry point (main-class). we can do so the thing with the `--main-class com.example.modi.entrypoint.Main` flag as well. 90 | 4. We now change the directory to `out/production/modi` and look at the all compiled class files in order to put them in a JAR file. You should see the `modi.jar` file in the `jar` directory after running the command. Note that, the `jar` directory must exists. 91 | 92 | The directory tree below shows the generated JAR structure. It's similar to the explode unnamed module. 93 | 94 | ---- 95 | modi.jar 96 | ├── META-INF 97 | │   └── MANIFEST.MF 98 | └── com 99 | └── example 100 | └── modi 101 | ├── entrypoint 102 | │   └── Main.class 103 | └── query 104 | └── Modi.class 105 | ---- 106 | 107 | === #{counter:index} The module descriptor 108 | 109 | We are able to inspect the module description of our JAR file. We can find the answers of how many platform modules are required, how many packages the JAR contains and what the main class is. 110 | 111 | ---- 112 | $ jar -d \ <1> 113 | -f jar/modi.jar 114 | No module descriptor found. Derived automatic module. <2> 115 | 116 | modi automatic 117 | requires java.base mandated <3> 118 | contains com.example.modi.entrypoint <4> 119 | contains com.example.modi.query 120 | main-class com.example.modi.entrypoint.Main <5> 121 | ---- 122 | 1. `-d` is the sort-form of the `--describe-module` flag. This is the main operation flag so we cannot use both `-c` and `-d` at the same time. 123 | 2. Since the project is not modularized yet as It doesn't contain a module description file, the name `modi` is automatically given to the module. The module system gives the module descriptor on the fly. fileThe module name, `modi`, is derived from the name of the JAR. 124 | 3. The modi module needs `java.base` to run without error. 125 | 4. 2 packages in the modi project exist. In other words, the modi project consists of `com.example.modi.entrypoint` and `com.example.modi.query` packages. 126 | 5. The main class, `com.example.modi.entrypoint.Main`, runs when we use the module. 127 | 128 | === #{counter:index} Running the nonmodular JAR 129 | 130 | You can run the regular (nonmodular and unamed) JAR file in three ways: 131 | 132 | ---- 133 | $ java -cp jar/modi.jar com.example.modi.entrypoint.Main java.base <1> 134 | :) 135 | # or 136 | $ java -jar jar/modi.jar java.base <2> 137 | :) 138 | ---- 139 | 1. We run the application on the classpath. 140 | 2. This is the conventional way of running a regular JAR file. 141 | 142 | Keep in mind that, we cannot run a modular JAR with the same ways like above. Let's try another approach now. This time, we move the JAR file from the classpath to the module path even the modi JAR is not a module however in fact, the JAR is going to be an automatic module. 143 | 144 | ---- 145 | $ java -p jar/modi.jar -m modi java.base <1> 146 | :) 147 | ---- 148 | 1. We use `-p` or `--module-path` to add our unnamed module , `modi.jar` to the module path. `--module-path` (or short-form `-p`) is the new flag to work with modules (including unnamed modules as well). `-m` is the the short-form of `--module`. We should add the main class using the pattern `module-name/main-class` with the flag but since we already defined the main class in the `MANIFEST` file, we don't have to do that. Just giving the unnamed module name i.e `modi` (see the previous section for how the `modi` name is given), It resolves the main class from the given `modi.jar` with the `-p` flag. We always need to use the `-m` flag at the end of the command along with its arguments if exists, e.g. `-m jar.name args...`. 149 | -------------------------------------------------------------------------------- /modi/arguments.args: -------------------------------------------------------------------------------- 1 | src/com/example/modi/entrypoint/Main.java 2 | src/com/example/modi/query/Modi.java 3 | -d out/production/modi -------------------------------------------------------------------------------- /modi/jar/modi.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ozlerhakan/java9-module-examples/2f8bf4d44fbb6ea5c6599cf5ad7899d6956c5451/modi/jar/modi.jar -------------------------------------------------------------------------------- /modi/src/com/example/modi/entrypoint/Main.java: -------------------------------------------------------------------------------- 1 | package com.example.modi.entrypoint; 2 | 3 | import com.example.modi.query.Modi; 4 | 5 | public class Main { 6 | 7 | public static void main(String[] args) { 8 | 9 | if (args.length == 0) { 10 | System.out.println("specify a searchable name (a module or a package name)"); 11 | System.exit(0); 12 | } 13 | 14 | final String keyword = args[0]; 15 | Modi modi = new Modi(); 16 | boolean exists = modi.exists(keyword); 17 | 18 | System.out.println(exists ? ":)" : ":("); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /modi/src/com/example/modi/query/Modi.java: -------------------------------------------------------------------------------- 1 | package com.example.modi.query; 2 | 3 | import java.util.Objects; 4 | 5 | /** 6 | * Created by hakan on 12/11/2017 7 | */ 8 | public final class Modi { 9 | 10 | /** 11 | * find whether the target keyword exists in the module system or not 12 | * @param keyword searchable module or package name 13 | * @return true if the keyword exists, otherwise false 14 | */ 15 | public boolean exists(String keyword) { 16 | 17 | Module module = String.class.getModule(); 18 | final ModuleLayer moduleLayer = module.getLayer(); 19 | 20 | // since all the modules must be unique , we can use parallelStream and find the target module if exists. 21 | return moduleLayer.modules() 22 | .parallelStream() 23 | .anyMatch(layer -> 24 | keyword.equalsIgnoreCase(layer.getName()) || 25 | layer.getPackages().stream().anyMatch(pck -> pck.equalsIgnoreCase(keyword))); 26 | 27 | 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /modim/.gitignore: -------------------------------------------------------------------------------- 1 | # Created by .ignore support plugin (hsz.mobi) 2 | .idea 3 | target/ 4 | *.iml 5 | out/ -------------------------------------------------------------------------------- /modim/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM alpine:3.6 2 | 3 | COPY modimage /opt/modi 4 | 5 | # Set up env variables 6 | ENV JAVA_HOME=/opt/modi 7 | ENV PATH=$PATH:$JAVA_HOME/bin 8 | 9 | ENTRYPOINT [ "modilula" ] 10 | -------------------------------------------------------------------------------- /modim/Dockerfile.jdk9-alpine: -------------------------------------------------------------------------------- 1 | # A JDK 9 with Alpine Linux 2 | FROM alpine:3.6 3 | # Add the musl-based JDK 9 distribution 4 | RUN mkdir /opt 5 | 6 | ADD jdk-9-ea+181_linux-x64-musl_bin.tar.gz /opt 7 | # Set up env variables 8 | ENV JAVA_HOME=/opt/jdk-9 9 | ENV PATH=$PATH:$JAVA_HOME/bin 10 | 11 | CMD ["jshell", "-J-XX:+UnlockExperimentalVMOptions", \ 12 | "-J-XX:+UseCGroupMemoryLimitForHeap", \ 13 | "-R-XX:+UnlockExperimentalVMOptions", \ 14 | "-R-XX:+UseCGroupMemoryLimitForHeap"] 15 | 16 | -------------------------------------------------------------------------------- /modim/README.adoc: -------------------------------------------------------------------------------- 1 | = ModiM(odule) 2 | :experimental: 3 | :toc: macro 4 | :toc-title: Sections 5 | 6 | This is an example project that shows how to compile and run a named module (i.e. an application module). We migrate the project by transforming the codebase to a module. 7 | 8 | .the final tree view of the project after completing the sections. 9 | ---- 10 | . 11 | ├── README.adoc 12 | ├── dot <1> 13 | │   ├── com.example.modi.dot 14 | │   └── summary.dot 15 | ├── jar <2> 16 | │   └── modi.jar 17 | ├── modimage <3> 18 | │ 19 | ├── out <4> 20 | │   └── production 21 | │   └── modi 22 | │   ├── com 23 | │   │   └── example 24 | │   │   └── modi 25 | │   │   ├── entrypoint 26 | │   │   │   └── Main.class 27 | │   │   └── query 28 | │   │   └── Modi.class 29 | │   └── module-info.class 30 | └── src 31 | ├── com 32 | │   └── example 33 | │   └── modi 34 | │   ├── entrypoint 35 | │   │   └── Main.java 36 | │   └── query 37 | │   └── Modi.java 38 | └── module-info.java 39 | ---- 40 | 1. When you follow the section 6, you find how to generate a project's dependencies and generate the corresponding dot files. 41 | 2. The `modi.jar` file is generated in the `jar` directory explained in section 4. 42 | 3. Section 9 will help you to generate a compatible Java runtime for Alpine Linux. In this case, we use the `modimage` directory to have a Java image. 43 | 4. After compiling the project using the commands below, the `out` directory will be created and compromises of the compiled code. 44 | 45 | toc::[] 46 | 47 | === #{counter:index} Compiling the project 48 | 49 | First we start compiling the modi module using the Java Compiler. The compiled modi module will be located in the `out/production/modi` directory. 50 | 51 | ---- 52 | $ javac -d out/production/modi \ 53 | src/com/example/modi/entrypoint/Main.java \ 54 | src/com/example/modi/query/Modi.java \ 55 | src/module-info.java 56 | 57 | $ # or you can use: 58 | 59 | $ javac -d out/production/modi --source-path src $(find src -name '*.java') 60 | ---- 61 | 62 | === #{counter:index} Running the project 63 | 64 | This time, we are in the running stage of the project. We start running the project on the module path because the project has a module description. 65 | 66 | ---- 67 | $ java -p out/production/modi \ <1> 68 | -m com.example.modi/com.example.modi.entrypoint.Main java.base <2> 69 | :) 70 | ---- 71 | 1. we can use either `-p` or `--module-path` to specify the directory of the compiled module path. 72 | 2. we can use either `-m module.name/path.to.main.class args..` or `--module module.name/path.to.main.class args..` to specify the main class in the module. It must be provided at the end on the command. 73 | 74 | === #{counter:index} Running the project with the module resolution output 75 | 76 | Here is the command that helps to run the modular application: 77 | 78 | ---- 79 | $ java \ 80 | -p out/production/modi/ \ 81 | --show-module-resolution \ <1> 82 | -m com.example.modi/com.example.modi.entrypoint.Main java.base 83 | root com.example.modi file:///Users/hakan/projects/java9/jigsaw/modim/out/production/modi/ 84 | java.base binds jdk.charsets jrt:/jdk.charsets 85 | java.base binds jdk.jartool jrt:/jdk.jartool 86 | java.base binds jdk.javadoc jrt:/jdk.javadoc 87 | java.base binds jdk.jlink jrt:/jdk.jlink 88 | java.base binds jdk.jdeps jrt:/jdk.jdeps 89 | java.base binds jdk.compiler jrt:/jdk.compiler 90 | java.base binds java.desktop jrt:/java.desktop 91 | java.base binds jdk.localedata jrt:/jdk.localedata 92 | java.base binds jdk.zipfs jrt:/jdk.zipfs 93 | java.base binds java.logging jrt:/java.logging 94 | java.base binds jdk.security.auth jrt:/jdk.security.auth 95 | java.base binds java.management jrt:/java.management 96 | java.base binds java.security.sasl jrt:/java.security.sasl 97 | java.base binds jdk.deploy jrt:/jdk.deploy 98 | java.base binds java.naming jrt:/java.naming 99 | java.base binds java.smartcardio jrt:/java.smartcardio 100 | java.base binds jdk.security.jgss jrt:/jdk.security.jgss 101 | java.base binds jdk.crypto.ec jrt:/jdk.crypto.ec 102 | java.base binds java.security.jgss jrt:/java.security.jgss 103 | java.base binds java.xml.crypto jrt:/java.xml.crypto 104 | java.base binds jdk.crypto.cryptoki jrt:/jdk.crypto.cryptoki 105 | jdk.crypto.cryptoki requires jdk.crypto.ec jrt:/jdk.crypto.ec 106 | java.xml.crypto requires java.logging jrt:/java.logging 107 | java.xml.crypto requires java.xml jrt:/java.xml 108 | java.security.jgss requires java.naming jrt:/java.naming 109 | jdk.security.jgss requires java.security.sasl jrt:/java.security.sasl 110 | jdk.security.jgss requires java.logging jrt:/java.logging 111 | jdk.security.jgss requires java.security.jgss jrt:/java.security.jgss 112 | java.naming requires java.security.sasl jrt:/java.security.sasl 113 | jdk.deploy requires java.desktop jrt:/java.desktop 114 | jdk.deploy requires java.naming jrt:/java.naming 115 | jdk.deploy requires java.management jrt:/java.management 116 | jdk.deploy requires java.rmi jrt:/java.rmi 117 | jdk.deploy requires jdk.unsupported jrt:/jdk.unsupported 118 | jdk.deploy requires java.scripting jrt:/java.scripting 119 | jdk.deploy requires java.prefs jrt:/java.prefs 120 | jdk.deploy requires java.xml jrt:/java.xml 121 | jdk.deploy requires java.logging jrt:/java.logging 122 | java.security.sasl requires java.logging jrt:/java.logging 123 | jdk.security.auth requires java.naming jrt:/java.naming 124 | jdk.security.auth requires java.security.jgss jrt:/java.security.jgss 125 | java.desktop requires java.xml jrt:/java.xml 126 | java.desktop requires java.datatransfer jrt:/java.datatransfer 127 | java.desktop requires java.prefs jrt:/java.prefs 128 | jdk.compiler requires java.compiler jrt:/java.compiler 129 | jdk.jdeps requires java.compiler jrt:/java.compiler 130 | jdk.jdeps requires jdk.compiler jrt:/jdk.compiler 131 | jdk.jlink requires jdk.jdeps jrt:/jdk.jdeps 132 | jdk.jlink requires jdk.internal.opt jrt:/jdk.internal.opt 133 | jdk.javadoc requires java.xml jrt:/java.xml 134 | jdk.javadoc requires jdk.compiler jrt:/jdk.compiler 135 | jdk.javadoc requires java.compiler jrt:/java.compiler 136 | java.rmi requires java.logging jrt:/java.logging 137 | java.prefs requires java.xml jrt:/java.xml 138 | java.naming binds jdk.naming.dns jrt:/jdk.naming.dns 139 | java.naming binds jdk.naming.rmi jrt:/jdk.naming.rmi 140 | java.scripting binds jdk.scripting.nashorn jrt:/jdk.scripting.nashorn 141 | java.compiler binds jdk.compiler jrt:/jdk.compiler 142 | java.compiler binds jdk.javadoc jrt:/jdk.javadoc 143 | java.datatransfer binds java.desktop jrt:/java.desktop 144 | java.management binds jdk.management jrt:/jdk.management 145 | java.management binds jdk.management.jfr jrt:/jdk.management.jfr 146 | java.management binds jdk.management.cmm jrt:/jdk.management.cmm 147 | java.management binds java.management.rmi jrt:/java.management.rmi 148 | java.management.rmi requires java.rmi jrt:/java.rmi 149 | java.management.rmi requires java.management jrt:/java.management 150 | java.management.rmi requires java.naming jrt:/java.naming 151 | jdk.management.cmm requires jdk.management jrt:/jdk.management 152 | jdk.management.cmm requires java.management jrt:/java.management 153 | jdk.management.jfr requires jdk.jfr jrt:/jdk.jfr 154 | jdk.management.jfr requires jdk.management jrt:/jdk.management 155 | jdk.management.jfr requires java.management jrt:/java.management 156 | jdk.management requires java.management jrt:/java.management 157 | jdk.scripting.nashorn requires java.logging jrt:/java.logging 158 | jdk.scripting.nashorn requires jdk.dynalink jrt:/jdk.dynalink 159 | jdk.scripting.nashorn requires java.scripting jrt:/java.scripting 160 | jdk.naming.rmi requires java.rmi jrt:/java.rmi 161 | jdk.naming.rmi requires java.naming jrt:/java.naming 162 | jdk.naming.dns requires java.naming jrt:/java.naming 163 | jdk.dynalink requires java.logging jrt:/java.logging 164 | jdk.dynalink binds jdk.scripting.nashorn jrt:/jdk.scripting.nashorn 165 | :) 166 | ---- 167 | 1. Optional, we can inspect the module resolution of the running process during the boot layer initialization. 168 | 169 | We can even limit modules being used by the project. Let's see what will happen if we prevent the java modules except `java.base` from being used. 170 | 171 | ---- 172 | java -p out/production/modi \ 173 | --show-module-resolution \ 174 | --limit-modules java.base \ 175 | -m com.example.modi/com.example.modi.entrypoint.Main java.base 176 | root com.example.modi file:///Users/hakan/IdeaProjects/java9/jigsaw/modim/out/production/modi/ 177 | :) <1> 178 | ---- 179 | 1. It works well! 180 | 181 | === #{counter:index} Generating a JAR file from the exploded named module 182 | 183 | We can generate a corresponding JAR file with the following command: 184 | 185 | ---- 186 | $ jar -c \ <1> 187 | -f jar/modi.jar \ <2> 188 | --module-version 1.0.0 <3> 189 | -e com.example.modi.entrypoint.Main \ <4> 190 | -C out/production/modi/ . <5> 191 | ---- 192 | 1. A new archive file we want to create. Either `--create` or `-c` operation flags can be used. 193 | 2. The new archive file name will be `modi.jar`. The `--file` flag is the equivalent of the `-f` flag. Make sure that the `jar` directory is already created in the current directory. 194 | 3. We can give a version number for each module being created. 195 | 4. We specify the entry point (i.e. a main class). we can do so as well like `--main-class com.example.modi.entrypoint.Main`. 196 | 5. We now change the directory to `out/production/modi` and look at the all compiled class files in order to put them in the jar file. You should see the `modi.jar` in the current directory after running the command. 197 | 198 | The directory tree below shows the generated modular jar structure. It's similar to the explode named module. Keep in mind that in order to extract a jar file, use the following command `jar -xvf jarfile.jar`. 199 | 200 | ---- 201 | jar/modi.jar 202 | ├── META-INF 203 | │   └── MANIFEST.MF 204 | ├── com 205 | │   └── example 206 | │   └── modi 207 | │   ├── entrypoint 208 | │   │   └── Main.class 209 | │   └── query 210 | │   └── Modi.class 211 | └── module-info.class 212 | ---- 213 | 214 | === #{counter:index} The module descriptor 215 | 216 | We are able to inspect the module description of our JAR file. We can find the answers of how many platform modules are required, how many packages the JAR contains and what the main class is. 217 | 218 | ---- 219 | $ jar -d \ <1> 220 | -f jar/modi.jar 221 | com.example.modi@1.0.0 jar:file:///Users/hakan/projects/java9/jigsaw/modim/jar/modi.jar/!module-info.class <2> 222 | requires java.base mandated <3> 223 | contains com.example.modi.entrypoint <4> 224 | contains com.example.modi.query 225 | main-class com.example.modi.entrypoint.Main <5> 226 | ---- 227 | 1. `-d` is the sort-form of the `--describe-module` flag. This is the main operation flag so we cannot use both `-c` and `-d` at the same time. 228 | 2. the module name with the given version number display 229 | 3. the modi module needs `java.base` to run without error. 230 | 4. 2 packages inside the modi project exist. In other words, the modi project consists of `com.example.modi.entrypoint` and `com.example.modi.query` packages. 231 | 5. The main class, `com.example.modi.entrypoint.Main`, runs when we use the module. 232 | 233 | 234 | === #{counter:index} Jdeps Bytecode Analysis 235 | 236 | Another new tool coming with Java 9 is Jdeps. Jdeps is a very useful tool to analyse your project dependencies. It's easy to resolve compile-time dependencies rather than looking each `import` statement at the project level. To do so, Jdeps is interested in compiled code instead source code. It performs bytecode analysis to make this possible. The command below shows a summary of the dependencies of the modi project. 237 | 238 | ---- 239 | $ jdeps -summary -recursive out/production/modi <1> 240 | com.example.modi -> java.base 241 | ---- 242 | 1. Jdeps recursively performs a deep look at the project's bytecode and summarises the result. Since we don't have a 3rd party dependency except the Java module itself (off course), we only see one related platform module which is the sole `java.base`. 243 | 244 | Additionally, Jdeps can writes the result of the summary into `dot` files: 245 | 246 | ---- 247 | $ jdeps --module-path out/production/modi \ 248 | -recursive -dotoutput dot/ \ <1> 249 | -m com.example.modi <2> 250 | ---- 251 | 1. Add dot files into the `dot` directory. 252 | 2. `com.example.modi` is the root module for recursive analysis 253 | 254 | If you want the comprehensive details of the result, you can leave the `-summart` flag out: 255 | 256 | ---- 257 | $ jdeps -recursive out/production/modi 258 | com.example.modi 259 | [file:///Users/hakan/projects/jigsaw/modim/out/production/modi/] 260 | requires mandated java.base (@9) 261 | com.example.modi -> java.base 262 | com.example.modi.entrypoint -> com.example.modi.query com.example.modi 263 | com.example.modi.entrypoint -> java.io java.base 264 | com.example.modi.entrypoint -> java.lang java.base 265 | com.example.modi.query -> java.lang java.base 266 | com.example.modi.query -> java.lang.invoke java.base 267 | com.example.modi.query -> java.util java.base 268 | com.example.modi.query -> java.util.function java.base 269 | com.example.modi.query -> java.util.stream java.base 270 | ---- 271 | 272 | === #{counter:index} Running the modular JAR 273 | 274 | You can run the modular jar file on the module path with giving the main class as below: 275 | 276 | ---- 277 | $ java --module-path jar --module com.example.modi java.base <1> 278 | :) 279 | ---- 280 | 1. `java -p jar -m com.example.modi java.base` is the same of the command. 281 | 282 | 283 | === #{counter:index} Custom Java runtime image 284 | 285 | As of Java 9, The new tool JLink is enable us to make our own custom runtime images to launching our modular applications. In other words, making a project modular, we are able to create a self-contained zero-dependency runtime image of the module using the `jlink` command. Here is the sample command to make an runtime image. Let's take a look at deeply. By the way my current platform is macOS. To make an shareable image using docker we need to use different JDK 9 distribution (See Section 9). 286 | 287 | ---- 288 | $ jlink \ 289 | --launcher modilula=com.example.modi/com.example.modi.entrypoint.Main \ <1> 290 | --output modimage/ \ <2> 291 | --verbose \ <3> 292 | --no-header-files --no-man-pages --compress 2 \ <4> reducing the image size! 293 | --module-path jar:$JAVA_HOME/jmods \ <5> 294 | --add-modules com.example.modi <6> 295 | com.example.modi file:///Users/hakan/projects/java9/jigsaw/modim/jar/modi.jar 296 | java.base file:///Library/Java/JavaVirtualMachines/jdk-9.jdk/Contents/Home/jmods/java.base.jmod 297 | 298 | Providers: 299 | java.base provides java.nio.file.spi.FileSystemProvider used by java.base 300 | ---- 301 | 1. `jlink` gives an executable for a project to run it directly. In this case, the executor name (or luncher name) is `madidula`. We refer to the main class of the `modi` module. 302 | 2. The path where the modi image exists. 303 | 3. It gives more details about the process. 304 | 4. the 3 flags are used to minimize the size of the runtime image. A zip level compression is applied without man pages and header files. 305 | 5. `$JAVA_HOME` should refet to the JDK directory. `$JAVA_HOME/jmods` points to the platform and boot modules of JDK 9. Each module's extension ends with `.jmod`. This is a new file format for packaging the modules. You can think of a `jmod` file as a regular `jar` file but they contain module specific details as well. the module path consists of JDK modules and the application module. 306 | 6. We only specify the `modi` module whose module name is `com.example.modi` for the image. The image will contain the dependencies of the modi itself. 307 | 308 | The generated image file structure is as follows: 309 | 310 | ---- 311 | modimage/ <1> 312 | ├── bin/ 313 | │   ├── java 314 | │   ├── keytool 315 | │   └── modilula <1> 316 | ├── conf/ 317 | │   318 | ├── legal/ 319 | │  320 | ├── lib/ 321 | │   322 | └── release 323 | ---- 324 | 1. The Modim Java runtime is only 24mb! 325 | 2. This command is our executable script launching the `com.example.modi` module. 326 | 327 | It should run successfully, If we run the `modidula` command. 328 | 329 | ---- 330 | $ ./modimage/bin/modilula java.base 331 | :) <1> 332 | ---- 333 | 1. yay! 334 | 335 | In the bundled Java runtime image, a Java command is also available. Let's list the modules the `modi` module requires to run the application. 336 | 337 | ---- 338 | $ ./modimage/bin/java --list-modules <1> 339 | com.example.modi@1.0.0 340 | java.base@9 341 | ---- 342 | 1. Only the `modi` module itself and the `java.base` module are applicable in the Java runtime. 343 | 344 | === #{counter:index} Dockerize the application module 345 | 346 | To make this application Dockerize, we need to use JDK 9 Early-Access Builds for Alpine Linux. We need an image first to have a working JDK based on an Alpine image. 347 | 348 | We use `Dockerfile.jdk9-alpine` taken from the https://github.com/arun-gupta/labs/blob/f6ebeb7101832e16b6802af6d47606bb73463d75/developer-tools/java/chapters/ch03-build-image-java-9.adoc#create-a-docker-image-using-jdk-9[docker-lab] repository to have JDK 9 running on Alpine 3.6. What is important to know that, before building the dockerfile, we must download the `tar` file from http://jdk.java.net/9/ea. The same steps are applied for obtaining the image through the docker run command. 349 | 350 | ---- 351 | $ # Build the Dockerfile.jdk9-alpine 352 | $ docker build -t alpine:jdk-9-musl -f Dockerfile.jdk9-alpine . 353 | 354 | $ docker images 355 | REPOSITORY TAG IMAGE ID CREATED SIZE 356 | alpine jdk-9-musl cd4ea7628c9b 15 minutes ago 356MB 357 | $ 358 | $ docker run -v $(pwd):/modi/ -w /modi --rm alpine:jdk-9-musl jlink \ 359 | --launcher modilula=com.example.modi/com.example.modi.entrypoint.Main \ 360 | --output modimage/ \ 361 | --verbose \ 362 | --no-header-files --no-man-pages --compress 2 \ 363 | --module-path jar:/opt/jdk-9/jmods \ 364 | --add-modules com.example.modi 365 | ---- 366 | 367 | Now that we have an runtime image, we can make a Docker image based on Alpine 3.6. 368 | 369 | ---- 370 | $ docker build -t modilula --no-cache . <1> 371 | $ docker images 372 | REPOSITORY TAG IMAGE ID CREATED SIZE 373 | modilula latest 289ab9865287 3 seconds ago 37.3MB <2> 374 | 375 | $ docker run --rm modilula:latest java.base 376 | :) <3> 377 | ---- 378 | 1. Docker looks at the `Dockerfile` and build the modidula image. 379 | 2. Including the alpine base image our application is just 37mb! 380 | 3. Voila! 381 | 382 | Now, you are free to ship the image to a registry and make everyone use the application like so: 383 | 384 | ---- 385 | $ docker tag modilula:latest ozlerhakan/modilula:latest 386 | $ docker push ozlerhakan/modilula:latest 387 | $ docker run --rm ozlerhakan/modilula:latest java.base 388 | :) 389 | ---- 390 | -------------------------------------------------------------------------------- /modim/dot/com.example.modi.dot: -------------------------------------------------------------------------------- 1 | digraph "com.example.modi" { 2 | // Path: /Users/hakan/IdeaProjects/jigsaw/modim/out/production/modi 3 | "com.example.modi.entrypoint" -> "com.example.modi.query (com.example.modi)"; 4 | "com.example.modi.entrypoint" -> "java.io (java.base)"; 5 | "com.example.modi.entrypoint" -> "java.lang (java.base)"; 6 | "com.example.modi.query" -> "java.lang (java.base)"; 7 | "com.example.modi.query" -> "java.lang.invoke (java.base)"; 8 | "com.example.modi.query" -> "java.util (java.base)"; 9 | "com.example.modi.query" -> "java.util.function (java.base)"; 10 | "com.example.modi.query" -> "java.util.stream (java.base)"; 11 | } 12 | -------------------------------------------------------------------------------- /modim/dot/summary.dot: -------------------------------------------------------------------------------- 1 | digraph "summary" { 2 | "com.example.modi" -> "java.base (java.base)"; 3 | } 4 | -------------------------------------------------------------------------------- /modim/jar/modi.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ozlerhakan/java9-module-examples/2f8bf4d44fbb6ea5c6599cf5ad7899d6956c5451/modim/jar/modi.jar -------------------------------------------------------------------------------- /modim/src/com/example/modi/entrypoint/Main.java: -------------------------------------------------------------------------------- 1 | package com.example.modi.entrypoint; 2 | 3 | import com.example.modi.query.Modi; 4 | 5 | public class Main { 6 | 7 | public static void main(String[] args) { 8 | 9 | if (args.length == 0) { 10 | System.out.println("specify a searchable name (a module or a package name)"); 11 | System.exit(0); 12 | } 13 | 14 | final String keyword = args[0]; 15 | Modi modi = new Modi(); 16 | boolean exists = modi.exists(keyword); 17 | 18 | System.out.println(exists ? ":)" : ":("); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /modim/src/com/example/modi/query/Modi.java: -------------------------------------------------------------------------------- 1 | package com.example.modi.query; 2 | 3 | import java.util.Objects; 4 | 5 | /** 6 | * Created by hakan on 12/11/2017 7 | */ 8 | public final class Modi { 9 | 10 | /** 11 | * find whether the target keyword exists in the module system or not 12 | * @param keyword searchable module or package name 13 | * @return true if the keyword exists, otherwise false 14 | */ 15 | public boolean exists(String keyword) { 16 | 17 | // this module is an application module 18 | final Module module = Modi.class.getModule(); 19 | final ModuleLayer moduleLayer = module.getLayer(); 20 | 21 | return moduleLayer.modules() 22 | .parallelStream() 23 | .anyMatch(layer -> 24 | keyword.equalsIgnoreCase(layer.getName()) || 25 | layer.getPackages().stream().anyMatch(pck -> pck.equalsIgnoreCase(keyword))); 26 | 27 | 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /modim/src/module-info.java: -------------------------------------------------------------------------------- 1 | module com.example.modi { 2 | } -------------------------------------------------------------------------------- /modims-exports-requires/.gitignore: -------------------------------------------------------------------------------- 1 | # Created by .ignore support plugin (hsz.mobi) 2 | .idea 3 | target/ 4 | *.iml 5 | out/ 6 | modimage -------------------------------------------------------------------------------- /modims-exports-requires/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM alpine:3.6 2 | 3 | # copy the Java runtime image to the /opt/modi directory of the container 4 | COPY modimage /opt/modi 5 | 6 | # Set up env variables 7 | ENV JAVA_HOME=/opt/modi 8 | ENV PATH=$PATH:$JAVA_HOME/bin 9 | 10 | ENTRYPOINT [ "modims" ] 11 | -------------------------------------------------------------------------------- /modims-exports-requires/README.adoc: -------------------------------------------------------------------------------- 1 | = ModiM(odule)s 2 | :experimental: 3 | :toc: macro 4 | :toc-title: Contents 5 | 6 | This is a project that shows how to compile and run multiple named modules (i.e. application modules). We refactor the codebase of the `modim` module and then transform it into maintainable multiple named modules. This system works as expected using the `exports` and `requires` clauses. We are able to search modules and packages by name from the `modi.entrypoint` module by means of the `modi.provider` module. In fact, the entrypoint module itself doesn't know the underlying implementations of the `modi.api` module. It only knows the `modi.provider` and the `modi.api`. The `modi.pack` and `modi.mod` implements the module and package search respectively. The 2 implementations of the `modi.api` are hidden from the `modi.entrypoint` module. Another way of doing the same approach is to use Service Loader. The `modims-provides-uses` project illustrates how to do that using Service Loader. 7 | 8 | You can examine the dot files of the modules by looking at the `dot` directory or try the following command out after cloning and compiling the modules yourselves: 9 | 10 | ---- 11 | $ jdeps -recursive --module-path out -dotoutput dot -m modi.entrypoint 12 | ---- 13 | 14 | .the complete relation of the system. 15 | image:dot/summary.jpeg[] 16 | 17 | The graph below describes the `exposes` - `requires` relation between modules. Every time a module must *export its package(s)* when another module wants to use the module's API. Likewise another module must *require the module* to access their API. In our case, the provider module needs to access the implementations (i.e. `modi.mod` and `modi.pack` modules) of the `modi.api` module. Thus, it requires the 2 implementation modules and the api module itself. The required modules must exports their necessary types to be reached by the other modules. For example, the `com.pack.impl` package is exported by the `modi.pack` module since the package contains the implementation type. 18 | 19 | image:dot/summary-requires-exposes.png[] 20 | 21 | .the final project tree view. 22 | ---- 23 | ├── README.adoc 24 | ├── dot 25 | ├── modi.api 26 | │   ├── modi.api.iml 27 | │   └── src 28 | │   ├── com 29 | │   │   └── modi 30 | │   │   └── api 31 | │   │   └── query 32 | │   │   └── Query.java 33 | │   └── module-info.java 34 | ├── modi.entrypoint 35 | │   ├── modi.entrypoint.iml 36 | │   └── src 37 | │   ├── com 38 | │   │   └── modi 39 | │   │   └── entrypoint 40 | │   │   └── Main.java 41 | │   └── module-info.java 42 | ├── modi.mod.impl 43 | │   ├── modi.mod.impl.iml 44 | │   └── src 45 | │   ├── com 46 | │   │   └── mod 47 | │   │   └── impl 48 | │   │   └── ModuleQuery.java 49 | │   └── module-info.java 50 | ├── modi.pack.impl 51 | │   ├── modi.pack.impl.iml 52 | │   └── src 53 | │   ├── com 54 | │   │   └── pack 55 | │   │   └── impl 56 | │   │   └── PackageQuery.java 57 | │   └── module-info.java 58 | ├── modi.provider 59 | │   ├── modi.provider.iml 60 | │   └── src 61 | │   ├── com 62 | │   │   └── modi 63 | │   │   └── provider 64 | │   │   ├── query 65 | │   │   │   └── QueryProvider.java 66 | │   │   └── util 67 | │   │   └── Flags.java 68 | │   └── module-info.java 69 | └── out 70 | ---- 71 | 72 | toc::[] 73 | 74 | === #{counter:index} Compiling the project 75 | 76 | We compile the 5 related modules separately and place their compiled classes into the `out/` directory. 77 | 78 | ---- 79 | $ javac -d out/modi.api/ --source-path modi.api/src/ $(find modi.api/src -name '*.java') <1> 80 | $ javac -d out/modi.mod/ --module-path out/ --source-path modi.mod.impl/src/ $(find modi.mod.impl/src/ -name '*.java') 81 | $ javac -d out/modi.pack/ --module-path out/ --source-path modi.pack.impl/src/ $(find modi.pack.impl/src/ -name '*.java') 82 | $ javac -d out/modi.provider/ --module-path out/ --source-path modi.provider/src/ $(find modi.provider/src/ -name '*.java') 83 | $ javac -d out/modi.entrypoint/ --module-path out/ --source-path modi.entrypoint/src/ $(find modi.entrypoint/src/ -name '*.java') 84 | ---- 85 | 1. The `modi.api` module is compiled first since it is used by other 4 modules. This module should be on their module path. So last 4 `javac` commands must use the `--module-path` flag. `--source-path` is the another form of `-sourcepath`. 86 | 87 | === #{counter:index} Running the project 88 | 89 | We are in the running stage of the project. The project itself consists of 5 modules each of which has a module description. In a way, the module path is all we need to run the complete system. The `out` directory consists of 5 modules. we directly give that directory in the `java` command and specify the `modi.entrypoint` module where it has a `static` `main` method to execute the project itself. 90 | 91 | ---- 92 | $ java -p out/ -m modi.entrypoint/com.modi.entrypoint.Main 93 | Commands: 94 | -m,--module a module name 95 | -p,--package a package name 96 | $ java -p out/ -m modi.entrypoint/com.modi.entrypoint.Main -p java.lang 97 | :) 98 | $ java -p out/ -m modi.entrypoint/com.modi.entrypoint.Main -m java.base 99 | :) 100 | ---- 101 | 102 | === #{counter:index} JAR festival 103 | 104 | Since each module is an independent named application module, you must create 5 different modular jar files. The created modular JARs have a module version which is 1.1.0. The `modi.entrypoint` module solely has a main class. 105 | 106 | ---- 107 | $ mkdir jar 108 | $ jar -c -f jar/modi.api@1.1.0.jar --module-version 1.1.0 -C out/modi.api/ . 109 | $ jar -c -f jar/modi.mod@1.1.0.jar --module-version 1.1.0 -C out/modi.mod/ . 110 | $ jar -c -f jar/modi.pack@1.1.0.jar --module-version 1.1.0 -C out/modi.pack/ . 111 | $ jar -c -f jar/modi.provider@1.1.0.jar --module-version 1.1.0 -C out/modi.provider/ . 112 | $ jar -c -f jar/modi.entrypoint@1.1.0.jar --module-version 1.1.0 -e com.modi.entrypoint.Main -C out/modi.entrypoint/ . 113 | ---- 114 | 115 | Let's inspect the module description of the `modi.entrypoint` JAR file. We can infer the following meanings from the result of the module description; the module itself needs to react the `modi.api` and the `modi.provider` modules. The description clarifies these necessary requirements using the `requires` clause. Next to this, the module contains one package which is `com.modi.entrypoint` and it has a main class called `Main` in the `com.modi.entrypoint` package. 116 | 117 | ---- 118 | $ jar -d -f jar/modi.entrypoint@1.1.0.jar 119 | modi.entrypoint@1.1.0 jar:file:///Users/hakan/projects/jigsaw/modims/jar/modi.entrypoint@1.1.0.jar/!module-info.class 120 | requires java.base mandated 121 | requires modi.api 122 | requires modi.provider 123 | contains com.modi.entrypoint 124 | main-class com.modi.entrypoint.Main 125 | ---- 126 | 127 | The `jar` directory now stores the application modules: 128 | 129 | ---- 130 | . 131 | └── jar 132 |    ├── modi.api@1.1.0.jar 133 |    ├── modi.entrypoint@1.1.0.jar 134 |    ├── modi.mod@1.1.0.jar 135 |    ├── modi.pack@1.1.0.jar 136 |    └── modi.provider@1.1.0.jar 137 | ---- 138 | 139 | Last, we run the application by putting the JAR files on the module path. Since the `modi.entrypoint` module has an module description, we don’t need to add the qualified module name and its class name with the `-m` flag. The Java 9 command will look at the module description and take the main class. 140 | 141 | ---- 142 | $ java -p jar/ -m modi.entrypoint -m java.base 143 | :) 144 | $ java -p jar/ -m modi.entrypoint -m java.xml.bind 145 | :( <1> 146 | ---- 147 | 1. The `java.xml.bind` module even it’s part of the Java module system, we only depend on the `java.base` module. As a result, searching an unrelated module does give :(. 148 | 149 | === #{counter:index} Dockerize the application module 150 | 151 | This time, we make our own Docker image containing the essentials modules as we did in the modim project with ease. But first, we need to make sure that you have a docker image serving JDK 9 on top of Alpine 3.6 like the `alpine:jdk-9-musl` image we use in our projects. Also see the modim project in this repository, if you want to get more particular information about creating a bare minimum Java runtime image for the Alpine Linux distribution. 152 | 153 | ---- 154 | $ docker run -v $(pwd):/modi/ -w /modi --rm alpine:jdk-9-musl jlink \ 155 | --launcher modims=modi.entrypoint/com.modi.entrypoint.Main \ 156 | --output modimage/ \ 157 | --verbose \ 158 | --no-header-files --no-man-pages --compress 2 \ 159 | --module-path jar:/opt/jdk-9/jmods \ 160 | --add-modules modi.entrypoint <1> 161 | java.base file:///opt/jdk-9/jmods/java.base.jmod 162 | modi.api file:///modi/jar/modi.api@1.1.0.jar 163 | modi.entrypoint file:///modi/jar/modi.entrypoint@1.1.0.jar 164 | modi.mod file:///modi/jar/modi.mod@1.1.0.jar 165 | modi.pack file:///modi/jar/modi.pack@1.1.0.jar 166 | modi.provider file:///modi/jar/modi.provider@1.1.0.jar 167 | 168 | Providers: 169 | java.base provides java.nio.file.spi.FileSystemProvider used by java.base 170 | ---- 171 | 1. `modi.entrypoint` is the root module, Jlink starts proceeding the runtime image. 172 | 173 | Now that we have an runtime image, we are able to make a Docker image based on Alpine 3.6. 174 | 175 | ---- 176 | $ docker build -t modims --no-cache . 177 | $ docker images 178 | REPOSITORY TAG IMAGE ID CREATED SIZE 179 | modims latest 289ab9865287 3 seconds ago 37.4MB 180 | 181 | $ docker run --rm modims:latest -m modi.mod 182 | :) 183 | $ docker run --rm modims:latest -p com.modi.provider.util 184 | :) 185 | $ docker run --rm --entrypoint java modims:latest --list-modules <1> 186 | java.base@9-ea 187 | modi.api@1.1.0 188 | modi.entrypoint@1.1.0 189 | modi.mod@1.1.0 190 | modi.pack@1.1.0 191 | modi.provider@1.1.0 192 | ---- 193 | 1. Only 5 modules come bundled with the application itself. -------------------------------------------------------------------------------- /modims-exports-requires/dot/modi.api.dot: -------------------------------------------------------------------------------- 1 | digraph "modi.api" { 2 | // Path: /Users/hakan/IdeaProjects/jigsaw/modims/out/modi.api 3 | "com.modi.api.query" -> "java.lang (java.base)"; 4 | } 5 | -------------------------------------------------------------------------------- /modims-exports-requires/dot/modi.entrypoint.dot: -------------------------------------------------------------------------------- 1 | digraph "modi.entrypoint" { 2 | // Path: /Users/hakan/IdeaProjects/jigsaw/modims/out/modi.entrypoint 3 | "com.modi.entrypoint" -> "com.modi.api.query (modi.api)"; 4 | "com.modi.entrypoint" -> "com.modi.provider.query (modi.provider)"; 5 | "com.modi.entrypoint" -> "java.io (java.base)"; 6 | "com.modi.entrypoint" -> "java.lang (java.base)"; 7 | "com.modi.entrypoint" -> "java.lang.invoke (java.base)"; 8 | "com.modi.entrypoint" -> "java.util (java.base)"; 9 | "com.modi.entrypoint" -> "java.util.function (java.base)"; 10 | } 11 | -------------------------------------------------------------------------------- /modims-exports-requires/dot/modi.mod.dot: -------------------------------------------------------------------------------- 1 | digraph "modi.mod" { 2 | // Path: /Users/hakan/IdeaProjects/jigsaw/modims/out/modi.mod 3 | "com.mod.impl" -> "com.modi.api.query (modi.api)"; 4 | "com.mod.impl" -> "java.lang (java.base)"; 5 | "com.mod.impl" -> "java.lang.invoke (java.base)"; 6 | "com.mod.impl" -> "java.util (java.base)"; 7 | "com.mod.impl" -> "java.util.function (java.base)"; 8 | "com.mod.impl" -> "java.util.stream (java.base)"; 9 | } 10 | -------------------------------------------------------------------------------- /modims-exports-requires/dot/modi.pack.dot: -------------------------------------------------------------------------------- 1 | digraph "modi.pack" { 2 | // Path: /Users/hakan/IdeaProjects/jigsaw/modims/out/modi.pack 3 | "com.pack.impl" -> "com.modi.api.query (modi.api)"; 4 | "com.pack.impl" -> "java.lang (java.base)"; 5 | "com.pack.impl" -> "java.lang.invoke (java.base)"; 6 | "com.pack.impl" -> "java.util (java.base)"; 7 | "com.pack.impl" -> "java.util.function (java.base)"; 8 | "com.pack.impl" -> "java.util.stream (java.base)"; 9 | } 10 | -------------------------------------------------------------------------------- /modims-exports-requires/dot/modi.provider.dot: -------------------------------------------------------------------------------- 1 | digraph "modi.provider" { 2 | // Path: /Users/hakan/IdeaProjects/jigsaw/modims/out/modi.provider 3 | "com.modi.provider.query" -> "com.mod.impl (modi.mod)"; 4 | "com.modi.provider.query" -> "com.modi.api.query (modi.api)"; 5 | "com.modi.provider.query" -> "com.modi.provider.util (modi.provider)"; 6 | "com.modi.provider.query" -> "com.pack.impl (modi.pack)"; 7 | "com.modi.provider.query" -> "java.lang (java.base)"; 8 | "com.modi.provider.query" -> "java.util (java.base)"; 9 | "com.modi.provider.util" -> "java.lang (java.base)"; 10 | } 11 | -------------------------------------------------------------------------------- /modims-exports-requires/dot/summary-requires-exposes.dot: -------------------------------------------------------------------------------- 1 | digraph "summary" { 2 | size ="10,10"; 3 | "modi.entrypoint" -> "modi.api\nexports com.modi.api.query" [label=requires,color=blue]; 4 | "modi.entrypoint" -> "modi.provider\nexports com.modi.provider.query" [label=requires,color=blue]; 5 | 6 | "modi.mod\nexports com.mod.impl" -> "modi.api\nexports com.modi.api.query" [label=requires,color=blue]; 7 | "modi.mod\nexports com.mod.impl" -> "modi.provider\nexports com.modi.provider.query" [style=dotted]; 8 | 9 | "modi.provider\nexports com.modi.provider.query" -> "modi.entrypoint" [style=dotted]; 10 | "modi.provider\nexports com.modi.provider.query" -> "modi.api\nexports com.modi.api.query" [label=requires,color=blue]; 11 | "modi.provider\nexports com.modi.provider.query" -> "modi.mod\nexports com.mod.impl" [label=requires,color=blue]; 12 | "modi.provider\nexports com.modi.provider.query" -> "modi.pack\nexports com.pack.impl" [label=requires,color=blue]; 13 | 14 | "modi.pack\nexports com.pack.impl" -> "modi.api\nexports com.modi.api.query" [label=requires,color=blue]; 15 | "modi.pack\nexports com.pack.impl" -> "modi.provider\nexports com.modi.provider.query" [style=dotted]; 16 | 17 | "modi.api\nexports com.modi.api.query" -> "modi.provider\nexports com.modi.provider.query" [style=dotted]; 18 | "modi.api\nexports com.modi.api.query" -> "modi.entrypoint" [style=dotted]; 19 | "modi.api\nexports com.modi.api.query" -> "modi.mod\nexports com.mod.impl" [style=dotted]; 20 | "modi.api\nexports com.modi.api.query" -> "modi.pack\nexports com.pack.impl" [style=dotted]; 21 | } 22 | -------------------------------------------------------------------------------- /modims-exports-requires/dot/summary-requires-exposes.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ozlerhakan/java9-module-examples/2f8bf4d44fbb6ea5c6599cf5ad7899d6956c5451/modims-exports-requires/dot/summary-requires-exposes.png -------------------------------------------------------------------------------- /modims-exports-requires/dot/summary.dot: -------------------------------------------------------------------------------- 1 | digraph "summary" { 2 | "modi.entrypoint" -> "java.base (java.base)"; 3 | "modi.entrypoint" -> "modi.api"; 4 | "modi.entrypoint" -> "modi.provider"; 5 | "modi.mod" -> "java.base (java.base)"; 6 | "modi.mod" -> "modi.api"; 7 | "modi.api" -> "java.base (java.base)"; 8 | "modi.provider" -> "java.base (java.base)"; 9 | "modi.provider" -> "modi.api"; 10 | "modi.provider" -> "modi.mod"; 11 | "modi.provider" -> "modi.pack"; 12 | "modi.pack" -> "java.base (java.base)"; 13 | "modi.pack" -> "modi.api"; 14 | } 15 | -------------------------------------------------------------------------------- /modims-exports-requires/dot/summary.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ozlerhakan/java9-module-examples/2f8bf4d44fbb6ea5c6599cf5ad7899d6956c5451/modims-exports-requires/dot/summary.jpeg -------------------------------------------------------------------------------- /modims-exports-requires/jar/modi.api@1.1.0.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ozlerhakan/java9-module-examples/2f8bf4d44fbb6ea5c6599cf5ad7899d6956c5451/modims-exports-requires/jar/modi.api@1.1.0.jar -------------------------------------------------------------------------------- /modims-exports-requires/jar/modi.entrypoint@1.1.0.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ozlerhakan/java9-module-examples/2f8bf4d44fbb6ea5c6599cf5ad7899d6956c5451/modims-exports-requires/jar/modi.entrypoint@1.1.0.jar -------------------------------------------------------------------------------- /modims-exports-requires/jar/modi.mod@1.1.0.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ozlerhakan/java9-module-examples/2f8bf4d44fbb6ea5c6599cf5ad7899d6956c5451/modims-exports-requires/jar/modi.mod@1.1.0.jar -------------------------------------------------------------------------------- /modims-exports-requires/jar/modi.pack@1.1.0.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ozlerhakan/java9-module-examples/2f8bf4d44fbb6ea5c6599cf5ad7899d6956c5451/modims-exports-requires/jar/modi.pack@1.1.0.jar -------------------------------------------------------------------------------- /modims-exports-requires/jar/modi.provider@1.1.0.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ozlerhakan/java9-module-examples/2f8bf4d44fbb6ea5c6599cf5ad7899d6956c5451/modims-exports-requires/jar/modi.provider@1.1.0.jar -------------------------------------------------------------------------------- /modims-exports-requires/modi.api/src/com/modi/api/query/Query.java: -------------------------------------------------------------------------------- 1 | package com.modi.api.query; 2 | 3 | /** 4 | * Created by hakan on 17/11/2017 5 | */ 6 | public interface Query { 7 | 8 | boolean exists(String keyword); 9 | } 10 | -------------------------------------------------------------------------------- /modims-exports-requires/modi.api/src/module-info.java: -------------------------------------------------------------------------------- 1 | module modi.api { 2 | 3 | exports com.modi.api.query; 4 | } -------------------------------------------------------------------------------- /modims-exports-requires/modi.entrypoint/src/com/modi/entrypoint/Main.java: -------------------------------------------------------------------------------- 1 | package com.modi.entrypoint; 2 | 3 | import com.modi.provider.query.QueryProvider; 4 | 5 | public class Main { 6 | 7 | public static void main(String[] args) { 8 | 9 | if (args.length != 2) { 10 | printUsage(); 11 | System.exit(0); 12 | } 13 | 14 | final String flag = args[0].toLowerCase(); 15 | final String keyword = args[1]; 16 | 17 | QueryProvider.getQuery(flag) 18 | .ifPresentOrElse(query -> { 19 | System.out.println(query); 20 | if (query.exists(keyword)) { 21 | System.out.println(":)"); 22 | } else { 23 | System.out.println(":("); 24 | } 25 | }, 26 | Main::printUsage); 27 | } 28 | 29 | private static void printUsage() { 30 | System.out.println("Commands:\n" + 31 | "-m,--module\ta module name \n" + 32 | "-p,--package\ta package name"); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /modims-exports-requires/modi.entrypoint/src/module-info.java: -------------------------------------------------------------------------------- 1 | module modi.entrypoint { 2 | 3 | // the entrypoint doesn't know the query implementations! 4 | // only it knows the api module and the query provider 5 | requires modi.provider; 6 | requires modi.api; 7 | } -------------------------------------------------------------------------------- /modims-exports-requires/modi.mod.impl/src/com/mod/impl/ModuleQuery.java: -------------------------------------------------------------------------------- 1 | package com.mod.impl; 2 | 3 | import com.modi.api.query.Query; 4 | 5 | /** 6 | * Created by hakan on 12/11/2017 7 | */ 8 | public final class ModuleQuery implements Query { 9 | 10 | //a public non-arg constructor class needed to be a service 11 | 12 | /** 13 | * find whether the target keyword exists in the module system or not 14 | * 15 | * @param keyword a searchable module name 16 | * @return true if the keyword exists, otherwise false 17 | */ 18 | public boolean exists(String keyword) { 19 | 20 | // this module is an application module 21 | final Module module = ModuleQuery.class.getModule(); 22 | final ModuleLayer moduleLayer = module.getLayer(); 23 | 24 | return moduleLayer.modules() 25 | .parallelStream() 26 | .anyMatch(layer -> keyword.equals(layer.getName())); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /modims-exports-requires/modi.mod.impl/src/module-info.java: -------------------------------------------------------------------------------- 1 | module modi.mod { 2 | 3 | // modi.mod exports the com.mod.impl package 4 | // exports 5 | exports com.mod.impl; 6 | 7 | // modi.mod requires the modi.api module 8 | // requires 9 | requires modi.api; 10 | } -------------------------------------------------------------------------------- /modims-exports-requires/modi.pack.impl/src/com/pack/impl/PackageQuery.java: -------------------------------------------------------------------------------- 1 | package com.pack.impl; 2 | 3 | import com.modi.api.query.Query; 4 | 5 | /** 6 | * Created by hakan on 19/11/2017 7 | */ 8 | public final class PackageQuery implements Query { 9 | 10 | //a public non-arg constructor class needed to be a service 11 | 12 | /** 13 | * find whether the target keyword exists in the module system or not 14 | * 15 | * @param keyword a searchable package name 16 | * @return true if the keyword exists, otherwise false 17 | */ 18 | @Override 19 | public boolean exists(String keyword) { 20 | // this module is an application module 21 | final Module module = PackageQuery.class.getModule(); 22 | final ModuleLayer moduleLayer = module.getLayer(); 23 | 24 | return moduleLayer.modules() 25 | .parallelStream() 26 | .anyMatch(layer -> layer.getPackages().stream().anyMatch(pck -> pck.equals(keyword))); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /modims-exports-requires/modi.pack.impl/src/module-info.java: -------------------------------------------------------------------------------- 1 | module modi.pack { 2 | 3 | exports com.pack.impl; // <-- 4 | 5 | requires modi.api; // --> 6 | } -------------------------------------------------------------------------------- /modims-exports-requires/modi.provider/src/com/modi/provider/query/QueryProvider.java: -------------------------------------------------------------------------------- 1 | package com.modi.provider.query; 2 | 3 | import com.modi.api.query.Query; 4 | import com.mod.impl.ModuleQuery; 5 | import com.pack.impl.PackageQuery; 6 | 7 | import java.util.Optional; 8 | 9 | import static com.modi.provider.util.Flags.MODULE_FLAG; 10 | import static com.modi.provider.util.Flags.M_FLAG; 11 | import static com.modi.provider.util.Flags.PACKAGE_FLAG; 12 | import static com.modi.provider.util.Flags.P_FLAG; 13 | 14 | /** 15 | * Created by hakan on 17/11/2017 16 | */ 17 | public class QueryProvider { 18 | 19 | public static Optional getQuery(String queryName) { 20 | 21 | if (queryName.equals(MODULE_FLAG) || queryName.equals(M_FLAG)) { 22 | return Optional.of(new ModuleQuery()); 23 | } else if (queryName.equals(PACKAGE_FLAG) || queryName.equals(P_FLAG)) { 24 | return Optional.of(new PackageQuery()); 25 | } 26 | 27 | return Optional.empty(); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /modims-exports-requires/modi.provider/src/com/modi/provider/util/Flags.java: -------------------------------------------------------------------------------- 1 | package com.modi.provider.util; 2 | 3 | /** 4 | * Created by hakan on 19/11/2017 5 | */ 6 | public final class Flags { 7 | 8 | private Flags() { 9 | } 10 | 11 | public final static String MODULE_FLAG = "--module"; 12 | public final static String M_FLAG = "-m"; 13 | 14 | public final static String PACKAGE_FLAG = "--package"; 15 | public final static String P_FLAG = "-p"; 16 | } 17 | -------------------------------------------------------------------------------- /modims-exports-requires/modi.provider/src/module-info.java: -------------------------------------------------------------------------------- 1 | module modi.provider { 2 | 3 | exports com.modi.provider.query; 4 | 5 | requires modi.mod; 6 | requires modi.api; 7 | requires modi.pack; 8 | } -------------------------------------------------------------------------------- /modims-provides-uses/.gitignore: -------------------------------------------------------------------------------- 1 | # Created by .ignore support plugin (hsz.mobi) 2 | .idea 3 | target/ 4 | *.iml 5 | out/ 6 | modimage -------------------------------------------------------------------------------- /modims-provides-uses/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM alpine:3.6 2 | 3 | # copy the Java runtime image to the /opt/modi directory of the container 4 | COPY modimage /opt/modi 5 | 6 | # Set up env variables 7 | ENV JAVA_HOME=/opt/modi 8 | ENV PATH=$PATH:$JAVA_HOME/bin 9 | 10 | ENTRYPOINT [ "modims" ] 11 | -------------------------------------------------------------------------------- /modims-provides-uses/README.adoc: -------------------------------------------------------------------------------- 1 | = ModiM(odule)s 2 | :experimental: 3 | :toc: macro 4 | :toc-title: Contents 5 | 6 | The project consists of 4 application modules expect a provider module. In fact, it serves the same state of the `modism-exports-requires` project. But now, our aim is to run the same system by means of the `provides ... with` and `uses` clauses. The dot files are located in the `dot` directory. The following `jdeps` commands generates the mentioned dot files of the module system. 7 | 8 | ---- 9 | $ jdeps --module-path out -dotoutput dot/ -recursive -m modi.entrypoint 10 | $ jdeps --module-path out -dotoutput dot/mod/ -recursive -m modi.mod 11 | $ jdeps --module-path out -dotoutput dot/pack/ -recursive -m modi.pack 12 | $ jdeps --module-path out -dotoutput dot/api/ -recursive -m modi.api 13 | ---- 14 | 15 | However, none of the above commands generates the entire module diagram in one picture because `modi.mod`, `modi.pack`, and `modi.entrypoint` don't belong to each other. Keep in mind that these modules implicitly depends on the `java.base` platform module. We draw our own graphs to illustrate the module relation as below. 16 | 17 | image:dot/summary.jpeg[] 18 | 19 | image:dot/summary-exports-requires.png[] 20 | 21 | But wait... how is it possible that the `modi.entrypoint` module works without touching `modi.pack` and `modi.mod`? In other words, how the application works without knowing `modi.pack` and `modi.mod`? The answer is *Service Provider*. Java Service Provider is around us since Java 1.6. We are able to use it in Java 9 as well to access loosely coupled service implementations/providers from other modules. 22 | 23 | image:dot/summary-w-provider.png[] 24 | 25 | In order to register the implementations of a service (or providers of a service), we need to use the `provides .. with` clause in the module description of each implementation module. In our case, we have a search service. The search interface was declared in the `modi.api` module. As you probably know that the application simply provides a way of searching a module name or a package name among the available modules and packages. Since we have 2 different options, we have 2 different search implementations/providers. ModuleQuery is in charge of finding whether a given module name exists or not. On the other hand, PackageQuery enables to search the presence of a given package name. These services could be anything like a CRUD service. As a result, the `modi.pack` module provides the Query interface with its implementation `PackageQuery` and the `modi.mod` module provides the Query interface with its implementation `ModuleQuery`. When we apply for Service Loader, it will find the registered Query providers (e.g. `ModuleQuery` and `PackageQuery`) at runtime. To find these implementations from another module we need to use the `uses` clause; we would access related service implementations as *providers*. The `modi.entrpoint` module applies for the `uses` clause to consume the concrete Query services. Plus, the entrypoint module itself doesn’t know the underlying implementations of the Query service. 26 | 27 | toc::[] 28 | 29 | === #{counter:index} Compiling the project 30 | 31 | We compile the 4 related modules separately and place their compiled classes into the out/ directory. 32 | 33 | ---- 34 | $ javac -d out/modi.api/ --source-path modi.api/src/ $(find modi.api/src -name '*.java') 35 | $ javac -d out/modi.mod --module-path out/modi.api/ --source-path modi.mod.impl/src/ $(find modi.mod.impl/src/ -name '*.java') <1> 36 | $ javac -d out/modi.pack --module-path out/modi.api/ --source-path modi.pack.impl/src/ $(find modi.pack.impl/src/ -name '*.java') <2> 37 | $ javac -d out/modi.entrypoint/ --module-path out/modi.api --source-path modi.entrypoint/src/ $(find modi.entrypoint/src/ -name '*.java') <3> 38 | ---- 39 | 1. The `modi.api` module is added on the module path from the `out/` directory. 40 | 2. Only the `modi.api` module is needed to be on the module path. 41 | 3. Only the `modi.api` module is needed to be on the module path. 42 | 43 | === #{counter:index} Running the project 44 | 45 | We are in the running stage of the project. The project itself consists of 4 modules each of which has a module description. In a way, the module path is all we need to run the complete system. The `out` directory now consists of 4 named exploded modules. We directly give that directory in the java command and specify the `modi.entrypoint` module where it has a static main method to execute the project itself. 46 | 47 | ---- 48 | $ java -p out/ -m modi.entrypoint/com.modi.entrypoint.Main -module 49 | Commands: 50 | -m,--module a module name 51 | -p,--package a package name 52 | $ java -p out/ -m modi.entrypoint/com.modi.entrypoint.Main --module java.base 53 | :) 54 | ---- 55 | 56 | === #{counter:index} JAR festival 57 | 58 | Since each module is an independent named application module, you must create 4 different modular JAR files. These JARs will have their own module version. 59 | 60 | ---- 61 | $ mkdir jar 62 | $ jar -c -f jar/modi.api@1.2.0.jar --module-version 1.2.0 -C out/modi.api/ . 63 | $ jar -c -f jar/modi.mod@1.2.0.jar --module-version 1.2.0 -C out/modi.mod/ . 64 | $ jar -c -f jar/modi.pack@1.2.0.jar --module-version 1.2.0 -C out/modi.pack/ . 65 | $ jar -c -f jar/modi.entrypoint@1.2.0.jar --module-version 1.2.0 -e com.modi.entrypoint.Main -C out/modi.entrypoint/ . 66 | ---- 67 | 68 | After the above steps, the `jar` directory provides the following application modules: 69 | 70 | ---- 71 | . 72 | └── jar 73 | ├── modi.api@1.2.0.jar 74 | ├── modi.entrypoint@1.2.0.jar 75 | ├── modi.mod@1.2.0.jar 76 | └── modi.pack@1.2.0.jar 77 | ---- 78 | 79 | Last, we run the application by putting the JAR files on the module path. Since the `modi.entrypoint` module has an module description, we don’t need to add the qualified module name and its class name with the `-m` flag. The Java 9 command will look at the module description and take the main class. 80 | 81 | ---- 82 | $ java -p jar/ -m modi.entrypoint -m java.base 83 | :) 84 | $ java -p jar/ -m modi.entrypoint -m java.xml.bind 85 | :( <1> 86 | ---- 87 | 1. The `java.xml.bind` module even it’s part of the Java module system, we only depend on the `java.base` module. As a result, searching an unrelated module does give :(. 88 | 89 | === #{counter:index} Dockerize the application module 90 | 91 | We are ready to create an own Docker image but first we need an application runtime image. 92 | 93 | ---- 94 | $ docker run -v $(pwd):/modi/ -w /modi --rm alpine:jdk-9-musl jlink \ 95 | --launcher modims=modi.entrypoint/com.modi.entrypoint.Main \ 96 | --output modimage/ \ 97 | --verbose \ 98 | --no-header-files --no-man-pages --compress 2 \ 99 | --module-path jar:/opt/jdk-9/jmods \ 100 | --add-modules modi.entrypoint,modi.mod,modi.pack <1> 101 | java.base file:///opt/jdk-9/jmods/java.base.jmod 102 | modi.api file:///modi/jar/modi.api@1.2.0.jar 103 | modi.entrypoint file:///modi/jar/modi.entrypoint@1.2.0.jar 104 | modi.mod file:///modi/jar/modi.mod@1.2.0.jar 105 | modi.pack file:///modi/jar/modi.pack@1.2.0.jar 106 | 107 | Providers: <2> 108 | modi.mod provides com.modi.api.query.Query used by modi.entrypoint 109 | modi.pack provides com.modi.api.query.Query used by modi.entrypoint 110 | java.base provides java.nio.file.spi.FileSystemProvider used by java.base 111 | ---- 112 | 1. Each mentioned application module is an individual root module. 113 | 2. Note that the runtime image provides 3 different providers now! 114 | 115 | Now that we have an runtime image, we are able to make a Docker image based on Alpine 3.6. 116 | 117 | ---- 118 | $ docker build -t modims --no-cache . 119 | Sending build context to Docker daemon 33.68MB 120 | Step 1/5 : FROM alpine:3.6 121 | ---> 053cde6e8953 122 | Step 2/5 : COPY modimage /opt/modi 123 | ---> cc2719e40f00 124 | Step 3/5 : ENV JAVA_HOME=/opt/modi 125 | ---> Running in 8d0a4cb2ba7f 126 | Removing intermediate container 8d0a4cb2ba7f 127 | ---> f3de07641028 128 | Step 4/5 : ENV PATH=$PATH:$JAVA_HOME/bin 129 | ---> Running in 38f2874e42ff 130 | Removing intermediate container 38f2874e42ff 131 | ---> 3b04f3233e47 132 | Step 5/5 : ENTRYPOINT [ "modims" ] 133 | ---> Running in e77434c19450 134 | Removing intermediate container e77434c19450 135 | ---> 0e128cebe4ba 136 | Successfully built 0e128cebe4ba 137 | Successfully tagged modims:latest 138 | 139 | $ docker run --rm modims:latest -m modi.mod 140 | :) 141 | ---- -------------------------------------------------------------------------------- /modims-provides-uses/dot/api/modi.api.dot: -------------------------------------------------------------------------------- 1 | digraph "modi.api" { 2 | // Path: /Users/hakan/IdeaProjects/jigsaw/modims-provides-uses/out/modi.api 3 | "com.modi.api.query" -> "java.lang (java.base)"; 4 | "com.modi.api.query.annotation" -> "java.lang (java.base)"; 5 | "com.modi.api.query.annotation" -> "java.lang.annotation (java.base)"; 6 | } 7 | -------------------------------------------------------------------------------- /modims-provides-uses/dot/api/summary.dot: -------------------------------------------------------------------------------- 1 | digraph "summary" { 2 | "modi.api" -> "java.base (java.base)"; 3 | } 4 | -------------------------------------------------------------------------------- /modims-provides-uses/dot/mod/modi.api.dot: -------------------------------------------------------------------------------- 1 | digraph "modi.api" { 2 | // Path: /Users/hakan/IdeaProjects/jigsaw/modims-provides-uses/out/modi.api 3 | "com.modi.api.query" -> "java.lang (java.base)"; 4 | "com.modi.api.query.annotation" -> "java.lang (java.base)"; 5 | "com.modi.api.query.annotation" -> "java.lang.annotation (java.base)"; 6 | } 7 | -------------------------------------------------------------------------------- /modims-provides-uses/dot/mod/modi.mod.dot: -------------------------------------------------------------------------------- 1 | digraph "modi.mod" { 2 | // Path: /Users/hakan/IdeaProjects/jigsaw/modims-provides-uses/out/modi.mod 3 | "com.mod.impl.mod" -> "com.modi.api.query (modi.api)"; 4 | "com.mod.impl.mod" -> "com.modi.api.query.annotation (modi.api)"; 5 | "com.mod.impl.mod" -> "java.lang (java.base)"; 6 | "com.mod.impl.mod" -> "java.lang.invoke (java.base)"; 7 | "com.mod.impl.mod" -> "java.util (java.base)"; 8 | "com.mod.impl.mod" -> "java.util.function (java.base)"; 9 | "com.mod.impl.mod" -> "java.util.stream (java.base)"; 10 | "com.mod.impl.util" -> "java.lang (java.base)"; 11 | } 12 | -------------------------------------------------------------------------------- /modims-provides-uses/dot/mod/summary.dot: -------------------------------------------------------------------------------- 1 | digraph "summary" { 2 | "modi.api" -> "java.base (java.base)"; 3 | "modi.mod" -> "java.base (java.base)"; 4 | "modi.mod" -> "modi.api"; 5 | } 6 | -------------------------------------------------------------------------------- /modims-provides-uses/dot/modi.api.dot: -------------------------------------------------------------------------------- 1 | digraph "modi.api" { 2 | // Path: /Users/hakan/IdeaProjects/jigsaw/modims-provides-uses/out/modi.api 3 | "com.modi.api.query" -> "java.lang (java.base)"; 4 | "com.modi.api.query.annotation" -> "java.lang (java.base)"; 5 | "com.modi.api.query.annotation" -> "java.lang.annotation (java.base)"; 6 | } 7 | -------------------------------------------------------------------------------- /modims-provides-uses/dot/modi.entrypoint.dot: -------------------------------------------------------------------------------- 1 | digraph "modi.entrypoint" { 2 | // Path: /Users/hakan/IdeaProjects/jigsaw/modims-provides-uses/out/modi.entrypoint 3 | "com.modi.entrypoint" -> "com.modi.api.query (modi.api)"; 4 | "com.modi.entrypoint" -> "com.modi.api.query.annotation (modi.api)"; 5 | "com.modi.entrypoint" -> "java.io (java.base)"; 6 | "com.modi.entrypoint" -> "java.lang (java.base)"; 7 | "com.modi.entrypoint" -> "java.lang.annotation (java.base)"; 8 | "com.modi.entrypoint" -> "java.lang.invoke (java.base)"; 9 | "com.modi.entrypoint" -> "java.util (java.base)"; 10 | "com.modi.entrypoint" -> "java.util.function (java.base)"; 11 | "com.modi.entrypoint" -> "java.util.stream (java.base)"; 12 | } 13 | -------------------------------------------------------------------------------- /modims-provides-uses/dot/pack/modi.api.dot: -------------------------------------------------------------------------------- 1 | digraph "modi.api" { 2 | // Path: /Users/hakan/IdeaProjects/jigsaw/modims-provides-uses/out/modi.api 3 | "com.modi.api.query" -> "java.lang (java.base)"; 4 | "com.modi.api.query.annotation" -> "java.lang (java.base)"; 5 | "com.modi.api.query.annotation" -> "java.lang.annotation (java.base)"; 6 | } 7 | -------------------------------------------------------------------------------- /modims-provides-uses/dot/pack/modi.pack.dot: -------------------------------------------------------------------------------- 1 | digraph "modi.pack" { 2 | // Path: /Users/hakan/IdeaProjects/jigsaw/modims-provides-uses/out/modi.pack 3 | "com.pack.impl.pack" -> "com.modi.api.query (modi.api)"; 4 | "com.pack.impl.pack" -> "com.modi.api.query.annotation (modi.api)"; 5 | "com.pack.impl.pack" -> "java.lang (java.base)"; 6 | "com.pack.impl.pack" -> "java.lang.invoke (java.base)"; 7 | "com.pack.impl.pack" -> "java.util (java.base)"; 8 | "com.pack.impl.pack" -> "java.util.function (java.base)"; 9 | "com.pack.impl.pack" -> "java.util.stream (java.base)"; 10 | "com.pack.impl.utils" -> "java.lang (java.base)"; 11 | } 12 | -------------------------------------------------------------------------------- /modims-provides-uses/dot/pack/summary.dot: -------------------------------------------------------------------------------- 1 | digraph "summary" { 2 | "modi.pack" -> "java.base (java.base)"; 3 | "modi.pack" -> "modi.api"; 4 | "modi.api" -> "java.base (java.base)"; 5 | "modi.mod" -> "java.base (java.base)"; 6 | "modi.mod" -> "modi.api"; 7 | "modi.entrypoint" -> "java.base (java.base)"; 8 | "modi.entrypoint" -> "modi.api"; 9 | } 10 | -------------------------------------------------------------------------------- /modims-provides-uses/dot/summary-exports-requires.dot: -------------------------------------------------------------------------------- 1 | digraph "summary" { 2 | "modi.entrypoint" -> "modi.api\nexports com.modi.api.query;\nexports com.modi.api.query.annotation;" [color=blue,label="requires"]; 3 | "modi.pack" -> "modi.api\nexports com.modi.api.query;\nexports com.modi.api.query.annotation;" [color=blue,label="requires"]; 4 | "modi.mod" -> "modi.api\nexports com.modi.api.query;\nexports com.modi.api.query.annotation;" [color=blue,label="requires"]; 5 | } 6 | -------------------------------------------------------------------------------- /modims-provides-uses/dot/summary-exports-requires.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ozlerhakan/java9-module-examples/2f8bf4d44fbb6ea5c6599cf5ad7899d6956c5451/modims-provides-uses/dot/summary-exports-requires.png -------------------------------------------------------------------------------- /modims-provides-uses/dot/summary-w-provider.dot: -------------------------------------------------------------------------------- 1 | digraph "not" { 2 | "modi.pack" -> "Service Loader" [color=blue,label="requires com.modi.api.query.Query with PackageQuery"]; 3 | "modi.mod" -> "Service Loader" [color=blue,label="\nprovides com.modi.api.query.Query with ModuleQuery"]; 4 | "modi.entrypoint" -> "Service Loader" [color=red, label="uses com.modi.api.query.Query;"] 5 | } 6 | -------------------------------------------------------------------------------- /modims-provides-uses/dot/summary-w-provider.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ozlerhakan/java9-module-examples/2f8bf4d44fbb6ea5c6599cf5ad7899d6956c5451/modims-provides-uses/dot/summary-w-provider.png -------------------------------------------------------------------------------- /modims-provides-uses/dot/summary.dot: -------------------------------------------------------------------------------- 1 | digraph "summary" { 2 | "modi.entrypoint" -> "java.base (java.base)"; 3 | "modi.entrypoint" -> "modi.api"; 4 | "modi.api" -> "java.base (java.base)"; 5 | } 6 | -------------------------------------------------------------------------------- /modims-provides-uses/dot/summary.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ozlerhakan/java9-module-examples/2f8bf4d44fbb6ea5c6599cf5ad7899d6956c5451/modims-provides-uses/dot/summary.jpeg -------------------------------------------------------------------------------- /modims-provides-uses/jar/modi.api@1.2.0.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ozlerhakan/java9-module-examples/2f8bf4d44fbb6ea5c6599cf5ad7899d6956c5451/modims-provides-uses/jar/modi.api@1.2.0.jar -------------------------------------------------------------------------------- /modims-provides-uses/jar/modi.entrypoint@1.2.0.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ozlerhakan/java9-module-examples/2f8bf4d44fbb6ea5c6599cf5ad7899d6956c5451/modims-provides-uses/jar/modi.entrypoint@1.2.0.jar -------------------------------------------------------------------------------- /modims-provides-uses/jar/modi.mod@1.2.0.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ozlerhakan/java9-module-examples/2f8bf4d44fbb6ea5c6599cf5ad7899d6956c5451/modims-provides-uses/jar/modi.mod@1.2.0.jar -------------------------------------------------------------------------------- /modims-provides-uses/jar/modi.pack@1.2.0.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ozlerhakan/java9-module-examples/2f8bf4d44fbb6ea5c6599cf5ad7899d6956c5451/modims-provides-uses/jar/modi.pack@1.2.0.jar -------------------------------------------------------------------------------- /modims-provides-uses/modi.api/src/com/modi/api/query/Query.java: -------------------------------------------------------------------------------- 1 | package com.modi.api.query; 2 | 3 | /** 4 | * Created by hakan on 17/11/2017 5 | */ 6 | public interface Query { 7 | 8 | boolean exists(String keyword); 9 | } 10 | -------------------------------------------------------------------------------- /modims-provides-uses/modi.api/src/com/modi/api/query/annotation/Search.java: -------------------------------------------------------------------------------- 1 | package com.modi.api.query.annotation; 2 | 3 | import java.lang.annotation.Retention; 4 | import java.lang.annotation.RetentionPolicy; 5 | 6 | /** 7 | * Created by hakan on 19/11/2017 8 | */ 9 | @Retention(RetentionPolicy.RUNTIME) 10 | public @interface Search { 11 | 12 | /** 13 | * specifies multiple flags 14 | * 15 | * @return list of flags 16 | */ 17 | String[] value() default {}; 18 | } 19 | -------------------------------------------------------------------------------- /modims-provides-uses/modi.api/src/module-info.java: -------------------------------------------------------------------------------- 1 | module modi.api { 2 | 3 | // exports 4 | exports com.modi.api.query; 5 | exports com.modi.api.query.annotation; 6 | } -------------------------------------------------------------------------------- /modims-provides-uses/modi.entrypoint/src/com/modi/entrypoint/Main.java: -------------------------------------------------------------------------------- 1 | package com.modi.entrypoint; 2 | 3 | 4 | import com.modi.api.query.Query; 5 | import com.modi.api.query.annotation.Search; 6 | 7 | import java.util.ServiceLoader; 8 | import java.util.stream.Stream; 9 | 10 | public class Main { 11 | 12 | public static void main(String[] args) { 13 | 14 | if (args.length != 2) { 15 | printUsage(); 16 | System.exit(0); 17 | } 18 | 19 | final String flag = args[0].toLowerCase(); 20 | final String keyword = args[1]; 21 | 22 | // we find the corresponding service at runtime! 23 | ServiceLoader loader = ServiceLoader.load(Query.class); 24 | 25 | loader.stream() 26 | .filter(service -> { 27 | Class queryType = service.type(); 28 | 29 | if (queryType.isAnnotationPresent(Search.class)) { 30 | String[] flags = queryType.getAnnotation(Search.class).value(); 31 | return Stream.of(flags).anyMatch(f -> f.equals(flag)); 32 | } 33 | return false; 34 | }) 35 | .map(ServiceLoader.Provider::get) 36 | .findFirst() 37 | .ifPresentOrElse(query -> { 38 | if (query.exists(keyword)) { 39 | System.out.println(":)"); 40 | } else { 41 | System.out.println(":("); 42 | } 43 | }, 44 | Main::printUsage); 45 | } 46 | 47 | private static void printUsage() { 48 | System.out.println("Commands:\n" + 49 | "-m,--module\ta module name \n" + 50 | "-p,--package\ta package name"); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /modims-provides-uses/modi.entrypoint/src/module-info.java: -------------------------------------------------------------------------------- 1 | module modi.entrypoint { 2 | 3 | requires modi.api; 4 | 5 | // to consume the query service in the Java module system, we need to use the uses clause. 6 | // modi.entrypoint uses ServiceLoader to find the target interface out along with its relevant implementations! 7 | // we'll access the related service implementations, moduleQuery and packageQuery, at runtime in the main class thanks to serviceloader. 8 | // The modi.entrypoint module doesn't need to know anything about modules providing the Query implementations. 9 | // only knowing the Query type is enough for serviceloader to find the implementations of the Query interface at runtime! 10 | uses com.modi.api.query.Query; 11 | } -------------------------------------------------------------------------------- /modims-provides-uses/modi.mod.impl/src/com/mod/impl/mod/ModuleQuery.java: -------------------------------------------------------------------------------- 1 | package com.mod.impl.mod; 2 | 3 | import com.modi.api.query.Query; 4 | import com.modi.api.query.annotation.Search; 5 | 6 | import static com.mod.impl.util.Flags.MODULE_FLAG; 7 | import static com.mod.impl.util.Flags.M_FLAG; 8 | 9 | /** 10 | * Created by hakan on 12/11/2017 11 | */ 12 | @Search({MODULE_FLAG, M_FLAG}) 13 | public final class ModuleQuery implements Query { 14 | 15 | //a public non-arg constructor class needed to be a service 16 | 17 | /** 18 | * find whether the target keyword exists in the module system or not 19 | * 20 | * @param keyword a searchable module name 21 | * @return true if the keyword exists, otherwise false 22 | */ 23 | public boolean exists(String keyword) { 24 | 25 | // this module is an application module 26 | final Module module = ModuleQuery.class.getModule(); 27 | final ModuleLayer moduleLayer = module.getLayer(); 28 | 29 | return moduleLayer.modules() 30 | .parallelStream() 31 | .anyMatch(layer -> keyword.equals(layer.getName())); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /modims-provides-uses/modi.mod.impl/src/com/mod/impl/util/Flags.java: -------------------------------------------------------------------------------- 1 | package com.mod.impl.util; 2 | 3 | /** 4 | * Created by hakan on 20/11/2017 5 | */ 6 | public final class Flags { 7 | 8 | private Flags() { 9 | } 10 | 11 | public final static String MODULE_FLAG = "--module"; 12 | public final static String M_FLAG = "-m"; 13 | 14 | } 15 | -------------------------------------------------------------------------------- /modims-provides-uses/modi.mod.impl/src/module-info.java: -------------------------------------------------------------------------------- 1 | import com.mod.impl.mod.ModuleQuery; 2 | 3 | module modi.mod { 4 | 5 | requires modi.api; 6 | 7 | // modi.mod provides the service Query with its implementation ModuleQuery! 8 | // to access the com.modi.api.query.Query type, modi.mod (this module) requires the target module. 9 | // provides with 10 | provides com.modi.api.query.Query with ModuleQuery; 11 | } -------------------------------------------------------------------------------- /modims-provides-uses/modi.pack.impl/src/com/pack/impl/pack/PackageQuery.java: -------------------------------------------------------------------------------- 1 | package com.pack.impl.pack; 2 | 3 | import com.modi.api.query.Query; 4 | import com.modi.api.query.annotation.Search; 5 | 6 | import static com.pack.impl.utils.Flags.PACKAGE_FLAG; 7 | import static com.pack.impl.utils.Flags.P_FLAG; 8 | 9 | /** 10 | * Created by hakan on 19/11/2017 11 | */ 12 | @Search({PACKAGE_FLAG, P_FLAG}) 13 | public final class PackageQuery implements Query { 14 | 15 | //a public non-arg constructor class needed to be a service 16 | 17 | /** 18 | * find whether the target keyword exists in the module system or not 19 | * 20 | * @param keyword a searchable package name 21 | * @return true if the keyword exists, otherwise false 22 | */ 23 | @Override 24 | public boolean exists(String keyword) { 25 | // this module is an application module 26 | final Module module = PackageQuery.class.getModule(); 27 | final ModuleLayer moduleLayer = module.getLayer(); 28 | 29 | return moduleLayer.modules() 30 | .parallelStream() 31 | .anyMatch(layer -> layer.getPackages().stream().anyMatch(pck -> pck.equals(keyword))); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /modims-provides-uses/modi.pack.impl/src/com/pack/impl/utils/Flags.java: -------------------------------------------------------------------------------- 1 | package com.pack.impl.utils; 2 | 3 | /** 4 | * Created by hakan on 20/11/2017 5 | */ 6 | public final class Flags { 7 | 8 | private Flags() { 9 | } 10 | 11 | public final static String PACKAGE_FLAG = "--package"; 12 | public final static String P_FLAG = "-p"; 13 | } 14 | -------------------------------------------------------------------------------- /modims-provides-uses/modi.pack.impl/src/module-info.java: -------------------------------------------------------------------------------- 1 | import com.pack.impl.pack.PackageQuery; 2 | 3 | module modi.pack { 4 | 5 | requires modi.api; 6 | 7 | // modi.pack provides the service Query with its implementation PackageQuery! 8 | // to access the com.modi.api.query.Query type, modi.pack (this module) requires the target module. 9 | // provides with 10 | provides com.modi.api.query.Query with PackageQuery; 11 | } -------------------------------------------------------------------------------- /reflect/.gitignore: -------------------------------------------------------------------------------- 1 | out 2 | .idea/ 3 | *.iml -------------------------------------------------------------------------------- /reflect/README.adoc: -------------------------------------------------------------------------------- 1 | = reflect 2 | 3 | In this example, we examine the module descriptions of the three modules, `reflect.deep`, `reflect.normal`, and `reflect.entrypoint`, for understanding the walkthrough of how to use deep reflection in the Java module system. For the sake of clarity, we need to know what deep reflection is at first. Deep reflection is the way we can manipulate nonpublic members of classes at runtime using the Java Reflection API. Applying for deep reflection prior to Java 9 doesn't require anything to do in advance. However, you need to adjust your modular descriptor as of Java 9, if other modules are willing to access the private members of exposed types at runtime. As Java 9 provides strong encapsulation, deep reflection is no exception. In order words, we must open an entire module or specific packages of the module in advance in which deep reflection is the case for other (automatic, application or platform) modules. 4 | 5 | Let's take a look at the `reflect.normal` and `reflect.deep` modules respectively. The two modules contain POJO classes. The following `Machine` class located in the `reflect.normal` module, consists of two public members: 6 | 7 | ---- 8 | package com.reflect.normal; 9 | 10 | public class Machine { 11 | 12 | public String name; 13 | 14 | public boolean start() { 15 | return true; 16 | } 17 | 18 | } 19 | ---- 20 | 21 | In the `reflect.entrypoint` module, we want to react this class and manage its public members at runtime. In order to do so, we only need to export the `com.reflect.normal` package so that other modules like `reflect.entrypoint` can manipulate the public members of the `Machine` class. 22 | 23 | .module-info.java 24 | ---- 25 | module com.reflect.normal { 26 | 27 | exports com.reflect.normal; 28 | } 29 | ---- 30 | 31 | NOTE: Keep in mind that module names must be the same as the name of the root package. This is a recommended way when Java Module Platform is taken into account. If the module name is the name of the root package of your module, we deduce the package and module names are unique in the Java namespace. In our case, the module name, `com.reflect.normal`, is the same as the root package name. 32 | 33 | Notice that we only use the `exports` clauses in the module descriptor. Not only does the `exports` clause enable to react the exported public types during compilation, but It also enables to manage the public members of the types at runtime. What is important to note that we do only access the public members of types with the `exports` clauses when consuming the reflection api. In no way do we read the (private, protected, default) members of a class with this approach. 34 | 35 | Now that we are able to read the public types of the module `reflect.ormal`, we can simply apply for the reflection API to modify their public members: 36 | 37 | ---- 38 | Field field = Machine.class.getField("name"); 39 | Machine newInstance = Machine.class.getDeclaredConstructor().newInstance(); 40 | field.set(newInstance, "Machine 9876"); 41 | 42 | String value = (String) field.get(newInstance); 43 | System.out.println(value); 44 | 45 | Method declaredMethod = Machine.class.getDeclaredMethod("start"); 46 | boolean result = (boolean) declaredMethod.invoke(newInstance); 47 | 48 | System.out.println(result); 49 | ---- 50 | 51 | Let's now focus on another module called `reflect.deep`. This module constains only one public class consisting of two private members. Provided that we export the corresponding package to other modules, we end up with the following error when using reflection API. 52 | 53 | ---- 54 | Exception in thread "main" java.lang.IllegalAccessException: class com.reflect.entrypoint.Main (in module com.ref.entrypoint) cannot access a member of class com.reflect.deep.Gear (in module com.reflect.deep) with modifiers "private" 55 | ---- 56 | 57 | Oh!, It looks like private members are not reachable from the outside when deep reflection is performed. What's more, we noticeably have seen that the `exports` clause couldn't access non-public members. To be used deep reflection we need to apply for the `opens` clauses. Simply put, we ensure that the class and its non-public members are accessible at runtime but not at compile time. 58 | 59 | .the module descriptor of the `reflect.deep` module 60 | ---- 61 | module com.reflect.deep { 62 | 63 | opens com.reflect.deep; 64 | 65 | } 66 | ---- 67 | 68 | As the mentioned package is reachable only at runtime, the following snippet can work on other modules based on the public exported types. 69 | 70 | ---- 71 | Field model = Class.forName("com.reflect.deep.Gear").getDeclaredField("model"); 72 | Object newInstanceOfGear = Class.forName("com.reflect.deep.Gear").getDeclaredConstructor().newInstance(); 73 | 74 | model.trySetAccessible(); 75 | model.set(newInstanceOfGear, 2017); 76 | System.out.println(model.get(newInstanceOfGear)); 77 | ---- 78 | 79 | Next to this, there are a few steps as well. For example, we can use both the `exports` and `opens` clauses over a package for public types at the same time. This approach comes with twofold. We access the exported types at compile time, likewise we are ready to use deep reflection at run time. Another approach is to open all the packages of a module for deep reflection: 80 | 81 | ---- 82 | open module com.reflect.deep { 83 | } 84 | ---- 85 | 86 | Since the later form looks like a precise and clear way when dealing with deep reflection, 87 | be aware that we open all the packages to be used for deep reflection at runtime by other modules. In addition, we break strong encapsulation when opening a package at runtime. So you might think this approach during migration only. 88 | 89 | You can use the following steps to compile and run the example or import the project into your favorite IDE and run the main class of the `reflect.entrypoint` module. 90 | 91 | ---- 92 | $ javac -d out/reflect.normal/ --source-path reflect.normal/src/ $(find reflect.normal/src -name '*.java') 93 | $ javac -d out/reflect.deep/ --source-path reflect.deep/src/ $(find reflect.deep/src/ -name '*.java') 94 | $ javac -d out/reflect.entrypoint --module-path out/reflect.normal:out/reflect.deep --source-path reflect.entrypoint/src/ $(find reflect.entrypoint/src/ -name '*.java') 95 | $ # run 96 | $ java -p out/ -m com.reflect.entrypoint/com.reflect.entrypoint.Main 97 | Machine 9876 98 | true 99 | 2017 100 | ---- 101 | -------------------------------------------------------------------------------- /reflect/reflect.deep/src/com/reflect/deep/Gear.java: -------------------------------------------------------------------------------- 1 | package com.reflect.deep; 2 | 3 | /** 4 | * Created by hakan on 30/11/2017 5 | */ 6 | public class Gear { 7 | 8 | private int model; 9 | 10 | private void doSomething() { 11 | System.out.println("doSomething"); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /reflect/reflect.deep/src/module-info.java: -------------------------------------------------------------------------------- 1 | module com.reflect.deep { 2 | 3 | opens com.reflect.deep; 4 | } 5 | 6 | // exports com.reflect.deep; 7 | // opens com.reflect.deep; 8 | 9 | //or 10 | 11 | // opens com.reflect.deep; 12 | 13 | //or open a package to a specific module 14 | 15 | // opens com.reflect.deep to reflect.entrypoint; 16 | 17 | // or 18 | 19 | // open module reflect.deep { } 20 | 21 | -------------------------------------------------------------------------------- /reflect/reflect.entrypoint/src/com/reflect/entrypoint/Main.java: -------------------------------------------------------------------------------- 1 | package com.reflect.entrypoint; 2 | 3 | import com.reflect.normal.Machine; 4 | 5 | import java.lang.reflect.Field; 6 | import java.lang.reflect.Method; 7 | 8 | /** 9 | * Created by hakan on 30/11/2017 10 | */ 11 | public class Main { 12 | 13 | public static void main(String[] args) throws Exception { 14 | 15 | // reflect.normal 16 | 17 | Field field = Machine.class.getField("name"); 18 | Machine newInstance = Machine.class.getDeclaredConstructor().newInstance(); 19 | field.set(newInstance, "Machine 9876"); 20 | 21 | String value = (String) field.get(newInstance); 22 | System.out.println(value); 23 | 24 | Method declaredMethod = Machine.class.getDeclaredMethod("start"); 25 | boolean result = (boolean) declaredMethod.invoke(newInstance); 26 | 27 | System.out.println(result); 28 | 29 | //reflect.deep 30 | 31 | Field model = Class.forName("com.reflect.deep.Gear").getDeclaredField("model"); 32 | Object newInstanceOfGear = Class.forName("com.reflect.deep.Gear").getDeclaredConstructor().newInstance(); 33 | 34 | model.trySetAccessible(); 35 | model.set(newInstanceOfGear, 2017); 36 | System.out.println(model.get(newInstanceOfGear)); 37 | 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /reflect/reflect.entrypoint/src/module-info.java: -------------------------------------------------------------------------------- 1 | module com.reflect.entrypoint { 2 | 3 | requires com.reflect.normal; 4 | requires com.reflect.deep; 5 | } -------------------------------------------------------------------------------- /reflect/reflect.normal/src/com/reflect/normal/Machine.java: -------------------------------------------------------------------------------- 1 | package com.reflect.normal; 2 | 3 | /** 4 | * Created by hakan on 30/11/2017 5 | */ 6 | public class Machine { 7 | 8 | public String name; 9 | 10 | public boolean start() { 11 | return true; 12 | } 13 | 14 | } 15 | -------------------------------------------------------------------------------- /reflect/reflect.normal/src/module-info.java: -------------------------------------------------------------------------------- 1 | module com.reflect.normal { 2 | 3 | exports com.reflect.normal; 4 | } -------------------------------------------------------------------------------- /requires-transitive/.gitignore: -------------------------------------------------------------------------------- 1 | # Created by .ignore support plugin (hsz.mobi) 2 | .idea 3 | target/ 4 | *.iml 5 | image/ 6 | 7 | -------------------------------------------------------------------------------- /requires-transitive/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM alpine:3.6 2 | 3 | # copy the Java runtime image to the /opt/modi directory of the container 4 | COPY image /opt/modi 5 | 6 | # Set up env variables 7 | ENV JAVA_HOME=/opt/modi 8 | ENV PATH=$PATH:$JAVA_HOME/bin 9 | 10 | ENTRYPOINT [ "transitive" ] 11 | -------------------------------------------------------------------------------- /requires-transitive/README.adoc: -------------------------------------------------------------------------------- 1 | = Ret (Requires Transitive) 2 | 3 | In this example, we'll learn how to handle transitive dependencies on a module system. The current system rely on three independent modules. The `employee.model` module has a model class called `Employee`. The `employee.service` module depends upon the `Employee` class and works as a service layer. The service module provides a service `EmployeeService` along with its implementation `EmployeeServiceImpl`. The last module simply consumes the employee service by querying employees based on a few criteria. The relation between modules are illustrated below: 4 | 5 | image:dot/entrypoint/summary-without-transitive.png[] 6 | 7 | Now we use one of the service methods by applying for Service Loader in the module `entrypoint`. If we run the code below, we don't see the excepted result rather we see the following exception: 8 | 9 | ---- 10 | EmployeeService employeeService = ServiceLoader.load(EmployeeService.class).findFirst().get(); 11 | 12 | employeeService.findOneBy(44).ifPresent(System.out::println); 13 | 14 | Error:(3, 41) java: employee.model.Employee is not visible 15 | (package employee.model is declared in module employee.model, but module employee.entrypoint does not read it) 16 | ---- 17 | 18 | It says that the `entrypoint` module cannot react the model `employee.model.Employee` at runtime because it's not visible to the entrypoint module. The `employeeService.findOneBy(44)` method returns the first selected employee from the list where the employee's age is 44. So it looks like the employee service finds one employee and reveals to the client. Therefore, we need to access to the `employee.model.Employee` class from the entrypoint module. Now we understand what the problem is. There are two options to fix that issue. First, we can add another `requires` clause to the module description of entrypoint which is not quite reliable since we have to handle it manually. Second is that, we can let the `employee.entrypoint` module deal with the transitive dependency automatically when adding the direct dependency (i.e. the `employee.service` module). In our case, the transitive dependency is the `employee.model.Employee` class exported from the `employee.model` module. Plus, this dependency is the dependency of the `employee.service` module. Editing the module description of the service module by just adding the `transitive` keyword in the `requires` clause will fix the problem. 19 | 20 | .the complete module description of the service module 21 | ---- 22 | module employee.service { 23 | 24 | ... 25 | 26 | requires transitive employee.model; 27 | 28 | ... 29 | } 30 | ---- 31 | 32 | Now the `Employee` class is visible and readable from other modules as long as the `employee.service` module is required. 33 | 34 | image:dot/entrypoint/summary.png[] 35 | 36 | [NOTE] 37 | ==== 38 | The compilation process is resolved by `maven` >= 3.0.0. 39 | 40 | ---- 41 | $ cd requires-transitive/ 42 | $ mvn clean install 43 | ---- 44 | ==== 45 | 46 | === Dockerize the application module 47 | 48 | To create our own Docker image, we need an application runtime image first for Alpine Linux. https://maven.apache.org/plugins/maven-jlink-plugin/[Maven Jlink Plugin] is in the pre-release stage for Java 9, I couldn't create one with the plugin (If you manage to work with it, please let me know). So we perform the same steps as we already did before, just copy all the modular application JARs into the `jar` directory and generate an runtime image. 49 | 50 | ---- 51 | $ pwd 52 | /jigsaw/requires-transitive 53 | $ mkdir jar 54 | $ cp employee.entrypoint/target/employee.entrypoint-1.0.jar jar 55 | $ cp employee.service/target/employee.service-1.0.jar jar 56 | $ cp employee.model/target/employee.model-1.0.jar jar 57 | $ 58 | $ docker run -v $(pwd):/modi/ -w /modi --rm alpine:jdk-9-musl jlink \ 59 | --launcher transitive=employee.entrypoint/employee.entrypoint.Main \ 60 | --output image/ \ 61 | --verbose \ 62 | --no-header-files --no-man-pages --compress 2 \ 63 | --module-path jar:/opt/jdk-9/jmods \ 64 | --add-modules employee.entrypoint 65 | employee.entrypoint file:///modi/jar/employee.entrypoint-1.0.jar 66 | employee.model file:///modi/jar/employee.model-1.0.jar 67 | employee.service file:///modi/jar/employee.service-1.0.jar 68 | java.base file:///opt/jdk-9/jmods/java.base.jmod 69 | 70 | Providers: <1> 71 | employee.service provides employee.service.api.EmployeeService used by employee.entrypoint 72 | java.base provides java.nio.file.spi.FileSystemProvider used by java.base 73 | ---- 74 | 1. Note that the runtime image provides 2 providers. 75 | 76 | Now that we have an runtime image, we can put it on a container and launch: 77 | 78 | ---- 79 | $ docker build -t transitive --no-cache . 80 | Sending build context to Docker daemon 33.61MB 81 | Step 1/5 : FROM alpine:3.6 82 | ---> 053cde6e8953 83 | Step 2/5 : COPY image /opt/modi 84 | ---> 65e025493e8a 85 | Step 3/5 : ENV JAVA_HOME=/opt/modi 86 | ---> Running in 7d4d380df3db 87 | Removing intermediate container 7d4d380df3db 88 | ---> 833f964c2860 89 | Step 4/5 : ENV PATH=$PATH:$JAVA_HOME/bin 90 | ---> Running in 4af4974e9283 91 | Removing intermediate container 4af4974e9283 92 | ---> 150a7a9f1072 93 | Step 5/5 : ENTRYPOINT [ "transitive" ] 94 | ---> Running in b2e73e9feff9 95 | Removing intermediate container b2e73e9feff9 96 | ---> eef3c9e6a6f4 97 | Successfully built eef3c9e6a6f4 98 | Successfully tagged transitive:latest 99 | $ 100 | $ # run the mudular runtime image called transitive 101 | $ 102 | $ docker run --rm transitive:latest 103 | {"age":44, "name":'Marc', "surname":'Denim', "address":'Izmir'} 104 | {"age":31, "name":'Sophie', "surname":'Fun', "address":'Adana'} 105 | {"age":25, "name":'Paul', "surname":'Doe', "address":'Izmir'} 106 | {"age":51, "name":'Paul', "surname":'Richard', "address":'Ankara'} 107 | ---- 108 | -------------------------------------------------------------------------------- /requires-transitive/dot/entrypoint/employee.entrypoint.dot: -------------------------------------------------------------------------------- 1 | digraph "employee.entrypoint" { 2 | // Path: /Users/hakan/IdeaProjects/jigsaw/requires-transitive/jar/employee.entrypoint-1.0.jar 3 | "employee.entrypoint" -> "employee.service.api (employee.service)"; 4 | "employee.entrypoint" -> "java.io (java.base)"; 5 | "employee.entrypoint" -> "java.lang (java.base)"; 6 | "employee.entrypoint" -> "java.lang.invoke (java.base)"; 7 | "employee.entrypoint" -> "java.util (java.base)"; 8 | "employee.entrypoint" -> "java.util.function (java.base)"; 9 | } 10 | -------------------------------------------------------------------------------- /requires-transitive/dot/entrypoint/employee.model.dot: -------------------------------------------------------------------------------- 1 | digraph "employee.model" { 2 | // Path: /Users/hakan/IdeaProjects/jigsaw/requires-transitive/jar/employee.model-1.0.jar 3 | "employee.model" -> "java.lang (java.base)"; 4 | "employee.model" -> "java.lang.invoke (java.base)"; 5 | } 6 | -------------------------------------------------------------------------------- /requires-transitive/dot/entrypoint/employee.service.dot: -------------------------------------------------------------------------------- 1 | digraph "employee.service" { 2 | // Path: /Users/hakan/IdeaProjects/jigsaw/requires-transitive/jar/employee.service-1.0.jar 3 | "employee.service.api" -> "employee.model (employee.model)"; 4 | "employee.service.api" -> "java.lang (java.base)"; 5 | "employee.service.api" -> "java.util (java.base)"; 6 | } 7 | -------------------------------------------------------------------------------- /requires-transitive/dot/entrypoint/summary-without-transitive.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ozlerhakan/java9-module-examples/2f8bf4d44fbb6ea5c6599cf5ad7899d6956c5451/requires-transitive/dot/entrypoint/summary-without-transitive.png -------------------------------------------------------------------------------- /requires-transitive/dot/entrypoint/summary.dot: -------------------------------------------------------------------------------- 1 | digraph "summary" { 2 | "employee.service" -> "employee.model" [label="requires"]; 3 | "employee.entrypoint" -> "employee.service" [label="requires"]; 4 | "employee.entrypoint" -> "employee.model" [color=blue,label="requires transitive"]; 5 | } 6 | -------------------------------------------------------------------------------- /requires-transitive/dot/entrypoint/summary.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ozlerhakan/java9-module-examples/2f8bf4d44fbb6ea5c6599cf5ad7899d6956c5451/requires-transitive/dot/entrypoint/summary.png -------------------------------------------------------------------------------- /requires-transitive/dot/model/employee.model.dot: -------------------------------------------------------------------------------- 1 | digraph "employee.model" { 2 | // Path: /Users/hakan/IdeaProjects/jigsaw/requires-transitive/jar/employee.model-1.0.jar 3 | "employee.model" -> "java.lang (java.base)"; 4 | "employee.model" -> "java.lang.invoke (java.base)"; 5 | } 6 | -------------------------------------------------------------------------------- /requires-transitive/dot/model/summary.dot: -------------------------------------------------------------------------------- 1 | digraph "summary" { 2 | "employee.model" -> "java.base (java.base)"; 3 | } 4 | -------------------------------------------------------------------------------- /requires-transitive/dot/service/employee.model.dot: -------------------------------------------------------------------------------- 1 | digraph "employee.model" { 2 | // Path: /Users/hakan/IdeaProjects/jigsaw/requires-transitive/jar/employee.model-1.0.jar 3 | "employee.model" -> "java.lang (java.base)"; 4 | "employee.model" -> "java.lang.invoke (java.base)"; 5 | } 6 | -------------------------------------------------------------------------------- /requires-transitive/dot/service/employee.service.dot: -------------------------------------------------------------------------------- 1 | digraph "employee.service" { 2 | // Path: /Users/hakan/IdeaProjects/jigsaw/requires-transitive/jar/employee.service-1.0.jar 3 | "employee.service.api" -> "employee.model (employee.model)"; 4 | "employee.service.api" -> "java.lang (java.base)"; 5 | "employee.service.api" -> "java.util (java.base)"; 6 | "employee.service.impl" -> "employee.model (employee.model)"; 7 | "employee.service.impl" -> "employee.service.api (employee.service)"; 8 | "employee.service.impl" -> "java.lang (java.base)"; 9 | "employee.service.impl" -> "java.lang.invoke (java.base)"; 10 | "employee.service.impl" -> "java.util (java.base)"; 11 | "employee.service.impl" -> "java.util.function (java.base)"; 12 | "employee.service.impl" -> "java.util.stream (java.base)"; 13 | } 14 | -------------------------------------------------------------------------------- /requires-transitive/dot/service/summary.dot: -------------------------------------------------------------------------------- 1 | digraph "summary" { 2 | "employee.service" -> "employee.model"; 3 | "employee.service" -> "java.base (java.base)"; 4 | "employee.model" -> "java.base (java.base)"; 5 | } 6 | -------------------------------------------------------------------------------- /requires-transitive/employee.entrypoint/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | transitive 8 | com.example.transitive 9 | 1.0 10 | 11 | 4.0.0 12 | 13 | employee.entrypoint 14 | 15 | 16 | 17 | 18 | org.apache.maven.plugins 19 | maven-jar-plugin 20 | 3.0.2 21 | 22 | 23 | 24 | employee.entrypoint.Main 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | com.example.transitive 35 | employee.service 36 | 1.0 37 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /requires-transitive/employee.entrypoint/src/main/java/employee/entrypoint/Main.java: -------------------------------------------------------------------------------- 1 | package employee.entrypoint; 2 | 3 | import employee.service.api.EmployeeService; 4 | 5 | import java.util.ServiceLoader; 6 | 7 | /** 8 | * Created by hakan on 23/11/2017 9 | */ 10 | public class Main { 11 | 12 | public static void main(String[] args) { 13 | 14 | EmployeeService employeeService = ServiceLoader.load(EmployeeService.class).findFirst().get(); 15 | 16 | employeeService.findOneBy(44).ifPresent(System.out::println); 17 | employeeService.findOneBy("Sophie").ifPresent(System.out::println); 18 | 19 | employeeService.findBy("Paul").forEach(System.out::println); 20 | employeeService.takeWhile(54).forEach(System.out::println); 21 | 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /requires-transitive/employee.entrypoint/src/main/java/module-info.java: -------------------------------------------------------------------------------- 1 | module employee.entrypoint { 2 | 3 | requires employee.service; 4 | 5 | uses employee.service.api.EmployeeService; 6 | } 7 | 8 | // uses employee.service.impl.EmployeeServiceImpl; we cannot use the implementation directly! 9 | -------------------------------------------------------------------------------- /requires-transitive/employee.model/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | transitive 8 | com.example.transitive 9 | 1.0 10 | 11 | 12 | 4.0.0 13 | employee.model 14 | 15 | -------------------------------------------------------------------------------- /requires-transitive/employee.model/src/main/java/employee/model/Employee.java: -------------------------------------------------------------------------------- 1 | package employee.model; 2 | 3 | /** 4 | * Created by hakan on 23/11/2017 5 | */ 6 | public class Employee { 7 | 8 | private int age; 9 | private String name; 10 | private String surname; 11 | private String address; 12 | 13 | public Employee(String name, String surname, int age, String address) { 14 | this.name = name; 15 | this.surname = surname; 16 | this.age = age; 17 | this.address = address; 18 | } 19 | 20 | public String getName() { 21 | return name; 22 | } 23 | 24 | public String getSurname() { 25 | return surname; 26 | } 27 | 28 | public int getAge() { 29 | return age; 30 | } 31 | 32 | public String getAddress() { 33 | return address; 34 | } 35 | 36 | @Override 37 | public String toString() { 38 | return "{" + 39 | "\"age\":" + age + 40 | ", \"name\":'" + name + '\'' + 41 | ", \"surname\":'" + surname + '\'' + 42 | ", \"address\":'" + address + '\'' + 43 | '}'; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /requires-transitive/employee.model/src/main/java/module-info.java: -------------------------------------------------------------------------------- 1 | module employee.model { 2 | 3 | exports employee.model; 4 | } -------------------------------------------------------------------------------- /requires-transitive/employee.service/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | transitive 7 | com.example.transitive 8 | 1.0 9 | 10 | 4.0.0 11 | 12 | employee.service 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | com.example.transitive 21 | employee.model 22 | 1.0 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /requires-transitive/employee.service/src/main/java/employee/service/api/EmployeeService.java: -------------------------------------------------------------------------------- 1 | package employee.service.api; 2 | 3 | import employee.model.Employee; 4 | 5 | import java.util.List; 6 | import java.util.Optional; 7 | 8 | /** 9 | * Created by hakan on 23/11/2017 10 | */ 11 | public interface EmployeeService { 12 | 13 | Optional findOneBy(String name); 14 | 15 | Optional findOneBy(int age); 16 | 17 | List findBy(String name); 18 | 19 | List takeWhile(int age); 20 | 21 | } 22 | -------------------------------------------------------------------------------- /requires-transitive/employee.service/src/main/java/employee/service/impl/EmployeeServiceImpl.java: -------------------------------------------------------------------------------- 1 | package employee.service.impl; 2 | 3 | import employee.model.Employee; 4 | import employee.service.api.EmployeeService; 5 | 6 | import java.util.List; 7 | import java.util.Optional; 8 | import java.util.function.Supplier; 9 | import java.util.stream.Collectors; 10 | 11 | /** 12 | * Created by hakan on 23/11/2017 13 | */ 14 | public class EmployeeServiceImpl implements EmployeeService { 15 | 16 | // public no-arg constructor is a must in order for Service Loader to find the service implementation 17 | 18 | private static final Supplier> orderedImmutableEmployeesByAge = () -> 19 | List.of(new Employee("Paul", "Doe", 25, "Izmir"), 20 | new Employee("Joe", "Donalt", 30, "Istanbul"), 21 | new Employee("Sophie", "Fun", 31, "Adana"), 22 | new Employee("Marc", "Denim", 44, "Izmir"), 23 | new Employee("Paul", "Richard", 51, "Ankara")); 24 | 25 | 26 | public Optional findOneBy(String name) { 27 | return orderedImmutableEmployeesByAge.get() 28 | .stream() 29 | .filter(employee -> employee.getName().equals(name)) 30 | .findFirst(); 31 | } 32 | 33 | public Optional findOneBy(int age) { 34 | return orderedImmutableEmployeesByAge.get() 35 | .stream() 36 | .filter(employee -> employee.getAge() == age) 37 | .findFirst(); 38 | } 39 | 40 | public List findBy(String name) { 41 | return orderedImmutableEmployeesByAge.get() 42 | .stream() 43 | .filter(employee -> employee.getName().equals(name)) 44 | .collect(Collectors.toList()); 45 | } 46 | 47 | public List takeWhile(int age) { 48 | return orderedImmutableEmployeesByAge.get() 49 | .stream() 50 | .takeWhile(employee -> employee.getAge() == age) 51 | .collect(Collectors.toList()); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /requires-transitive/employee.service/src/main/java/module-info.java: -------------------------------------------------------------------------------- 1 | import employee.service.impl.EmployeeServiceImpl; 2 | 3 | module employee.service { 4 | 5 | exports employee.service.api; 6 | 7 | requires transitive employee.model; 8 | 9 | provides employee.service.api.EmployeeService with EmployeeServiceImpl; 10 | } -------------------------------------------------------------------------------- /requires-transitive/jar/employee.entrypoint-1.0.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ozlerhakan/java9-module-examples/2f8bf4d44fbb6ea5c6599cf5ad7899d6956c5451/requires-transitive/jar/employee.entrypoint-1.0.jar -------------------------------------------------------------------------------- /requires-transitive/jar/employee.model-1.0.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ozlerhakan/java9-module-examples/2f8bf4d44fbb6ea5c6599cf5ad7899d6956c5451/requires-transitive/jar/employee.model-1.0.jar -------------------------------------------------------------------------------- /requires-transitive/jar/employee.service-1.0.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ozlerhakan/java9-module-examples/2f8bf4d44fbb6ea5c6599cf5ad7899d6956c5451/requires-transitive/jar/employee.service-1.0.jar -------------------------------------------------------------------------------- /requires-transitive/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | com.example.transitive 8 | transitive 9 | pom 10 | 1.0 11 | 12 | 13 | employee.model 14 | employee.service 15 | employee.entrypoint 16 | 17 | 18 | 19 | 20 | 21 | 22 | org.apache.maven.plugins 23 | maven-compiler-plugin 24 | 3.6.1 25 | 26 | 9 27 | 9 28 | 9 29 | 30 | 31 | 32 | 33 | 34 | 35 | --------------------------------------------------------------------------------