【问题标题】:Dependency Injection and JavaFX依赖注入和JavaFX
【发布时间】:2017-03-25 04:08:09
【问题描述】:

由于 JavaFX 运行时想要实例化我的 Application 对象和我的所有控制器对象,我如何将依赖项注入这些对象?

如果对象由 DI 框架(如 Spring)实例化,则该框架将连接所有依赖项。如果我手动实例化对象,我将通过构造函数参数提供依赖关系。但是我在 JavaFX 应用程序中应该做什么?

谢谢!

【问题讨论】:

    标签: java javafx dependency-injection


    【解决方案1】:

    您可以为FXMLLoader 指定controller factory。控制器工厂是将控制器类映射到将用作控制器的对象(可能但不一定是该类的实例)的函数。

    所以如果你想让 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 将通过调用context.getBean(c);Class<?> 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 文件名匹配,一切正常。)

    【讨论】:

    • 注入需要与包含的 FXML 共享的模型的最佳方法是什么,每个模型都有自己的同一模型的实例?对模型使用单例范围允许这样做,但是在页面不再显示之后,模型类不会被车库收集。所以我尝试使用原型,但随后每个 FXML 控制器都会获得相同类型的不同模型。有没有什么东西可以像限定符原型一样被垃圾回收
    【解决方案2】:

    我使用了 FXMLLoader 类的 setControllerFactory 方法来设置这个序列化器使用的控制器工厂。

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

    【讨论】: