Example example_patch
Part of the full Java 9 Jigsaw modules example suite.
|
Authors
Originally written by Martin Lehmann, Kristine Schaal and Rüdiger Grammes (cf. original repository). Migrated for Java Modules support documentation of Apache MavenTM in the course of the Maven Support & Care program by Gerd Aschemann (and other team members) as forked repository. Please add discussions, requirements, bugfixes, etc. to the fork instead of the original. |
What is this example about?
Modules in this example
-
Modules: momain, modb
-
In addition, a directory modb-patch
-
modmain has a Main class which is started in run.sh (without patch) or run-patch.sh (with patch)
Module Dependency Graph, created with DepVis
Example shows …
It is possible to patch a module (during compile- and runtime). Use cases e.g.:
-
Whithebox testing: Unit tests need access to internal classes of a package. See example_test for details.
-
Fixing a bug in a third party lib: Prior to modules, it was possible to do this by correct order on the classpath - put the patch iahead of the classes to be patched.
To patch a module, the patching code is compiled resp. started "as if" it were part of a module. The classes of the patch are added to the module (here modb), resp. overwrite existing classes inside the module if the module already contains a class with the same name. See scripts compile-patch, resp. run-patch for details on the options to use.
Note: No JavaDoc generation for this example
Maven 4 Migration
This example has been migrated to Maven 4 using a hybrid compilation approach.
Migration Challenges
Module patching presents a unique challenge for Maven migration.
- The Problem
-
Maven can compile regular modules (
modmainandmodb). But the patch sources (modb-patch/) must be compiled with--patch-moduleto compile them as if they were part of themodbmodule. Maven 4 does not have built-in support for patch source compilation (unlike its automatic--patch-modulesupport for test sources withscope="test").
Migration Approach
The Maven 4 migration uses a hybrid compilation strategy.
- Phase 1 - Maven compilation of regular modules
-
Maven 4 compiles the two regular modules (
modmainandmodb) using the Module Source Hierarchy.<sources> <source> <module>modmain</module> <directory>src/modmain/main/java</directory> </source> <source> <module>modb</module> <directory>src/modb/main/java</directory> </source> </sources> - Phase 2 - Manual compilation of patch sources
-
The
compile.shscript manually compiles the patch sources usingjavacwith--patch-module.javac --release 11 \ --patch-module modb=src/modb-patch/main/java \ --module-path target/classes \ -d patches/modb \ src/modb-patch/main/java/pkgb/B.javaKey aspects of this compilation:
-
--release 11ensures the patch is compiled for the same target as the main modules -
--patch-module modb=src/modb-patch/main/javatreats the patch directory (via symlink) as sources belonging to modulemodb -
--module-path target/classesprovides access to already-compiledmodbclasses for dependency resolution (e.g.,Helperclass) -
Patch source file accessed via symlink:
src/modb-patch/main/java→../../../../src/modb-patch -
This approach maintains full consistency with the
src/<name>/main/javadirectory pattern -
The patch JAR is created in
patchlib/modb.jar(non-modular JAR containing replacement classes)
-
Runtime Behavior
The run.sh script demonstrates three scenarios.
- Without patch
-
Module runs with original classes from
mlib/modb.jar. - With exploded patch classes
-
--patch-module modb=patches/modbapplies patch from directory. - With patch JAR
-
--patch-module modb=patchlib/modb.jarapplies patch from JAR.
All three scenarios produce output that matches the expected golden master result.
Technical Implementation
- Symbolic link structure
-
m4/src/modmain/main/java -> ../../../../src/modmain m4/src/modb/main/java -> ../../../../src/modb m4/src/modb-patch/main/java -> ../../../../src/modb-patchThese source symlinks point to the original shared source directories and are checked into Git.
The patch compilation uses
--patch-module modb=src/modb-patch/main/java, pointing directly to the symlinked patch sources. This maintains full consistency with thesrc/<name>/main/javadirectory pattern throughout them4/migration. Dependencies from the originalmodbmodule (like theHelperclass) are automatically resolved via--module-path target/classes. - Module-path access via mlib
-
Maven (4) uses maven-jar-plugin with classifiers, creating JARs with names like:
target/example_patch-m4-1.0-SNAPSHOT-modmain.jar target/example_patch-m4-1.0-SNAPSHOT-modb.jarHowever, Java’s
--module-pathworks best with simple JAR names or explicit paths. Therun.shscript createsmlibto provide module-path access using OS-specific approaches:- Unix/macOS
-
Creates symlink
mlib → target(usingln -sfn) - Windows
-
Creates directory
mlib/and copies JARs fromtarget/The Windows approach avoids
AccessDeniedExceptionissues when Java’s module system traverses symlinks that were checked out before thetarget/directory existed.
A committed symlink
m4/mlib → targetexists in Git for convenience on Unix systems. On Windows, this symlink is replaced with a directory and copied JARs at runtime byrun.sh. Both approaches provide the same functionality:--module-path mlibaccesses the Maven-built JARs. - Build artifacts
-
-
target/- Maven-built JARs with classifiers (example_patch-m4-1.0-SNAPSHOT-modmain.jar, etc.) -
target/classes/- Maven-compiled module classes -
mlib/- Symlink totarget/for module-path compatibility -
patches/modb/- Exploded patch classes -
patchlib/modb.jar- Patch JAR
-
Output
This example uses golden master testing to ensure output consistency.
The expected output is compared with actual output using verify.sh.
Expected Output
I am modb, doing something with pkg.Helper
I am modb, doing something with pkg.Helper
I am doing more because I am PATCHED!
I am modb, doing something with pkg.Helper
I am doing more because I am PATCHED!