17

Since JavaFX runtime wants to instantiate my Application object and all of my controller objects, how do I inject dependencies into these objects?

If objects were instantiated by a DI framework, like Spring, the framework would wire up all the dependencies. If I was instantiating the objects manually, I would provide the dependencies through constructor parameters. But what do I do in a JavaFX application?

Thanks!

George
  • 2,436
  • 4
  • 15
  • 30

2 Answers2

35

You can specify a controller factory for the FXMLLoader. The controller factory is a function that maps the controller class to an object (presumably, but not necessarily, an instance of that class) which will be used as the controller.

So if you want Spring to create the controller instances for you, this can be as simple as:

ApplicationContext context = ... ;

FXMLLoader loader = new FXMLLoader(getClass().getResource("path/to/fxml"));
loader.setControllerFactory(context::getBean);
Parent root = loader.load();
SomeController controller = loader.getController(); // if you need it...
// ...

And now the FXMLLoader will create controller instances for a Class<?> c by calling context.getBean(c);.

So, e.g., you could have a configuration:

@Configuration
public class AppConfig {

    @Bean
    public MyService service() {
        return new MyServiceImpl();
    }

    @Bean
    @Scope("prototype")
    public SomeController someController() {
        return new SomeController();
    }

    // ...
}

with

public class SomeController {

    // injected by FXMLLoader:
    @FXML
    private TextField someTextField ;

    // Injected by Spring:
    @Inject
    private MyService service ;

    public void initialize() {
        someTextField.setText(service.getSomeText());
    }

    // event handler:
    @FXML
    private void performAction(ActionEvent e) {
        service.doAction(...);
    }
}

If you're not using a DI framework, and you want to do the injection "by hand", you can do so, but it involves using quite a lot of reflection. The following shows how (and will give you an idea of how much ugly work Spring is doing for you!):

FXMLLoader loader = new FXMLLoader(getClass().getResource("path/to/fxml"));
MyService service = new MyServiceImpl();
loader.setControllerFactory((Class<?> type -> {
    try {
        // look for constructor taking MyService as a parameter
        for (Constructor<?> c : type.getConstructors()) {
            if (c.getParameterCount() == 1) {
                if (c.getParameterTypes()[0]==MyService.class) {
                    return c.newInstance(service);
                }
            }
        }
        // didn't find appropriate constructor, just use default constructor:
        return type.newInstance();
    } catch (Exception exc) {
        throw new RuntimeException(exc);
    }
});
Parent root = loader.load();
// ...

and then just do

public class SomeController {

    private final MyService service ;

    public SomeController(MyService service) {
        this.service = service ;
    }

    // injected by FXMLLoader:
    @FXML
    private TextField someTextField ;

    public void initialize() {
        someTextField.setText(service.getSomeText());
    }

    // event handler:
    @FXML
    private void performAction(ActionEvent e) {
        service.doAction(...);
    }
}

Finally, you might want to check out afterburner.fx, which is a very lightweight (in all the best ways) JavaFX-specific DI framework. (It uses a convention-over-configuration approach, where you just match FXML file names to controller class names, and optionally CSS file names, and everything just works.)

James_D
  • 201,275
  • 16
  • 291
  • 322
  • What is the best method to inject models that need to be shared with included FXML each having their own instance of the same model? Using singleton scope for model allows this but then after the page is no longer displayed the model class is not garage collected. So I tried using prototype but then each FXML controller would get a different model of the same type. Is there something that Is like a qualifier prototype that can be garbage collected – jpell Nov 26 '21 at 21:17
1

i used setControllerFactory methode of FXMLLoader class to Sets the controller factory used by this serializer.

public class Main extends Application {

    @Override
    public void start(Stage primaryStage) throws Exception{
        AnnotationConfigApplicationContext ctx=new AnnotationConfigApplicationContext();
        ctx.register(AppConfig.class);
        ctx.refresh();
        FXMLLoader loader = new FXMLLoader(getClass().getResource("../view/sample.fxml"));
        loader.setControllerFactory(ctx::getBean);

        Parent root = loader.load();
        Controller controller = loader.getController();
        primaryStage.setTitle("Hello World");
        primaryStage.setScene(new Scene(root, 925, 400));
        primaryStage.show();
    }

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

    }
}

then use @component to controller class

  @Component
        public class Controller {
            @Autowired
            private ItemController itemController;
            @FXML
            private TextField item;
            @FXML
            private TextField quantity;
        }