Java 9 Platform Module System (JPMS)
Since Java 9, packages can be grouped into modules. Java 9 Platform Module System (JPMS), the most important new software engineering technology in Java since its inception. Modularity is the result of?Project Jigsaw (https://openjdk.java.net/projects/jigsaw/) which helps developers at all levels be more productive as they build, maintain, and evolve software systems, especially large systems.
?
Note: I will not discuss all module aspects on this article as that would make a long article. I will only give an overview of the Java 9 Platform Module System (JPMS) and its benefits.
I will dedicate a separate article for the following.
1.?????Building, compiling, running & packaging modules
2.?????Module migration & versioning
?
What is a Module?
A set of related packages that are grouped together. Some of the packages are intended for use by code outside the module, these are exported packages. Some of the packages are internal to the module, which means they can only be used by code inside the module but not outside the module, these are concealed packages. So, a module can be described as a unit of reuse. A project build on modules is more reliable than a non-modular application that is built on loose set of packages that can access each other freely and that expose too many APIs.
Before I set the ball rolling, let me enumerate all the benefits of modules. If you are not familiar to modules, some of these benefits will make sense after you complete the article.
Benefits of modules
1.?????Reliable configurations or dependencies
Compile and runtime dependency resolution is made easy, because each module knows its dependencies in advance.
2.?????Strong encapsulation
Limiting package access to specific modules.
3.?????Scalable Java platform
Previous monolith consisting of massive packages making it difficult to maintain, evolve the platform. Code is uninviting. But now the JDK is modularized into 95 modules
4.?????Platform integrity
It was possible before java 9 to use classes in the platform that were not meant for use by outside classes. With Java 9, some internal APIs are truly encapsulated and now hidden from outside use. This causes legacy code, or code using these internal APIs to fail.
5.?????Improved performance
The compiler does not need to load every other class because it knows what your module depends on. This makes load time quicker. Because we know which module exports which package, there is no need to scan every other module in the system image or module path when loading a class.
6.?????Custom Runtime Images / builds
Besides compile-time & runtime, the module system adds the notion of “link time”, which is an optional phase between the two in which a set of modules can be assembled and optimized into custom runtime images. Benefit of this is the size of the resulting application is reduced because only those classes that are actually used are included in the application.
7.?????No cyclic dependencies
Modules don’t depend on each other in a cycle. Cyclic dependency is a design smell and are prohibited in modular JDk. You might say, nah, this is dependency management which maven can do right? However, maven only work at compile time but resolution in modular JDK happens at runtime as well. If code in one module imports a package, then that package is exported from exactly one other module. This means there are no spilt packages, which happens when two jars on the class-path contains the same package. Split packages are a huge failing of the class-path mechanism because they lead to difficult debug scenarios. Because of module resolution, they are completely impossible. This is really the meaning of reliable dependencies.
In OOP the basic unit of reuse is the class. Java and other languages have an awesome mechanism for promoting reuse of a class. Reuse cab be through inheritance of state or behavior through the use of classes, abstract classes and interfaces.
?
If I ask how many packages your code contains, you probably would say 10 to 20 or 50, we are only talking about your own application. Today, no application uses a handful of packages, even a smallest micro-service would not fit in a single package. Before Java 9, the Java language failed to provide a custom accessibility when a project is bigger than a single package.
Accessibility in JDK 8
1.?????private
2.?????default (aka package private)
3.?????protected
4.?????public
?
This accessibility mechanism has a big problem that plagued Java language before Java 9. It fails to provide custom accessibility control between packages. The only way to share code between packages is by making classes public, but then you are sharing with everyone (all packages). This fails to provide an application with “package friendship”. We all can agree that a package is a way to organize classes. But how about if we want to organize packages too? Before modular JDK, there was no way to control access from one package to another. This fundamental problem let to the development of Modules.
Modular JDK
Every folder represents a module, which is a set of related packages. Let’s take a look at java.base package.
JavaBase Module
Defines the foundational APIs of the Java SE Platform. All other modules depend on this module
This is the module that contains java.lang , java.io , java.math , java.util etc.?
JDK 8 – None Modular JDK
This is how JDK 8 code is organized. We see that it’s not fine-grained.
Now, back to Modular JDK 9.
We want to open module-info.java located in java.base module. This file is called a module descriptor. It contains all the information about the module, that is what packages the module requires, the packages it exports etc. This file must reside in the root directory of your module as can be seen with java.base module below. This is a java file which also gets compiled.
Module-info rules
1.?????must reside in the root directory of your module
2.?????Must use the keyword module instead of class, interface, or enum.
3.?????Can’t have duplicate statements. E.g exporting the same package twice
4.?????It is not allowed to have circular dependencies between modules. In other words, if module A requires module B, then module B cannot also require module A. The module dependency graph must be an acyclic graph.
5.?????Cannot be empty, at minimum, it must define the name of the module
领英推荐
Unpacking the module-info.java
Java base is the foundational or base module that all other modules depend on, this technical means, the java.base does not depend on any other module.
We can see that java.base exports the packages that every other code uses, like the java.lang. The exports keyword means all other modules that requires the java.base will automatically have access to the package following the exports keyword. So, exports java.lang; means java.lang is available for use by all other modules that explicitly requires the java.base.
You can also qualify an export. That is, you can specify to which modules should a package be accessible.
The package jdk.internal is only accessible in the following modules:
1.?????java.compiler
2.?????jdk.compiler
3.?????jdk.incubator.vector
4.?????jdk.shell
Other directives in java.base
Uses directive means java.base module uses the service. Which means java.base is a consumer of the service.
Provides directive means java.base is a service provider. Which means it provides a service which other modules can use.
java.desktop module.
Although you do not see requires java.base inside module descriptor for the java.desktop module, it requires it implicitly. This is the same as using the String class without import it with java.lang.String as the compiler implicitly add the import statements at compile time.
requires java.prefs; means java.desktop will have access to packages exported by java.prefs, remember these are only packages that were either exported to all modules(unqualified exports) or packages exported with a qualifier as seen in the previous example.
requires transitive java.datatransfer; means any module that requires java.desktop will automatically require require java.datatransfer module, which means that module in question will not need to explicitly require java.datatransfer.?
Opens
Observe the use of opens directive in java.desktop module descriptor file.
opens com.sun.java.swing.plaf.windows to jdk.jconsole; means that the java.desktop opens up its package(public types) com.sun.java.swing.plaf.windows to module jdk.jconsole for reflective access. This means the jdk.jconsole module can use reflection on the classes specified in com.sun.java.swing.plaf.windows package of the java.desktop module.
Any other module that tries to reflect on those classes would result in an error.
For those of you that have done spring boot applications, you might have come across the error below when running spring boot on JDK9+.
This means either your spring boot application or one of your dependencies is trying to reflect on classes that are not open for reflection or are meant to be used only by internal oracle classes and not meant for outside use.
Service Provider
The code below is defined in module descriptor for java.desktop
A service provider can provide multiple implementations for the service. The above means that java.desktop module is a service provider, and it has six implementations of the service. Service module is another important aspect of modular JDK. Let me dive a bit into service modules.
Service Module
Service modules let one plug an implementation that aren’t known at compile time. The implementations are discovered dynamically at runtime by a ServiceLoader. In fact, the implementation might not even exist. There are consumer modules which uses the implementations provided by the provider modules. The consumer doesn’t in anyway know provider modules; provider modules also do not know each other; they can even be provided by different vendors. Provider modules can provider multiple implementations of the service as seen with java.desktop. One of the perfect examples of this that is used mostly in the industry is the JDBC driver which has many providers such as MySQL, Oracle etc. You might be wondering how the runtime loads a JDBC driver for a specific vendor. It does it through a ServiceLoader.
?
What happens when there are multiple implementations or providers that the service loader has discovered? Remember that those providers can even come from different vendors. The basis for choosing one provider over another is application dependent not module dependent. It’s the choice the consumer module has to make by inspecting the properties of the available providers when they come back from the service loader. By properties I don’t mean reflecting over their fields, I mean calling methods of the service type on every instance of the discovered provider instances. Thanks to service modules! The consumer module should not be concerned with the identity of the provider classes. The provider classes are private details of the provider module. Oracle strongly discourages provider modules from exporting its classes, because we lose the benefit of loose coupling. We want to code to interfaces not implementations.
How does legacy code or none modular code work in harmony with modular code?
Unnamed Module
From Java 9 and forward, all Java classes must be located in a module for the Java VM to use them. But what do you do with older Java libraries where you just have the compiled classes, or a JAR file?
In Java 9 you can still use the?classpath?argument to the Java VM when running an application. On the classpath you can include all your older Java classes, just like you have done before Java 9. All classes found on the classpath will be included in what Java calls the?unnamed module.
The unnamed module?exports?all its packages. However, the classes in the unnamed module are only readable by other classes in the unnamed module - or from automatic modules (see next section). No named module can read the classes of the unnamed module.
If a package is exported by a named module, but also found in the unnamed module, the package from the named module will be used.
All classes in the unnamed module?requires?all modules found on the module path. That way, all classes in the unnamed module can read all classes exported by all the Java modules found on the module path.
Automatic Modules
What if you are modularizing your own code, but your code uses a third party library which is not yet modularized? While you can include the third party library on the classpath and thus include it in the unnamed module, your own named modules cannot use it, because named modules cannot read classes from the unnamed module.
The solution is called?automatic modules. An?automatic module?is made from a JAR file with Java classes that are not modularized, meaning the JAR file has no module descriptor. This is the case with JAR files developed with Java 8 or earlier. When you place an ordinary JAR file on the module path (not the classpath) the Java VM will convert it to an automatic module at runtime.
An automatic module requires all named modules on the module path. In other words, it can read all packages exported by all named modules in the module path.
If your application contains multiple automatic modules, each automatic module can read the classes of all other automatic modules.
An automatic module can read classes in the unnamed module. This is different from explicitly named modules (real Java modules) which cannot read classes in the unnamed module.
An automatic module exports all its packages, so all named modules on the module path can use the classes of an automatic module. Named modules still have to explicitly require the automatic module though.
The rule about not allowing split packages also counts for automatic modules. If multiple JAR files contain (and thus exports) the same Java package, then only one of these JAR files can be used as an automatic module.
We saw the use of directives, how they make it easy to control access to packages. Thanks to modular JDK.
I have put together some Java content to share knowledge that can benefit someone. Can you be kind to support the channel by subscribing? Below is the youtube link.