Question looks easy, and probably has an easy solution, but I didn't find one.
Given module A that needs module B, which in turn needs module C.
Module A should not need to know about module C. I well guess this is one major point about using modules.
How to do that? (I have found many oversimplified examples for declaring A to require B, but B never required anything else.)
The question is general, but in my example case, I have a project "Tracker" (corresponding to module A) and a project "Poppy" (corresponding to module B) which in turn depends on modules from javafx.
"Poppy" pops up a popup, and when on its own, works fine. "Tracker" has no use of javafx, except indirectly via "Poppy", but Poppy might be changed to use Swing instead of javafx, or other graphics libraries.
module-info are
module Tracker {
requires transitive Poppy;
opens tracker to pop;
}
and
module Poppy {
requires transitive javafx.graphics;
requires transitive javafx.controls;
requires transitive javafx.base;
opens pop to javafx.graphics, javafx.fxml;
exports pop;
}
Where pop is the package name for the main class pop.Poppy.
Tracker calls Poppy.popup()
with
public static void popup() {
Scene scene;
Stage st = new Stage();
VBox r2 = createRoot(null);
scene = new Scene(r2);
st.setScene(scene);
st.show();
}
The call (developing with eclipse) result in java.lang.module.FindException: Module javafx.controls not found, required by Poppy.
Other tries resulted in complaints about other javafx.thingy not found.
Eclipse does not show me any (compile) errors. Just to double-check, in Poppy I changed the method name popup() to xpopup() and got the expected error for Tracker that there is no Poppy.popup().
So Tracker is aware of Poppy (but not of javafx, or anything Poppy cares to depend on, and rightfully should not be).
Meanwhile, I suspected the imports as a possible cause, and moved them to a different class in a different package that is not exported, now the pop package is clean of any direct references to javafx but calls a static method of a different class in a different package.
No success.
Has anyone out there ever done that before, nesting three modules A, B and C such that A doesn't need to have to declare all the stuff needed by B, C and whatever C and its dependencies might be using?
What am I missing?
The answers were guessing right, it is a XYProblem, or, as I like to put it: I tried to fix the healthy leg instead of the broken one.
The answer to your general question is that you'd have:
module A {
requires B;
}
And:
module B {
requires C;
}
That sets up the modules so that module A only knows about module B and nothing about module C. I think you understand that solution, so I believe this to be somewhat of an XY Problem. You're focusing on how to define the module descriptors, but your real problem would appear to be that the --module-path is not being configured correctly at run-time.
When running your application, the command should look something like:
java --module-path <path> --module A/com.example.Main
Where <path> "contains" all needed modules. With the above, that means module A, B, and C need to be on that path. This does not mean that module A "knows about" module C. But module B requires module C, and so module C needs to be loadable. Otherwise you'll get an exception saying module C cannot be found. Again, not because module A "thinks it needs module C itself", rather that module A requires module B and module B requires module C.
This is not any different for applications that don't make use of Java Platform Module System (JPMS) modules. If project A depends on project B, and project B depends on project C, then all three projects would need to be on the classpath at run-time, regardless of project A knowing about project C. If project C was not on the classpath then the one difference is that you'd get a ClassNotFoundException when a type from project C is used for the first time, whenever that may be, instead of getting a guaranteed FindException right at the start.
There's an additional implicit question you seem to have, and that is how module B might swap out module C for module D. That depends on when you want to be able to swap the modules.
At compile/build time: This is as "simple" as rewriting module B to use module D instead of module C. You'd only be changing implementation details of module B, and so module A would not have to change at all.
At execution time: This is not possible if you have requires C in module B's descriptor. However, you could define a service and make use of the ServiceLoader mechanism. Module B would uses that service and you'd have other modules provides that service. Something like:
module B {
exports com.example.spi;
uses com.example.spi.GuiService;
}
module CProvider {
requires B;
requires C;
provides com.example.spi.GuiService with
com.example.cprovider.CGuiService;
}
module DProvider {
requires B;
requires D;
provides com.example.spi.GuiService with
com.example.dprovider.DGuiService;
}
Here you'd put either module CProvider or module DProvider on the --module-path when executing your application. If you put module CProvider on the path then module C will also need to be on the path. If you instead put module DProvider on the path then module D will also need to be on the path. Regardless, both modules A and B would still also need to be on the path.
Of course, you could put both CProvider and DProvider on the module-path. Then the logic to choose which provider implementation to use would be in the code (i.e., the choice is now made during the application's execution instead of when launching the application).
The issue isn’t your imports or the transitive keyword — it’s that the JavaFX modules themselves must still be present on the runtime module path.
Declaring
requires transitive javafx.controls;
in Poppy only means: “any module that requires Poppy can also use JavaFX types at compile time.”
It does not put javafx.controls on the module path at runtime. That’s why you get:
java.lang.module.FindException: Module javafx.controls not found
java --module-path lib/javafx:out \
--add-modules Tracker \
tracker.Main
(where lib/javafx contains the JavaFX jars).
Poppy’s module-info.java, just require JavaFX normally:module Poppy {
requires javafx.controls;
requires javafx.graphics;
requires javafx.base;
opens pop to javafx.graphics, javafx.fxml;
exports pop;
}
Tracker’s module-info.java:module Tracker {
requires Poppy;
opens tracker to Poppy;
}
Now:
Tracker can call Poppy.popup() without knowing about JavaFX.
Poppy handles its own dependencies.
Only JavaFX needs to be on the runtime module path.
transitive. That keeps the dependency graph clean.With this setup you get exactly what you want: A → B → C works, A does not need to know about C, but C must still be available at runtime.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With