Different behaviour between Maven & Eclipse to lau

2020-02-02 00:42发布

I'm starting to dig into Java 11 migration for a large app (includes Java FX parts) and I need your help to understand the difference between Maven (3.5.4) on the command-line and Eclipse (2018-09 with Java11 upgrade).

I have a simple Java 11 class

import java.util.stream.Stream;

import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.stage.Stage;

public class HelloFX extends Application {

    @Override
    public void start(Stage stage) {
        String javaVersion = System.getProperty("java.version");
        String javafxVersion = System.getProperty("javafx.version");
        Label l = new Label("Hello, JavaFX " + javafxVersion + ", running on Java " + javaVersion + ".");
        Scene scene = new Scene(l, 640, 480);
        stage.setScene(scene);
        stage.show();
    }

    public static void main(String[] args) {
        Stream.of("jdk.module.path",
                "jdk.module.upgrade.path",
                "jdk.module.main",
                "jdk.module.main.class").forEach(key -> System.out.println(key + " : " + System.getProperty(key)));

        Application.launch();
    }

}

and a simple pom

<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>com.gluonhq</groupId>
    <artifactId>hellofx</artifactId>
    <version>1.0-SNAPSHOT</version>
    <dependencies>
        <dependency>
            <groupId>org.openjfx</groupId>
            <artifactId>javafx-controls</artifactId>
            <version>11</version>
        </dependency>
    </dependencies>
    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.8.0</version>
                <configuration>
                    <release>11</release>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.codehaus.mojo</groupId>
                <artifactId>exec-maven-plugin</artifactId>
                <version>1.2.1</version>
                <executions>
                    <execution>
                        <goals>
                            <goal>java</goal>
                        </goals>
                    </execution>
                </executions>
                <configuration>
                    <mainClass>HelloFX</mainClass>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>

When I run 'mvn compile exec:java' I think nothing uses the new module-path and the program displays the JavaFX panel as expected. The system out is:

jdk.module.path : null

jdk.module.upgrade.path : null

jdk.module.main : null

jdk.module.main.class : null

When ran from an Eclipse launcher, I have to add to the launcher the following vm arguments:

--module-path=${env_var:JAVAFX_PATH} --add-modules=javafx.controls

and the panel is also displayed but the output is:

jdk.module.path : C:\dev\tools\javafx-sdk-11\lib

jdk.module.upgrade.path : null

jdk.module.main : null

jdk.module.main.class : null

jdk.module.main.class : null

I cannot make it work in Eclipse as it works from the command line: I am forced to mess with the modules and module-path. If I do not add the vm parameters, I got either "Error: JavaFX runtime components are missing, and are required to run this application" or "Error occurred during initialization of boot layer java.lang.module.FindException: Module javafx.controls not found".

How can it work form the command-line without any more configuration ? To my knowledge Maven do not add automagically anything to the module path...

Any idea ? What am I missing ?

Update1: I realized that when importing the project in Eclipse "as Maven project" (which is what I always do) it results in the JRE being added in the module path (which is not the case for my classis projects). See the screenshot enter image description here

1条回答
爷、活的狠高调
2楼-- · 2020-02-02 00:57

When running from command line, if you are choosing the Maven (same works for Gradle) build system, you let the plugins do the work for you.

When you run from your IDE the main class, but not from the built-in Maven/Gradle windows, on the contrary, you are running the plain java command line options.

And these results in two different things (but with same final result of course), as you already have figured out via the properties print out.

As already covered by this answer for IntelliJ, but applies to any other IDE, or this other one for Eclipse, there are two ways of running a JavaFX 11 project, based on the use or not of the Maven/Gradle build system.

JavaFX project, without build tools

To run your JavaFX project from your IDE, you have to download the JavaFX SDK and add a library with the different javafx jars to your IDE, with a path like /Users/<user>/Downloads/javafx-sdk-11/lib/.

Now, to run that project, even if it is not modular, you have to add the path to those modules, and include the modules you are using to the VM options/arguments of the project.

Whether you run the project from your IDE or from command line, you will be running something like:

java --module-path /Users/<user>/Downloads/javafx-sdk-11/lib/ \
    --add-modules=javafx.controls org.openjfx.hellofx.HelloFX

Note that even if your project is not modular, you are still using the JavaFX modules, and since you are not using any build tool, you have to take care of downloading the SDK in the first place.

JavaFX project, build tools

If you use Maven or Gradle build tools, the first main difference is that you don't need to download the JavaFX SDK. You will include in your pom (or build.gradle file) what modules you need, and Maven/Gradle will manage to download just those modules (and dependencies) to your local .m2/.gradle repository.

When you run your main class from Maven exec:java goal you are using a plugin, and the same goes for the run task on Gradle.

At this point, it looks like when you run:

mvn compile exec:java

or

gradle run

you are not adding the above VM arguments, but the fact is that Maven/Gradle are taking care of it for you.

Gradle

In the Gradle case, this is more evident, since you have to set them in the run task:

run {
    doFirst {
        jvmArgs = [
            '--module-path', classpath.asPath,
            '--add-modules', 'javafx.controls'
        ]
    }
}

While you don't need the SDK, the classpath contains the path to your .m2 or .gradle repository where the javafx artifacts have been downloaded.

Maven

For Maven, while the pom manages the dependencies of the different javafx modules, and sets the classifier to download the platform-specific modules (see for instance /Users/<User>/.m2/repository/org/openjfx/javafx-controls/11/javafx.controls-11.pom), the plugin manages to configure the classpath and create the required options to run the project.

In short, a new class that doesn't extend Application is used to call your application class: HelloFX.main(args).

EDIT

See this answer for a more detailed explanation on why launching a JavaFX application without module-path fails. But in short:

This error comes from sun.launcher.LauncherHelper in the java.base module. The reason for this is that the Main app extends Application and has a main method. If that is the case, the LauncherHelper will check for the javafx.graphics module to be present as a named module. If that module is not present, the launch is aborted.

A more detailed explanation on how the maven plugin works without setting the module-path:

If you add debug level (default is info) when running the Maven goals, you will get more detailed information on what is going on behind the scenes.

Running mvn compile exec:java shows:

 ...
[DEBUG]   (f) mainClass = org.openjfx.hellofx.HelloFX
 ...
[DEBUG] Invoking : org.openjfx.hellofx.HelloFX.main()
 ...

And if you check the exec-maven-plugin source code, you can find at ExecJavaMojo::execute how the main method of the Application class is called from a thread.

This is exactly what allows launching an Application class from an external class that does not extend Application class, to skip the checks.

Conclusion

Is up to you to choose build tools or not, though nowadays using them is the preferred option, of course. Either way, the end result will be the same.

But it is important to understand what are the differences of those approaches, and how your IDE deals with them.

查看更多
登录 后发表回答