Java 9
- JPMS (Java Platform Module System) also known as Project Jigsaw
- benefits
- modularity as a first class citizen
- designing for modularity early
- new concepts, syntax, and tools
- modular platform
- smaller footprint
- benefits
- 5 pillars of modularity
- Encapulated: stronger encapsulation
- protection of a module's internals
- Interoperable: reliable modularity
- working with other modules
- Composable: reliable modularity
- modules can be combined with other modules
- Expandable: reliable modularity
- modules can be scaled up
- Autonomous: decomposable
- modules work independently of other modules
- Encapulated: stronger encapsulation
What it is trying to solve
Java 8
limited in terms of expressing modularity beyond a single package
once a class is public, there is no further way to control which classes in other packages can see it
- its all or nothing
path of the class that is stored on disk is intertwined with the package hierarchy
- means that how you organize the package hierarchies must be aligned with the accessibility that you want to assign to your class
example
1 package example works fine
- all packages are inside the movement package
multiple packages example will not compile
Java 9 improvements
class ccessibility
- JPMS offers better controls to expose classes
classpath hell
- JPMS maintains class integrity
- can be verified by static analysis
- support for versioning
controlling the system footprint
- a large chunk of the JDK libraries were delivered as 1 big JAR file
rt.jar
- by java 8, it had grown to over 66 MB of line code
- typical apps will not use all 66 MB, so why make all of it available at runtime
- thus the
rt.jar
has been modularized- libraries that used to eist in
rt.jar
have now been segmented into smallerJMOD
files- a new file format to package modules
- libraries that used to eist in
- a large chunk of the JDK libraries were delivered as 1 big JAR file
Module-info
- Regular java class
- Module-related syntax only
- module meta-data available at runtime
- providing fidelity across phrases
- 1 module-info per module
- declared at the root of module
- mandatory for JPMS
- creates a namespace for the module
- must be globally unique, like package names
- no duplication
- modules dictate how packages are stored on file system
- module hierarchies must match filesystem hierarchies
- segregated package hierarchies
- modules could each have their own codebase and lifecycles, but is not a must
- filename
module-info.java
module module.name { // 1
exports package.name.a; // 2
exports package.name.b to other.module.name.a; // 3
requires other.module.name.b; // 4
}
Module name
- first line contains module keyword followed by module name
module.name
in given example
- Module naming convention is similar to package convention
- reversed domain notation: domainName.module
- domain name: organization.com
- module: project
- module name in module-info:
com.organization.project
- reversed domain notation: domainName.module
Module API
- The second line declares that classes from a
package.name.a
may be accessible for other modules - Module descriptor can export multiple packages, each on a separate line
Restricted API
- Line #3 declares that package
package.name.b
is accessible only forother.module.name.a
- This functionality should be use carefully, it brakes the rule that module knows only depended modules
- It also increases coupling of modules
Module dependency
- In the last line contains the information about the module dependencies
- In the provided example the
module module.name
depends onother.module.name.b
module and has access to its exported packages - dependencies are enforced at run time
- apps fail if they can't resolve all of their dependencies
Transitive dependencies
- a transfer of dependencies to dependent modules
- in simpler terms
- every package a given module requires gets automatically passed to dependent modules
- in simpler terms
- it keep dependency graphs coherent
- it is a clean way to transfer dependencies to dependent modules
- but avoid having to repeat the dependency requirements
module module.name {
requires transitive other.module.name.b; // add transitive key word
}
from
to
Qualified dependencies
exporting of packages to chosen modules
- similar to white listing approach to exporting
allow the exporting module to choose which foreign modules are allowed to read it
acts as a fine-grained filter, giving individual access at the package level
drawbacks of using qualified dependencies
- exporting modules should normally not know about which other modules are depending upon them
- qualified exports break this rule
- it should only be used in friend like contexts
- it is used because the modules are working closely with other modules to provide a functionality
- funcationalities are structured into independent modules
- but federated to act as 1 in providing that functionality
limitations to qualified dependencies
- it can't be used to grant readability to all forieng modules because they would all need to be known
- foreign modules must already exist, if not compilation will fail
qualified exporting should not be used as the default exporting tool
- but should be instead be used in special circumstances
- best used to give fine-grained access to known modules working together
problem
solution 1: changing accessibility from public to package private
- works if dependency does not require access from anywhere else
solution 2: refactor code
- is a hacky solution
Proper solution:
- change accessibility from public to package private
- use the
to
key word inmodule-info.java
file
module module.name {
exports package.name.b to other.module.name.a, other.module.name.c, ...; // use the to key, can export to multiple packages
}
Service dependencies
service provider
- has an interface where multiple types implements it
- in the
module-info.java
filemodule module.name {
exports package.name.a;;
provides package.name.a.InterfaceName with // interface
package.name.a.type.One; // class that implements interface
package.name.a.type.Two; // can provide multiple classes that implements the interface
...
}
service consumer
module module.name.b {
requires package.name.a;
uses package.name.a.InterfaceName; // only interface is specified
}service loader
- pluggable services framework
- binds service providers to consumers
- not new, but enhanced for JPMS
- does not replace dependency injection frameworks
- but can be used in apps that need to deliver functionality in a modular and interoperable way without using third party frameworks
- ideal for stand-alone jave SE apps
- example: a class that uses the service loader to obtain an instance to one of the interface implementations
import java.util.ServiceLoader;
import java.util.Optional;
import com.red30tech.chassis.api.InterfaceName;ServiceLoader<InterfaceName> serviceLoader = ServiceLoader.load(InterfaceName.class);
System.out.println("Found " + serviceLoader.stream().count() + " interface name configured"); // count depends on number of classes implemented with interface that are provided in the modular-info.java file
Optional<InterfaceName> optional = serviceLoader.findFirst();
optional.orElseThrow(() -> new RuntimeException("No service providers found"));
InterfaceName interfaceName = optional.get();
Optional dependencies
- mandatory at compilation time, but optional at run time
- the interface can now add an option to enable or disable the implementation of the optional dependencies
- optional dependencies must be coded defensively
- must use with
try/catch
and withNoClassDefFoundError
exception
- must use with
- optinal modules become regular modules if they get required by other modules in the graph
moduel module.name {
requires package.name.b;
requires static package.name.c; // use the static keyword to make it optional
exports package.name.a;
provides package.name.a.InterfaceName with
package.name.a.type.One;
package.name.a.type.Two;
}
try {
OptionalDependencyName a = new OptionalDependencyName();
} catch (NoClassDefFoundError exception) {
a = null;
}
# run without optional dependency
java --module-path mods/ -m com.domain.module/com.domain.module.Main
# run with optional dependency
java --module-path mods/ --add-modules com.domain.optionalmodule -m com.domain.module/com.domain.module.Main
Runtime dependencies
- API misuses are caught at compilation time
- by not exporting the package
- the package won't be readable by foreign modules
- by not exporting the package
- if a dependency was not exported
- importing or instantiating the dependency will cause a compilation error
- reflection-based framework
- not importing but using the type without instantiating will allow compilation but fails at runtime
Open dependencies
- allows module access at run time only (via reflection)
- compile time access is closed
moduel module.name {
requires package.name.b;
requires static package.name.c;
exports package.name.a;
opens package.name.a.type; // use the opens keyword to allow reflection-based access at run time to all classes
provides package.name.a.InterfaceName with
package.name.a.type.One;
package.name.a.type.Two;
}
Rules of modularization
- Firstly
- cycles between modules (on compilation level) are prohibited
- It’s a limitation but no one should cry because of that
- Cycles in general are sign of a bad design
- Secondly, even if module encapsulation is controlled on compile and runtime level
- you can brake it using reflection API and freely use debug tools
- Thirdly, all modules have an implicit dependency to
java.base
module and it doesn’t have to be specified in module descriptor- The implicit dependency on
java.base
is similar to implicit import ofjava.lang.String
class
- The implicit dependency on
- Fourthly, due to backward compatibility, every class not placed in the modules goes to unnamed module
- That module has dependency to all other modules and has access to the packages which they exported
- It’s important that not exported packages are not accessible
- Since Java 9 some APIs are marked as internal and are unavailable from regular packages
- If you compile code using such packages in Java 8 and try to use it with Java 9, you’ll get runtime error
Modular structure design
- small apps may have just 1 module
- 9 tips
- Token modularization
- package non-modular classes into jars and use automatic modules
- automatic jars can be read and depended upon by modules
- gateway to modular java
- Piecemeal Modularization
- modularizing large code bases is a big undertaking
- use a piecemeal approach
- start with root packages, such as utility
- can be read by unnamed modules
- modularizing large code bases is a big undertaking
- use modularity for better design
- modularity helps detect hidden bad designs
- cyclic dependencies, lack of interfaces, and packages that try to do too much
- take the opportunity to refactor during modularization
- Break Monoliths along Natural Fault Lines
- consider module boundaries in tiers
- front-end/back-end/persistence
- mobile, web, and desktop
- relational vs document vs graph databases
- Keep Private Things Private
- modules are the next level of lexical scope
- hide classes that shouldn't be exposed
- identify the export package
- put public classes there
- leads to better APIs
- OSGi Status Quo
- future of OSGi integration is not clear
- JPMS offers native modularity
- hold off JPMS until there is clarity with OSGi integration
- Complement Microservices with JPMS
- introduce JPMS to your microservices architecture
- JPMS will provide better encapsulation and the next level of lexical scoping
- better code hygiene
- focus on larger services
- Use the Tools to Deliver Software
- introduce the static analysis tools in your build process
- Jdeps, java -dry-run, jdeprscan
- used to produce metrics and quality software
- All Things in Moderation
- don't over-modularize
- over-modularized code is worse than a monolith
- understand the dependencies in your package
- Token modularization
JPMS introduces the module path
it tells the compiler and runtime where to find the modules
directory hierarchy must match module/package hierarchy
- each module is a separate hierarchy
the module path supersedes the class path
- the class path is for backward compatibility
the module path can aggregate many modules
- each module can be its own island of code
1 modular structure
multi modular structure
Tools and Strategies
javac
javac -d ./mods/ --module-source-path src $(find src -name "*.java")
javac -d ./mods/ --module com.domain.module --module-source-path src
javac -d ./mods/ --module-path mods --module com.domain.module --module-source-path src
- build with version
javac -d ./mods/ --module-source-path src --module-version 123.01 $(find src -name "*.java")
Jar
a jar file that contains module-info at its root
a module is 1 to 1 with a jar file
build
rm -rf bin
mkdir bin
javac -d ./mods/ --module-source-path src $(find src -name "*.java")
find src -name "*.java"
jar --create --file ./bin/com.domain.modulea.jar -C mods/com.domain.modulea .
jar --create --file ./bin/com.domain.moduleb.jar -C mods/com.domain.moduleb .build with versioning
rm -rf bin
mkdir bin
javac -d ./mods/ --module-source-path src $(find src -name "*.java")
find src -name "*.java"
jar --create --file ./bin/com.domain.modulea.jar --module-version=123.02 -C mods/com.domain.modulea .
jar --create --file ./bin/com.domain.moduleb.jar -C mods/com.domain.moduleb .run
java --module-path bin -m com.domain.modulea/com.domain.modulea.ClassName
Dependency checking tools
- describe module
- describe modules used and their dependencies without running the program
java --module-path mods/ --describe-module com.domain.modulea
- describe modules used and their dependencies without running the program
- list modules
- list all of the observable modules
- observable modules are modules that are available at run time
- but not necessarily the ones used by the application
java --module-path mods/ --list-modules com.domain.modulea
- list all of the observable modules
- show module resolution
- shows how modules are resolved before running the application
- will include both the JDK library modules and the application modules
- will also run the application
java --module-path mods/ --show-module-resolution -m com.domain.modulea/com.domain.modulea.ClassName
- dry run
- to make sure application will resolve all dependencies without actually running the app
- an error will occur if module does not exist
java --module-path mods/ --dry-run -m com.domain.modulea/com.domain.modulea.ClassName
- upgrade version at run time
- build first version
javac -d ./mods/ --module-version 123.01 --module-source-path src $(find src -name "*.java")
- build second version
javac -d ./mods2/ --module-version 123.02 --module com.domain.modulea --module-source-path src
- run with upgraded version
java --upgrade-module-path mods2 --module-path mods -m com.domain.modulea/com.domain.modulea.ClassName
- build first version
Jdeps
- a class dependency analyzer tool
- check dependencies
- prints the dependencies between each module
jdeps --module-path mods/ mods/com.domain.modulea
- prints the dependencies between each module
- list jdeps
- print a summarized list of dependencies
jdeps --list-deps --module-path mods/ mods/com.domain.modulea
- print a summarized list of dependencies
Module packaging tools
Jmod
a tool and a file format
it creates jmod files
similar in intent as jar files, but designed to work with
Jlink
used to build custom runtime images
pre-java9
- 1 jar file as
rt.jar
contains all JDK libraries - causes longer time to start
- not suitable for small apps with very short lifecycle
- 1 jar file as
build jmods
rm -rf jmods jlink
mkdir jmods
javac -d ./mods/ --module-source-path src $(find src -name "*.java")
jmod create jmods/com.domain.modulea.jmod --class-path mods/com.domain.modulea
jmod create jmods/com.domain.moduleb.jmod --class-path mods/com.domain.moduleblist jmod contents
jmod list jmods/com.domain.modulea.jmod
describe jmod contents
jmod describe jmods/com.domain.modulea.jmod
extract classes from jmod contents
jmod extract jmods/com.domain.modulea.jmod
Custom image building tools
Jlink
a tool to create custom runtime images
self-contained images that include the JRE
it contains everything needed to run, no pre-installing of Java Runtime on the host is required
it strips away everything from the JDK that isn't used by the app
- results in a smaller overall app distribution
build jlink from jmod
rm -rf jmods jlink
mkdir jmods
javac -d ./mods/ --module-source-path src $(find src -name "*.java")
jmod create jmods/com.domain.modulea.jmod --class-path mods/com.domain.modulea
jmod create jmods/com.domain.moduleb.jmod --class-path mods/com.domain.moduleb
jlink --module-path $JAVA_HOME/jmods:jmods --add-modules com.domain.modulea --output jlink --launcher run=com.domain.modulea/com.domain.modulea.ClassNamerun jlink
jlink/bin/run
Jmod hasing
a hash is a tag that marks interrelated Jmod files ensuring they are used together
it prevents files from different tags to be interchanged
hash jmod
rm -rf jmods jlink
mkdir jmods
javac -d ./mods/ --module-source-path src $(find src -name "*.java")
jmod create jmods/com.domain.modulea.jmod --class-path mods/com.domain.modulea
jmod create jmods/com.domain.moduleb.jmod --class-path mods/com.domain.moduleb
jmod hash --module-path jmods --hash-modules .*
jlink --module-path $JAVA_HOME/jmods:jmods --add-modules com.domain.modulea --output jlink --launcher run=com.domain.modulea/com.domain.modulea.ClassNameview hash with describe
jmod describe jmods/com.domain.modulea.jmod
Jar files vs Jmod files
jar files | jmod files |
---|---|
support modules | use for custom run time image |
use for running on a pre-installed JRE | can hold native libraries |
use for packaging on custom images |
backward compatibility with classes
Jdeprscan
- a static analysis tool that scans code for uses of deprecated API elements
- use to show every method in the standard JDK libraries that have been deprecated or slated for removal
- scan jdk libraries
jdeprscan --list --release 6
- list jdk libraries for removel
jdeprscan --list --for-removal
- scan classes
jdeprscan --class-path classes classes
Explicit vs unamed modules
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 nonmodule classes loaded from the class path are part of what is called the unnamed module
The unnamed module is a new concept created to bridge modularized and unmodularized code
Explicit modules are those defined via the module-info class
Unnamed modules are special modules created by their runtime that contain all classes that are loaded from the class path
At most, there will be only one unnamed module at runtime
Classes within the unnamed module can read any public class from an exported package of an explicit module
- But the reverse is not true
Explicit modules cannot read the classes from the unnamed module, nor can they depend upon the unnamed module
unnamed modules exist only for interoperability between modularized and unmodularized code
- This means that the axle class can access anything that the movement module exports without explicitly requiring it
if a package is defined in both the class path and the module path, then the class found in the module is loaded
These readability rules are important to preserve reliable configuration in JPMS
Otherwise, JPMS would be broken
These constructs exist for backward compatibility and not as an end state
While unnamed modules can participate in JPMS, they are second-class citizens in the world of modularity because they can't fully take advantage of all the features
build unnamed module
- src_1 has no
module-info.java
file- however, the classes inside can still import from src_2
rm -rf mods bin classes
mkdir mods bin classes
javac -d ./mods/ --module-source-path src_2 $(find src_2 -name "*.java")
javac -d ./classes/ -cp mods/com.domain.moduleb --source-path src_1 $(find src_1 -name "*.java")
jar --create --file ./bin/com.domain.moduleb.jar -C mods/com.domain.moduleb .- src_1 has no
run unnamed module
java --module-path bin --add-modules ALL-MODULE-PATH -cp classes com.domain.modulea.ClassName
backward compatibility with JARs
Automatic modules
- a jar that was created from unmodularized code doesn't have the module info class but can still be used within JPMS
- created by the platform to hold classes loaded from a non-modular jar
- classes loaded from non-modular jars are contained by the platform inside automatic modules
- it helps with the in between world of partially modularized code bases and libraries
- it offer better integration with modules
- but still is limited because they cannot depend upon explicit modules
- together with unnamed modules, they allow piecemeal migration to modules
- and avoid a big bang migration approach
- it is class exclusive
- this means that if a class exists in 2 different jar files
- only 1 jar file will be used as automatic
- the other jar will be discarded in its entirety
- this means that if a class exists in 2 different jar files
build automatic module
- src_1 has no
module-info.java
file- however, the classes inside can still import from src_2
rm -rf mods bin classes
mkdir mods bin classes
javac -d ./mods/ --module-source-path src_2 $(find src_2 -name "*.java")
javac -d ./classes/ -cp mods/com.domain.moduleb --source-path src_1 $(find src_1 -name "*.java")
jar --create --file ./bin/com.domain.moduleb.jar -C mods/com.domain.moduleb .
jar --create --file ./bin/com.domain.modulea.jar -C classes .- src_1 has no
run automatic module
java --module-path bin --add-modules ALL-MODULE-PATH com.domain.modulea.ClassName