Search code examples
javaspringspring-bootjavafx

Spring boot with javaFx


I'm working on a java desktop app using spring boot and javaFx. it's a crud app so I'm working with mysql database I want the controllers to be both spring boot and javaFx controllers, and for that I used setControllerFactory().
here is my application

Application:

package com.gi.quizui;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class Application {

    public static void main(String[] args) {

        javafx.application.Application.launch(QuizApplication.class, args);
    }
}

QuizApplication:

package com.gi.quizui;
import javafx.application.Application;
import javafx.application.Platform;
import javafx.stage.Stage;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.context.ApplicationEvent;
import org.springframework.context.ConfigurableApplicationContext;

public class QuizApplication extends Application {

    private ConfigurableApplicationContext applicationContext;

    @Override
    public void init() {
        applicationContext = new SpringApplicationBuilder(com.gi.quizui.Application.class).run();
    }

    @Override
    public void start(Stage stage) {
        applicationContext.publishEvent(new StageReadyEvent(stage));
    }

    @Override
    public void stop() {
        applicationContext.close();
        Platform.exit();
    }

    class StageReadyEvent extends ApplicationEvent {
        public StageReadyEvent(Stage stage) {
            super(stage);
        }

        public ConfigurableApplicationContext getAppContext() {

            return applicationContext;
        }

        public Stage getStage() {

            return ((Stage) getSource());
        }
    }
}

and stage initializer:

package com.gi.quizui;
import com.gi.quizui.QuizApplication.StageReadyEvent;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.stage.Stage;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.ApplicationListener;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.core.io.Resource;
import org.springframework.stereotype.Component;

import java.io.IOException;

@Component
public class StageInitializer implements ApplicationListener<StageReadyEvent> {

    @Value("classpath:fxml/quiz.fxml")
    private Resource quizResource;

    public StageInitializer(@Value("${spring.application.ui.title}") String applicationTitle) {
        this.applicationTitle = applicationTitle;
    }

    private String applicationTitle;

    @Override
    public void onApplicationEvent(StageReadyEvent event) {
        Stage stage = event.getStage();
        ConfigurableApplicationContext springContext = event.getAppContext();
        try {
            FXMLLoader fxmlLoader = new FXMLLoader(quizResource.getURL());
            fxmlLoader.setControllerFactory(springContext::getBean);
            Parent parent = fxmlLoader.load();
            stage.setScene(new Scene(parent, 800, 600));
            stage.setTitle(applicationTitle);
            stage.show();
        } catch (IOException e) {
            throw new RuntimeException();
        }
    }
}

I keep getting this error : (edit)

Exception in Application start method
2021-01-16 15:37:15.286  INFO 8652 --- [lication Thread] j.LocalContainerEntityManagerFactoryBean : Closing JPA EntityManagerFactory for persistence unit 'default'
2021-01-16 15:37:15.320  INFO 8652 --- [lication Thread] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Shutdown initiated...
2021-01-16 15:37:15.343  INFO 8652 --- [lication Thread] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Shutdown completed.
Exception in thread "main" java.lang.RuntimeException: Exception in Application start method
    at com.sun.javafx.application.LauncherImpl.launchApplication1(LauncherImpl.java:900)
    at com.sun.javafx.application.LauncherImpl.lambda$launchApplication$2(LauncherImpl.java:195)
    at java.base/java.lang.Thread.run(Thread.java:832)
Caused by: java.lang.RuntimeException: javafx.fxml.LoadException: 
/C:/Users/FacilOrdi/IdeaProjects/quizManagement/target/classes/fxml/quiz.fxml:7

    at com.gi.quizui.StageInitializer.onApplicationEvent(StageInitializer.java:40)
    at com.gi.quizui.StageInitializer.onApplicationEvent(StageInitializer.java:16)
    at org.springframework.context.event.SimpleApplicationEventMulticaster.doInvokeListener(SimpleApplicationEventMulticaster.java:172)
    at org.springframework.context.event.SimpleApplicationEventMulticaster.invokeListener(SimpleApplicationEventMulticaster.java:165)
    at org.springframework.context.event.SimpleApplicationEventMulticaster.multicastEvent(SimpleApplicationEventMulticaster.java:139)
    at org.springframework.context.support.AbstractApplicationContext.publishEvent(AbstractApplicationContext.java:426)
    at org.springframework.context.support.AbstractApplicationContext.publishEvent(AbstractApplicationContext.java:383)
    at com.gi.quizui.QuizApplication.start(QuizApplication.java:20)
    at com.sun.javafx.application.LauncherImpl.lambda$launchApplication1$9(LauncherImpl.java:846)
    at com.sun.javafx.application.PlatformImpl.lambda$runAndWait$12(PlatformImpl.java:455)
    at com.sun.javafx.application.PlatformImpl.lambda$runLater$10(PlatformImpl.java:428)
    at java.base/java.security.AccessController.doPrivileged(AccessController.java:391)
    at com.sun.javafx.application.PlatformImpl.lambda$runLater$11(PlatformImpl.java:427)
    at com.sun.glass.ui.InvokeLaterDispatcher$Future.run(InvokeLaterDispatcher.java:96)
    at com.sun.glass.ui.win.WinApplication._runLoop(Native Method)
    at com.sun.glass.ui.win.WinApplication.lambda$runLoop$3(WinApplication.java:174)
    ... 1 more
Caused by: javafx.fxml.LoadException: 
/C:/Users/FacilOrdi/IdeaProjects/quizManagement/target/classes/fxml/quiz.fxml:7

    at javafx.fxml.FXMLLoader.constructLoadException(FXMLLoader.java:2625)
    at javafx.fxml.FXMLLoader.loadImpl(FXMLLoader.java:2603)
    at javafx.fxml.FXMLLoader.loadImpl(FXMLLoader.java:2466)
    at javafx.fxml.FXMLLoader.load(FXMLLoader.java:2435)
    at com.gi.quizui.StageInitializer.onApplicationEvent(StageInitializer.java:35)
    ... 16 more
Caused by: org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'com.gi.controllers.QuizController' available
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.getBean(DefaultListableBeanFactory.java:351)
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.getBean(DefaultListableBeanFactory.java:342)
    at org.springframework.context.support.AbstractApplicationContext.getBean(AbstractApplicationContext.java:1177)
    at javafx.fxml.FXMLLoader$ValueElement.processAttribute(FXMLLoader.java:938)
    at javafx.fxml.FXMLLoader$InstanceDeclarationElement.processAttribute(FXMLLoader.java:980)
    at javafx.fxml.FXMLLoader$Element.processStartElement(FXMLLoader.java:227)
    at javafx.fxml.FXMLLoader$ValueElement.processStartElement(FXMLLoader.java:752)
    at javafx.fxml.FXMLLoader.processStartElement(FXMLLoader.java:2722)
    at javafx.fxml.FXMLLoader.loadImpl(FXMLLoader.java:2552)
    ... 19 more

controller:

package com.gi.controllers;


import org.springframework.stereotype.Component;

@Component
public class QuizController {


}

I don't understand why it is not working, I've found related questions but I don't get how it works. It would be very helpful if there are other alternatives to do this.


Solution

  • In order to be aware of classes which have annotations such as @Component, etc., Spring Boot needs to scan through part of the classpath at startup time. By default it will scan the package containing the class you pass to SpringApplicationBuilder (Application in your case) and all subpackages.

    So with the way you have things set up, it will scan com.gi.quizui and all subpackages.

    Your QuizController class is not in a sub-package of com.gi.quizui; it is in com.gi.controllers. Consequently it won't be found.

    The recommendation in the Spring Boot documentation is actually to have the Application class in the highest-level package that contains classes or resources, which is likely com.gi in your case. If you move that class to that package, it should fix the issue.

    Alternatively, you can override the default behavior using the @ComponentScan annotation:

    package com.gi.quizui;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    import org.springframework.context.annotation.ComponentScan ;
    
    @SpringBootApplication
    @ComponentScan(basePackages={"com.gi.quizui","com.gi.controllers"})
    public class Application {
    
        public static void main(String[] args) {
    
            javafx.application.Application.launch(QuizApplication.class, args);
        }
    }
    

    A typesafe alternative is to use the basePackageClasses parameter instead of basePackages:

    package com.gi.quizui;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    import org.springframework.context.annotation.ComponentScan ;
    
    import com.gi.controllers.QuizController ;
    
    @SpringBootApplication
    @ComponentScan(basePackageClasses={Application.class, QuizController.class})
    public class Application {
    
        public static void main(String[] args) {
    
            javafx.application.Application.launch(QuizApplication.class, args);
        }
    }