依赖注入和JavaFX

Geo*_*rge 12 java dependency-injection javafx

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

如果对象由像DI一样的DI框架实例化,那么框架将连接所有依赖项.如果我手动实例化对象,我会通过构造函数参数提供依赖项.但是我在JavaFX应用程序中该怎么办?

谢谢!

Jam*_*s_D 23

您可以为其指定控制器工厂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...
// ...
Run Code Online (Sandbox Code Playgroud)

现在,FXMLLoaderClass<?> c通过调用创建控制器实例context.getBean(c);.

所以,例如,你可以有一个配置:

@Configuration
public class AppConfig {

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

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

    // ...
}
Run Code Online (Sandbox Code Playgroud)

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(...);
    }
}
Run Code Online (Sandbox Code Playgroud)

如果你没有使用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();
// ...
Run Code Online (Sandbox Code Playgroud)

然后就做

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(...);
    }
}
Run Code Online (Sandbox Code Playgroud)

最后,您可能需要查看afterburner.fx,这是一个非常轻量级(在所有最佳方式中)特定于JavaFX的DI框架.(它使用一种约定优于配置的方法,在这种方法中,您只需将FXML文件名与控制器类名称以及可选的CSS文件名匹配,并且一切正常.)