Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

support spring-boot package? #96

Closed
zhuwenjie11 opened this issue Dec 13, 2017 · 28 comments
Closed

support spring-boot package? #96

zhuwenjie11 opened this issue Dec 13, 2017 · 28 comments

Comments

@zhuwenjie11
Copy link

My javaFX APP is developed using spring-boot, and the project package uses spring-boot-maven-plugin, and fxlauncher doesn't seem to support this way.

@edvin
Copy link
Owner

edvin commented Dec 13, 2017

What doesn't it support? Doesn't the spring boot app expose an Application class you can point to?

@humpfle
Copy link

humpfle commented Jan 30, 2018

Hi. I´m trying to run my JavaFX-Spring Boot application using the fxlauncher, too. There are several challanges to get it running:

  1. The spring-boot-maven-plugin rewrites the packaged jar and creates an executable self containing spring jar by default. There are ways to use an external lib folder. But in my opinion it would be feasible to create the executable spring boot jar and to create the app.xml with only one dependency. The whole jar should be downloaded if there are any changes.

  2. To launch the executable jar in the spring context there is a special spring boot bootstrap class which must be used as an executable jar´s entry point. (This class calls the javaFX main() method of our application). For this reason we have to change the <app.mainClass> fxlauncher property to "org.springframework.boot.loader.JarLauncher".

The current issue: The fxlauncher can currently not handle this because it expects an javafx.application.Application type.

Error creating app class

java.lang.ClassCastException: org.springframework.boot.loader.JarLauncher cannot be cast to javafx.application.Application
        at fxlauncher.Launcher$1.lambda$createApplication$1(Launcher.java:49)
        at com.sun.javafx.application.PlatformImpl.lambda$runAndWait$174(PlatformImpl.java:326)
        at com.sun.javafx.application.PlatformImpl.lambda$null$172(PlatformImpl.java:295)
        at java.security.AccessController.doPrivileged(Native Method)
        at com.sun.javafx.application.PlatformImpl.lambda$runLater$173(PlatformImpl.java:294)
        at com.sun.glass.ui.InvokeLaterDispatcher$Future.run(InvokeLaterDispatcher.java:95)
        at com.sun.glass.ui.win.WinApplication._runLoop(Native Method)
        at com.sun.glass.ui.win.WinApplication.lambda$null$147(WinApplication.java:177)
        at java.lang.Thread.run(Unknown Source)

@humpfle
Copy link

humpfle commented Jan 30, 2018

I have just tried the following and the application is able to start!

Add spring-boot-loader dependency to FXLauncher project:

   <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-loader</artifactId>
            <version>1.5.9.RELEASE</version>
        </dependency>
    </dependencies>

Change "Laucher.java"

`private final AbstractLauncher superLauncher = new AbstractLauncher() {
@OverRide
protected Parameters getParameters() {
return Launcher.this.getParameters();
}

    @Override
    protected void updateProgress(double progress) {
        Platform.runLater(() -> uiProvider.updateProgress(progress));
    }

    @Override
    protected void createApplication(Class<JarLauncher> appClass) {
        PlatformImpl.runAndWait(() ->
        {
            try {
                app = appClass.newInstance();
            } catch (Throwable t) {
                reportError("Error creating app class", t);
            }
        });
    }`

=> It would be great if a similar implementation could be provided in a separate class, like SpringBootLauncher.java?!

@edvin
Copy link
Owner

edvin commented Jan 30, 2018

We have the headless launcher which should work with this approach :)

@humpfle
Copy link

humpfle commented Jan 30, 2018

great thank you. It works perfectly ;)

For others:
#73

@edvin
Copy link
Owner

edvin commented Jan 31, 2018

Glad to hear it :))

@humpfle
Copy link

humpfle commented Jan 31, 2018

One more comment to this topic: The headless launcher works in principle fine, but it would be nicer if the FXLauncher UI / the update progress bar could be visible. I guess therefore a new Launcher implementation would be necessary.

Furthermore for some reason some resources (i.E. css files) are not found. If i start the executable jar directly everything is fine, if i am using java -jar fxlauncher the application starts but resources are not found. I have currently no idea why, but maybe its related to the classloader?!

E.5.2 System ClassLoader
Launched applications should use Thread.getContextClassLoader() when loading classes (most libraries and frameworks will do this by default). Trying to load nested jar classes via ClassLoader.getSystemClassLoader() will fail.

https://docs.spring.io/spring-boot/docs/current/reference/html/executable-jar.html

@edvin
Copy link
Owner

edvin commented Feb 1, 2018

I think it would be quite easy to make Launcher support non-JavaFX apps. To me it looks like we'd need do the following:

  1. In createApplication, do an Application.class.isAssignableFrom(appClass) to determine if the appClass is a subclass of Application. If not, don't bother instantiating it.

  2. In launchAppFromManifest, if app == null, do this instead:

Method mainMethod = appClass.getMethod("main", String[].class);
mainMethod.invoke(null, (Object) new String[0]);

(Or additionally pass parameters)

It would probably be best to split this function into two new functions: launchConsoleApp and launchJavaFXApp or something like that.

I think it's as easy as that - do you want to try and submit a PR?

About the resources not being found: We actually do call Thread.currentThread().setContextClassLoader(classLoader) so that's a small mystery. Have you tried to look for the missing resources programmatically?

@humpfle
Copy link

humpfle commented Feb 2, 2018

Hey, thanks for your hint. I have implemented it, but currently i am receiving an IllegalStateException: "Application launch must not be called more than once"

java.lang.reflect.InvocationTargetException at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source) at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source) at java.lang.reflect.Method.invoke(Unknown Source) at fxlauncher.Launcher.lambda$launchConsoleApplication$1(Launcher.java:182) at com.sun.javafx.application.PlatformImpl.lambda$runAndWait$174(PlatformImpl.java:326) at com.sun.javafx.application.PlatformImpl.lambda$null$172(PlatformImpl.java:295) at java.security.AccessController.doPrivileged(Native Method) at com.sun.javafx.application.PlatformImpl.lambda$runLater$173(PlatformImpl.java:294) at com.sun.glass.ui.InvokeLaterDispatcher$Future.run(InvokeLaterDispatcher.java:95) at com.sun.glass.ui.win.WinApplication._runLoop(Native Method) at com.sun.glass.ui.win.WinApplication.lambda$null$147(WinApplication.java:177) at java.lang.Thread.run(Unknown Source) Caused by: java.lang.reflect.InvocationTargetException at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source) at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source) at java.lang.reflect.Method.invoke(Unknown Source) at org.springframework.boot.loader.MainMethodRunner.run(MainMethodRunner.java:48) at org.springframework.boot.loader.Launcher.launch(Launcher.java:87) at org.springframework.boot.loader.Launcher.launch(Launcher.java:50) at org.springframework.boot.loader.**JarLauncher.main**(JarLauncher.java:51) ... 13 more Caused by: java.lang.IllegalStateException: Application launch must not be called more than once at com.sun.javafx.application.LauncherImpl.launchApplication(LauncherImpl.java:162) at com.sun.javafx.application.LauncherImpl.launchApplication(LauncherImpl.java:143) at javafx.application.Application.launch(Application.java:252) **at com.myProject.MainApp.main(MainApp.java:81)** ... 21 more

It looks like that the main() method of the Spring Boot JarLauncher is invoked correctly and tries to start my JavaFX Application main Method. The message "Application launch must not be called more than once" is very confusing, but i will spend some time to analyse that problem later...

And regarding the stylesheet problem.

There is no special implementation for that. The errors are coming from the Java com.sun.javafx.css.StyleManager class. (method. loadStylesheetUnPrivileged())
2018-01-31 01:49:42,663 6821 [JavaFX Application Thread] INFO javafx.css - Could not find stylesheet: jar:file:/C:/FX/installer/bundles/FX/app/./cache/FX-1.1.0.jar!/BOOT-INF/classes!/styles/common.css
2018-01-31 01:49:42,674 6832 [JavaFX Application Thread] WARN javafx.css - CSS Error parsing jar:file:/C:/FX/target/installer/bundles/FX/app/./cache/FX-1.1.0.jar!/BOOT-INF/lib/fontawesomefx-8.9.jar!/styles/glyphs_blue.css: Could not import "glyphs.css"

The css files are available within the Jars, i´ve verified it. If i am starting the executable jar without the fxlauncher, the stylesheets are correctly loaded.

@humpfle
Copy link

humpfle commented Feb 3, 2018

The IllegalStateException: "Application launch must not be called more than once" occurs in the JavaFX main class when "launch()" from com.sun.javafx.application.LauncherImpl the is called. This method is previously called by the FXLauncher main method, too... But unfortunetly i have currently no idea how to solve it.

@edvin
Copy link
Owner

edvin commented Feb 3, 2018

JavaFX only allows you to run launch() once per JVM, so I guess you have to manually start your app from a JavaFX app class that just forwards the correct calls. Should be doable.

@humpfle
Copy link

humpfle commented Feb 4, 2018

I´m wondering why the approach with the "SpringBootLauncher" has worked....

What do you mean with "manually start your app from a JavaFX app?" Implementing another JavaFx App which executes "exec("java -jar MyApp.jar"); ?

@edvin
Copy link
Owner

edvin commented Feb 5, 2018

Do you mean why the headless launcher worked? That's because it didn't start a JavaFX Application to show the progress ui, hence you are free to do so later.

What I mean you should do, is to implement javafx.application.Application and do whatever bootstrapping Spring Boot needs there, basically what happens in your existing main function, including instantiating the real JavaFX App class and passing on the init(), start(), stop() functions and so on.

@humpfle
Copy link

humpfle commented Feb 11, 2018

(I thought that my previous solution worked as well / integrating spring loader dependency and invoke the JarLauncher directly... )

I´ve implemented several solutions... Each has it own problems. So, the best solution for me (for now) is using the JFXPanel together with Swing to avoid any "Application launch must not be called more than once" problems.

https://github.com/humpfle/fxlauncher/commit/8c8dfa2c3dfa3e20090bb709490cf0ac98dfba60

@edvin
Copy link
Owner

edvin commented Feb 12, 2018

I think this is based on a misunderstanding somewhere, this shouldn't be necessary, but unfortunately I don't have the time to look into it at the moment. I can merge if you supply a PR as it wouldn't hurt anything though :)

@humpfle
Copy link

humpfle commented Feb 13, 2018

I am going to do some more testing. But it would be nice if you can merge it then. (What is a PR :) ?)

@edvin
Copy link
Owner

edvin commented Feb 13, 2018

PR is a Pull Request: https://help.github.com/articles/about-pull-requests/

@humpfle
Copy link

humpfle commented Feb 13, 2018

Ok, great. Im familiar with the term "Merge Request" but it seems to be the same. So I will supply a Pull Request in the next few days. 👍

@edvin
Copy link
Owner

edvin commented Feb 13, 2018

Fantastic :)

@humpfle
Copy link

humpfle commented Feb 16, 2018

There are still other issues related to spring boot. Spring boot has its own classloader (LaunchedURLClassLoader). I am not able to get it fully worked with the fxlauncher. If i use the HeadlessLauncher, some resources are not found and some reflection logic is not working. If i´m using the SwingJFXLauncher, i got class not found exceptions..

Since with Spring boot, we have only one fat executable JAR and no other dependencies (so the app.xml contains only one entry as well), maybe it would be really better just to use the ProgressBuilder to start the jar in a new application.

@edvin
Copy link
Owner

edvin commented Feb 16, 2018

Thanks for the info. If that works, then I say go for it. I've never used/needed SpringBoot myself, since I write my desktop apps in TornadoFX, but it's good to have a more or less working solution for SpringBoot as well.

@humpfle
Copy link

humpfle commented Feb 17, 2018

Finally it works and it will work for any other executable jar as well.

Please have a look here: (Note that i´ve also deleted the previous Swing JFXPanel approach):

#99

@edvin
Copy link
Owner

edvin commented Feb 19, 2018

Very good work, that was exactly what I was hoping for :)

@edvin edvin closed this as completed Feb 19, 2018
@humpfle
Copy link

humpfle commented Feb 19, 2018

great! can you please build a new release?

@edvin
Copy link
Owner

edvin commented Feb 19, 2018

Absolutely, will release tonight!

@edvin
Copy link
Owner

edvin commented Feb 20, 2018

FXLauncher 1.0.18 is in Maven Central :)

@humpfle
Copy link

humpfle commented Feb 20, 2018

It seems that something went wrong. The changes are not present in 1.0.18. Can you please verify it? Thanks!

@edvin
Copy link
Owner

edvin commented Mar 2, 2018

That's weird, I did compile with this fix. I'll do a new release next week with another fix, will make sure it's in there then.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants