I am implementing a program where the core part is separated from the GUI and loaded at runtime as a service. I am struggling for weeks to have the implementation discovered at runtime. To try isolating the problem, I picked up a minimal ServiceLoader example from here https://reflectoring.io/service-provider-interface and inflated it into my project structure. I got the the conclusion that the javaxplugin is messing up something. The plugin is required for the GUI of my project, but is not required to run the code of the ServiceLoader. I am using version 0.0.10 of org.openjfx.javafxplugin, the last version is 0.0.13 but this causes the additional problem that the main class cannot be found anymore, so I am staying with the older version for the moment.
If the plugin is not requested in the build.gradle, the ServiceLoader code works, the implementation is loaded and the program gives the expected output. When the javaxplugin is requested in the build.gradle, the program does not work anymore.
Does anybody have a suggestion? I am really stuck because this is a JavaFX application and I need that plugin.
The project is a Gradle project with 3 subprojects (modules): the api, the implementation (core) and the application (GUI). The relevant files are here below.
API module-info.java:
module tlapi {
exports com.chesolver.spi;
exports com.chesolver;
}
The strange thing here is that if I enable the javafx pluging, the compiler rises the error
com.chesolver.spi.Library: module tlapi does not declare 'uses', which appears pretty wierd to me,since module tlapi is the api and com.chesolver.spi.Library is part of the interface contained in this module.
API build.gradle:
plugins {
id 'tubesheetlayout.java-library-conventions'
}
core module-info:
module tlcore {
requires tlapi;
}
core build.gradle:
plugins {
id 'tubesheetlayout.java-library-conventions'
id 'org.javamodularity.moduleplugin' version '1.8.9'
}
dependencies {
implementation project(':api')
}
application module-info:
module tlclient {
requires tlapi;
}
application build.gradle:
plugins {
id 'tubesheetlayout.java-application-conventions'
// *NOTICE* if uncommented the ServiceLoader code does not work
//id 'org.openjfx.javafxplugin' version '0.0.13'
//id 'org.javamodularity.moduleplugin' version '1.8.9'
}
dependencies {
implementation project(':api')
implementation project(':core')
}
application {
mainClass = "com.chesolver.library.LibraryClient"
}
Your main issue (incorrectly specifying service modules) and resources on how to fix it
For a Java platform modular application, you need to use Java Platform modular service definitions in your modules. This means using the
providesandusesstatements in yourmodule-info.java.Your code, and the service tutorial you linked, don't integrate services into the Java platform module system. The Maven module system discussed in the tutorial (and probably Gradle modules too), is something completely different.
To learn about modular services, study the "Services" sections in:
providesandusesstatements formodule-info.java.ServiceLoaderjavadoc, which should be thoroughly reviewed and understood.Baeldung provides example code for a modular service tutorial, which is simpler than what I have in this answer. But the Baeldung tutorial doesn't demonstrate binding multiple service implementations, jlinking, or using the loaded service modules from a JavaFX application, which is why I added an example for those things here.
Key Advice
My suggestion about this is: don't use the service mechanism unless you know you need it.
Example Solution
I know this question is about Gradle specifically, but I don't really use that tool. I will provide an alternate solution using Maven. Some aspects of it will carry directly over to Gradle, and others you will need to adapt.
The solution consists of a multi-module Maven project. Each of the submodules corresponds to a Java platform module. There is a parent pom.xml to specify all of the child modules and all of the child modules inherit from the parent.
The submodules involved are these:
ShapeFactoryinterface that can create shapes.ShapeFactoryimplementation that provides circles.ShapeFactoryimplementation that provides squares.ShapeFactoryservice providers and uses them to generate shapes.Select the shape factory service provider from the combo box, then click "Create Shape" and the selected provider will be used to generate a shape, which will then be displayed.
I'll post the code here, unfortunately, there is a lot of it :-(
Building and running in Idea
You can import the maven project from the root directory into the Idea IDE. The project will load as a single Idea project, with multiple Idea project modules. When you run the main
ShapeApplicationclass from the IDE, the IDE will automatically build all the modules and provide the services to your application.Building and running from the command line
To build everything, run
mvn clean installon the root of the project. To create a jlinked app change to theshape-appdirectory and runmvn javafx:jlink.The
.imlare just idea module project files, you can ignore them.Parent pom.xml
shape-service
circle-provider
square-provider
shape-app
Caveats
bind-servicesoption. Apparently you can just bind listed services rather than all, but I could not find out how to do that with the javafx-maven-plugin. You can probably get more fine-grained control using the jlink command line than the maven plugin, though that would be more painful.MODULEPATHsetting in the javafx-maven-plugin, it won't find your service modules.shape.versionproperty in the parent pom.xml and, wherever there is1.0-SNAPSHOT, replace that with the${shape-version}, then all projects will always use the same version.MANIFEST.MFfiles). I recommend only supporting the 100% modular environment unless you absolutely also have to support classpath execution.