springspring-bootjavafx-8openjfxspring-boot-jpa

Spring data jpa with java FX


I'm using Spring JPA with OpenJFX. It's this project JavaFX-weaver, simply adding spring-boot-start-data-jpa inside pom.

However my starting time of Spring JPA is 15-20s and the UI will not show until spring is initalized. When users will start the application it takes a lot of time, every time!

As a workaround i tried to create a simply java fx application without Spring (using this demo here) and then starting there in the main method the main method from spring over a button (see example bellow). That will start spring, but dependencies and properties are not laoded.

Do you know a good way to practice that case ? Every help is welcome.

Thank you

AppBootstrap (Java + OpenJFX)

public class AppBootstrap extends Application {

    @Override
    public void start(Stage primaryStage) {

        Button btn = new Button();

        // start spring jpa main method
        btn.setOnAction(event -> App.main(new String[]{""})); 

        StackPane root = new StackPane();
        root.getChildren().add(btn);

        primaryStage.setScene(new Scene(root, 300, 250));
        primaryStage.show();
    }

    public static void main(String[] args) {
        launch();
    }
}

App (Spring JPA + javafx-weaver)

@SpringBootApplication
public class App {
    public static void main(String[] args) {
        Application.launch(SpringbootJavaFxApplication.class, args);
    }
}

Solution

  • Startup of an JPA powered Application increases load time for ApplicationContext. While you can make things faster by not checking or creating a database scheme, e.g. by setting hibernate.hbm2ddl.auto=none, this is not the best option.

    It is by design that the primary stage is shown after the ApplicationContext is loaded, since it should be able to be dependency injected.

    The best practice I recommend is using a splash screen while loading the ApplicationContext. It's a bit tricky, since you have separate Threads, but roughly it looks like this:

    Create a splash window

    public class Splash {
    
        private static final int SPLASH_WIDTH = 200;
        private static final int SPLASH_HEIGHT = 200;
    
        private final Parent parent;
        private final Stage stage; 
    
        public Splash() {
            this.stage = new Stage();
            stage.setWidth(SPLASH_WIDTH);
            stage.setHeight(SPLASH_HEIGHT);
            Label progressText = new Label("Application loading ...");
            VBox splashLayout = new VBox();
            splashLayout.setAlignment(Pos.CENTER);
            splashLayout.getChildren().addAll(progressText);
            progressText.setAlignment(Pos.CENTER);
            splashLayout.setStyle(
                    "-fx-padding: 5; " +
                            "-fx-background-color: white; " +
                            "-fx-border-width:5; " +
                            "-fx-border-color: white;"
            );
            splashLayout.setEffect(new DropShadow());
            this.parent = splashLayout;
        }
    
        public void show() {
            Scene splashScene = new Scene(parent);
            stage.initStyle(StageStyle.UNDECORATED);
            final Rectangle2D bounds = Screen.getPrimary().getBounds();
            stage.setScene(splashScene);
            stage.setX(bounds.getMinX() + bounds.getWidth() / 2 - SPLASH_WIDTH / 2.0);
            stage.setY(bounds.getMinY() + bounds.getHeight() / 2 - SPLASH_HEIGHT / 2.0);
            stage.show();
        }
    
        public void hide() {
            stage.toFront();
            FadeTransition fadeSplash = new FadeTransition(Duration.seconds(0.3), parent);
            fadeSplash.setFromValue(1.0);
            fadeSplash.setToValue(0.0);
            fadeSplash.setOnFinished(actionEvent -> stage.hide());
            fadeSplash.play();
        }
    }
    
    

    Initialize Application

    public class SpringbootJavaFxApplication extends Application {
    
        private ConfigurableApplicationContext context;
    
        class ApplicationContextLoader extends Task<Void> {
    
            private final Stage primaryStage;
    
            ApplicationContextLoader(Stage primaryStage) {
                this.primaryStage = primaryStage;
            }
    
            @Override
            protected Void call() {
                ApplicationContextInitializer<GenericApplicationContext> initializer =
                        context -> {
                            context.registerBean(Application.class, () -> SpringbootJavaFxApplication.this);
                            context.registerBean(Stage.class, () -> primaryStage);
                            context.registerBean(Parameters.class,
                                    SpringbootJavaFxApplication.this::getParameters); // for demonstration, not really needed
                        };
                SpringbootJavaFxApplication.this.context = new SpringApplicationBuilder()
                        .sources(JavaFxSpringbootDemo.class)
                        .initializers(initializer)
                        .run(getParameters().getRaw().toArray(new String[0]));
    
                return null;
            }
        }
    
        @Override
        public void start(Stage primaryStage) {
            var splash = new Splash();
            splash.show();
            final ApplicationContextLoader applicationContextLoader = new ApplicationContextLoader(primaryStage);
            applicationContextLoader.stateProperty().addListener((observableValue, oldState, newState) -> {
                if (newState == Worker.State.SUCCEEDED) {
                    context.publishEvent(new StageReadyEvent(primaryStage));
                    splash.hide();
                }
            });
            new Thread(applicationContextLoader).start();
        }
    
        @Override
        public void stop() {
            this.context.close();
            Platform.exit();
        }
    }