Инъекция зависимостей и JavaFX

Поскольку среда выполнения JavaFX хочет создать экземпляр моего объекта приложения и всех объектов моего контроллера, как мне вставлять зависимости в эти объекты?

Если объекты были созданы с помощью среды DI, например Spring, структура привязала бы все зависимости. Если бы я вручную создавал объекты, я бы предоставил зависимости через параметры конструктора. Но что мне делать в приложении JavaFX?

Благодарю!

Ответ 1

Вы можете указать фабрику контроллера для FXMLLoader. Контроллер-фабрика - это функция, которая сопоставляет класс контроллера с объектом (предположительно, но не обязательно, экземпляр этого класса), который будет использоваться в качестве контроллера.

Поэтому, если вы хотите, чтобы Spring создавала для вас экземпляры контроллера, это может быть просто:

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...
// ...

И теперь FXMLLoader создаст экземпляры контроллера для Class<?> c, вызвав context.getBean(c); ,

Так, например, у вас может быть конфигурация:

@Configuration
public class AppConfig {

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

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

    // ...
}

с

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(...);
    }
}

Если вы не используете рамки DI, и хотите сделать инъекцию "вручную", вы можете это сделать, но это связано с использованием довольно много размышлений. Следующее показывает, как (и даст вам представление о том, насколько уродливая работа Spring делает для вас!):

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();
// ...

а затем просто сделайте

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(...);
    }
}

Наконец, вы можете проверить afterburner.fx, который является очень легким (по самым лучшим образом) JavaFX-специфическим интерфейсом DI. (Он использует подход с привязкой к конфигурации, где вы просто сопоставляете имена файлов FXML с именами классов контроллеров и, возможно, имена файлов CSS, и все просто работает.)

Ответ 2

я использовал метод setControllerFactory класса FXMLLoader для установки фабрики контроллеров, используемой этим сериализатором.

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);

    }
}

затем используйте @component для класса контроллера

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