Why Java needs modules
JPMS is the outcome of project Jigsaw, which was undertaken with the following stated aims:
- Make it easier for developers to organize large apps and libraries
- Improve the structure and security of the platform and JDK itself
- Improve app performance
- Better handle decomposition of the platform for smaller devices
It’s worth noting that the JPMS is a SE (Standard Edition) feature, and therefore effects every aspect of Java from the ground up. Having said that, the change is designed to allow most code to function without modification when moving from Java 8 to Java 9.The chief idea behind a module is to allow the collection of related packages that are visible to the module, while hiding elements from external consumers of the module. In other words, a module allows for another level of encapsulation.
JPMS: 'requires' directive
A requires module directive specifies that this module depends on another module—this relationship is called a module dependency.
Each module must explicitly state its dependencies.
When module com.mycompany.myotherpackage requires module com.mycompany.mypackage,
module com.mycompany.myotherpackage is said to read module com.mycompany.mypackage
and module com.mycompany.mypackage is read by module com.mycompany.myotherpackage.
To specify a dependency on another module, use requires, as in:
giving a module-info like this:
Now, the com.mycompany.myotherpackage module is able to access everything
that the com.mycompany.mypackage module has made available to foreign modules.
Control is left up to the com.mycompany.mypackage module to decide what can be accessed.
By default nothing's accessible.
The dependent module, in this case com.mycompany.myotherpackage,
gets all of the packages exposed by the exporting module.
An exports module directive specifies one of the module’s packages whose public types (and their nested public and protected types) should be accessible to code in all other modules.
Here a module (com.mycompany.mypackage) that can be used by other modules because it exposes some package (com.mycompany.mypackage.api) to these foreign modules.
The syntax would be:
Now this module com.mycompany.mypackage has made the com.mycompany.mypackage.api package available at the foreign modules.
In all, both the dependent and the exporting modules must express what is provided and what is consumed.
So remember to export packages, but require modules.
Remember the golden rule: EXPORT A PACKAGE, BUT REQUIRE A MODULE.
A module can expose any number of packages to foreign modules, but they must each include their own export statement.
There's also no support for comma-delimited packages that can be specified in one export statement.
So this would give a compilation error.
There's also no support for wild cards.
To specify a dependency on another module and to ensure that other modules reading your module also read that dependency—known as implied readability
=>Use requires transitive, as in:
Consider the following directive from the java.desktop module declaration:
In this case, any module that reads java.desktop also implicitly reads java.xml.
For example, if a method from the java.desktop module returns a type from the java.xml module, code in modules that read java.desktop becomes dependent on java.xml.
Without the requires transitive directive in java.desktop’s module declaration, such dependent modules will not compile unless they explicitly read java.xml.
Remember that dependencies are not transitive by default.
A transitive dependency is a transfer of dependencies to dependent modules.
One thing to keep in mind is that dependencies are only transferred to direct dependents.
Of course, you could always reuse transitive dependencies at the next level.
Transitive dependencies avoid having to repeat the dependency requirements.
An exports…to directive enables you to specify in a comma-separated list precisely which module’s or modules’ code can access the exported package—this is known as a qualified export.
Golden rule: Export packages, but require modules.
This means that exporting is always done at the package level,
so all public classes within the package are exported as one block.
You can't pick which public classes within that exported package are visible.
You may only want some of the public classes to be accessible by foreign modules.
You can always hide classes by making them package private,
but this would also make them invisible to other packages in the same module that might need them.
A Qualified export is the exporting of packages to chosen modules.
Think of them as a white listing approach to exporting.
Qualified exports allow the exporting module to choose which foreign modules are allowed to read it.
It acts as a fine-grained filter, giving individual access at the package level.
I can add as many modules to the exports as I want, separated by commas.
Transitivity still works with qualified export.
A uses module directive specifies a service used by this module—making the module a service consumer.
A service is an object of a class that implements the interface or extends the abstract class specified in the uses directive.
A provides…with module directive specifies that a module provides a service implementation—making the module a service provider.
The provides part of the directive specifies an interface or abstract class listed in a module’s uses directive
and the with part of the directive specifies the name of the service provider class that implements the interface or extends the abstract class.
Java SE service loader wires desired implementation at Runtime.
It binds service providers to consumers.
Before Java 9, reflection could be used to learn about all types in a package and all members of a type—even its private members.
Thus, nothing was truly encapsulated.
A key motivation of the module system is strong encapsulation.
By default, a type in a module is not accessible to other modules unless it’s a public type and you export its package.
You expose only the packages you want to expose.
With Java 9, this also applies to reflection.
An opens module directive of the form:
indicates that a specific package’s public types (and their nested public and protected types) are accessible to code in other modules at runtime only.
Also, all the types in the specified package (and all of the types’ members) are accessible via reflection.
Use an opens…to module directive of the form:
If all the packages in a given module should be accessible at runtime and via reflection to all other modules, use:
Java 9 is backward compatible for the most part.
This means you'll be able to take your Java 8 code, compile and run it using the Java 9 compiler.
But cohabiting modularized with unmodularized code will require special attention.
Class paths are still supported in Java 9.
You can still compile and run your older code reading classes and jars from the class
Class paths and module paths can even be combined.
This is useful when part of your code has been modularized and part of it isn't.
Imagine two directories one with Java8 code and one with Java 9.
I can compile both.
Modules will be compiled in the mods directory, while the classes in the classes directory.
Now I can run the application.
The module path is always searched first when loading classes, and if it's not found there,
the class path is searched, so classes and modules can coexist.
All non-module classes loaded from the class path are part of what is called the unnamed module.
A class which is not a member of a 'named module' is considered to be a member of a special module known as the unnamed module. The unnamed module concept is similar to the unnamed package (the default package). The unnamed module is not a real module. It can be considered as the default module which does not have a name.
All classed compiled in Java 8 and older versions, which are not yet migrated to modules, also belong to the unnamed module when run in Java 9.
Jars created from explicitly named modules contain the module info class.
A jar that was created from unmodularized code doesn't have the module info class but can still be used within JPMS.
This is because JPMS introduces the concept of an automatic module.
Automatic Modules are named modules which are automatically created for a non-modular jar. It happens when the jar is placed on the 'module path' (as dependency) of a modular application.
In other words; non modular jar files become modular (automatic module) when used by a modular application.