Home > Back-end >  how to configure javafx with springboot with more than 1 window?
how to configure javafx with springboot with more than 1 window?

Time:06-06

I have problem. This is my main:

    @SpringBootApplication
public class MyAppSpringApplication extends Application {

    public static ConfigurableApplicationContext springContext;
    private FXMLLoader fxmlLoader;


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

    @Override
    public void start(Stage stage) throws Exception {
        fxmlLoader.setLocation(getClass().getResource("/sample.fxml"));
        Parent root = fxmlLoader.load();
        stage.setTitle("Sample app");
        Scene scene = new Scene(root);
        stage.setScene(scene);
        stage.show();
    }

    @Override
    public void stop() throws Exception {
        springContext.stop();
    }

    @Override
    public void init() throws Exception {
        springContext = SpringApplication.run(MyAppSpringApplication.class);
        fxmlLoader = new FXMLLoader();
        fxmlLoader.setControllerFactory(springContext::getBean);
    }
}

And my first window (sample.fxml) with samplecontroller and sampleservice works ok. But i create another dish-builder.fxml with their contoller and service, but when i try to use my service there, it doesnt work because of null in dishbuilderservice (albo doesnt work sampleservice in that new controller). I heard that i shound also use that:

public static ConfigurableApplicationContext springContext;

but i have no idea how should i use it. Sorry for my weak knowledge and english.

     @Controller
    public class DishBuilderController implements Initializable {
    
        @Autowired
        DishBuilderService dishBuilderService;
    
        @Autowired
        SampleService sampleService;
    
        private void somefun(){
            sampleService.somefunInService(); //here sampleService and 
        every other service has null.

}

Here is the moment when i open new dishBuilder window (its in SampleController):

@FXML
    void addNoweOknoClicked(ActionEvent event) {
        try {
            Stage stage = (Stage)anchorPane.getScene().getWindow();
            FXMLLoader fxmlLoader = new FXMLLoader();
            fxmlLoader.setLocation(getClass().getResource("/dish-builder.fxml"));
            AnchorPane root = fxmlLoader.load();
            stage.setTitle("Sample app");
            Scene scene = new Scene(root);
            stage.setScene(scene);
            stage.show();
        }catch (IOException e){
            e.printStackTrace();
        }
    }

CodePudding user response:

When you load dish-builder.fxml you are not setting the controller factory on the FXMLLoader. This means the FXMLLoader is simply creating the controller by calling its no-arg constructor. Since the controller is not a spring-managed bean, spring cannot inject any components into it.

You need to set the controller factory, as you do when you load sample.fxml, so that the FXMLLoader will ask Spring to retrieve the controller from the application context.

A couple of points that are not strictly relevant to your question:

  • There is no need to expose the ApplicationContext as a public static field. You can inject it into any spring-managed beans that need access to it
  • It is not recommended to re-use FXMLLoaders. Therefore there's no point in making the FXMLLoader an instance variable.
  • The @Controller annotation is intended for web controllers in a Spring MVC application. These are quite different to controllers in the JavaFX sense. You should use a generic @Component annotation for JavaFX controllers.
  • In the event that you were to reload an FXML file, you would need a new controller instance. This means that if the controller is managed by Spring, it needs to have PROTOTYPE scope, instead of the default SINGLETON scope.

So you need:

@SpringBootApplication
public class MyAppSpringApplication extends Application {

    private ConfigurableApplicationContext springContext;


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

    @Override
    public void start(Stage stage) throws Exception {
        FXMLLoader fxmlLoader = new FXMLLoader();
        fxmlLoader.setControllerFactory(springContext::getBean);
        fxmlLoader.setLocation(getClass().getResource("/sample.fxml"));
        Parent root = fxmlLoader.load();
        stage.setTitle("Sample app");
        Scene scene = new Scene(root);
        stage.setScene(scene);
        stage.show();
    }

    @Override
    public void stop() throws Exception {
        springContext.stop();
    }

    @Override
    public void init() throws Exception {
        springContext = SpringApplication.run(MyAppSpringApplication.class);
    }
}

Then your SampleController should look like

@Component
@Scope("prototype")
public class SampleController {

    @Autowired
    private ConfigurableApplicationContext springContext ;

    @FXML
    void addNoweOknoClicked(ActionEvent event) {
        try {
            Stage stage = (Stage)anchorPane.getScene().getWindow();
            FXMLLoader fxmlLoader = new FXMLLoader();
            fxmlLoader.setControllerFactory(springContext::getBean);
            fxmlLoader.setLocation(getClass().getResource("/dish-builder.fxml"));
            AnchorPane root = fxmlLoader.load();
            stage.setTitle("Sample app");
            Scene scene = new Scene(root);
            stage.setScene(scene);
            stage.show();
        }catch (IOException e){
            e.printStackTrace();
        }
    }
}

and similarly

@Component
@Scope("prototype")
public class DishBuilderController implements Initializable {

    @Autowired
    DishBuilderService dishBuilderService;

    @Autowired
    SampleService sampleService;

    private void somefun(){
        // this should now work, since the controller is managed by Spring:
        sampleService.somefunInService();   
    }

}
  • Related