Module Graph
The structure of an application is reflected by its module graph constructed from dependencies between its modules. An important aspect of the module system is to perform module resolution at compile time and runtime to ensure that the application has a reliable configuration. This process resolves all dependencies, making sure that they are satisfied—that is, the requires directives are matched by exports directives with all types required being accessible.
The module resolution mandates that the module graph is acyclic, meaning that the graph cannot have dependency cycles where a module depends on itself, either directly or indirectly. The module system also does not allow a package to be split between different modules, primarily because this would mean searching in several modules to locate a type in a split package. Therefore, all code in a package must be in the same module.
Every module depends on the proverbial java.base module in the JDK. Its packages are available to all modules—for example, the package java.lang containing classes like Object and String. Specifying java.base in a requires directive is optional in a module declaration. If not specified explicitly, an implicit dependency on java.base is automatically applied.
Figure 19.4(a) shows the module graph from Figure 19.3 augmented by the two dependencies on java.base. The declarations of module client and module seller in Figure 19.4(b) and (c) include explicit requires directives specifying java.base. The convention is to leave the dependency on java.base implicit. Showing all implicit dependencies from all modules on java.base in a module graph can quickly make the graph look like spaghetti. Often this dependency is only shown for modules that do not depend on other modules (a.k.a. sink nodes in graph terminology).
Figure 19.4 Module Graph and Implicit Dependencies
Readability and Accessibility
A readability relationship is implied by the requires directive. Module client reads module seller, since the module declaration of client specifies the following requires directive:
requires seller; // (1)
This requires directive alone is not enough to allow code in module client to access types in packages contained in module seller. Module seller must reciprocally export the relevant packages required by module client, as in the following exports directive in the declaration of module seller:
exports com.abc.seller; // (2)
What if the code in module client wants to access the class GizmoSeller in the package com.abc.seller of module seller? In this case, this class is only accessible to module client if it is declared public in the package com.abc.seller:
public class GizmoSeller { /*…*/ } // (3)
The three conditions, as exemplified by (1), (2), and (3) above, must be satisfied for the accessibility relationship. The module system ensures, both at compile time and runtime, that these conditions are met before allowing access to code from other modules.
We see that only a public type in an exported package of a module is accessible by modules that read this module. The exported public types comprise the public API of a module.
Leave a Reply