如何使用 Dagger 2 将服务注入 JavaFX 控制器

Seb*_*n S 9 java dependency-injection javafx dagger-2

JavaFX 本身有一些 DI 方法来允许在 XML 描述的 UI 和控制器之间进行绑定:

<Pane fx:controller="foo.bar.MyController">
  <children>
    <Label fx:id="myLabel" furtherAttribute="..." />
  </children>
</Pane>
Run Code Online (Sandbox Code Playgroud)

Java 端看起来像这样:

public class MyController implements Initializable {

    @FXML private Label myLabel;

    @Override
    public void initialize(URL url, ResourceBundle resourceBundle) {
        // FXML-fields have been injected at this point of time:
        myLabel.setText("Hello world!");
    }

}
Run Code Online (Sandbox Code Playgroud)

为此,我不能只创建 MyController 的实例。相反,我必须要求 JavaFX 为我做一些事情:

FXMLLoader loader = new FXMLLoader(MyApp.class.getResource("/fxml/myFxmlFile.fxml"), rb);
loader.load();
MyController ctrl = (MyController) loader.getController();
Run Code Online (Sandbox Code Playgroud)

到现在为止还挺好

但是,如果我想使用 Dagger 2 将一些非 FXML 依赖项注入到这个控制器类的构造函数中,我有一个问题,因为我无法控制实例化过程,如果我使用 JavaFX。

public class MyController implements Initializable {

    @FXML private Label myLabel;

    /*
    How do I make this work?

    private final SomeService myService;

    @Inject
    public MyController(SomeService myService) {
        this.myService = myService;
    }
    */

    @Override
    public void initialize(URL url, ResourceBundle resourceBundle) {
        // FXML-fields have been injected at this point of time:
        myLabel.setText("Hello world!");
    }

}
Run Code Online (Sandbox Code Playgroud)

有一个 API 看起来很有前途:loader.setControllerFactory(...);也许这是一个很好的起点。但是我对这些库没有足够的经验来知道如何解决这个问题。

Bes*_*ter 3

感谢@Sebastian_S 的映射多重绑定机制提示,我已经成功地使用将每个控制器映射到其类来进行自动控制器绑定Map<Class<?>, Provider<Object>>

在模块中,将所有控制器收集到名为“Controllers”的映射中,并具有相应的类键

@Module
public class MyModule {

    // ********************** CONTROLLERS **********************
    @Provides
    @IntoMap
    @Named("Controllers")
    @ClassKey(FirstController.class)
    static Object provideFirstController(DepA depA, DepB depB) {
        return new FirstController(depA, depB);
    }

    @Provides
    @IntoMap
    @Named("Controllers")
    @ClassKey(SecondController.class)
    static Object provideSecondController(DepA depA, DepC depC) {
        return new SecondController(depA, depC);
    }
}
Run Code Online (Sandbox Code Playgroud)

然后在 Component 中,我们可以使用它的名称来获取这个 Map 的实例。该映射的值类型应该是Provider<Object>因为我们希望每次需要时都获得一个新的控制器实例FXMLLoader

@Singleton
@Component(modules = MyModule.class)
public interface MyDiContainer {
    // ********************** CONTROLLERS **********************
    @Named("Controllers")
    Map<Class<?>, Provider<Object>> getControllers();
}
Run Code Online (Sandbox Code Playgroud)

最后,在 FXML 加载代码中,您应该设置新的 ControllerFactory

MyDiContainer myDiContainer = DaggerMyDiContainer.create()
Map<Class<?>, Provider<Object>> controllers = myDiContainer.getControllers();

FXMLLoader loader = new FXMLLoader();
loader.setControllerFactory(type -> controllers.get(type).get());
Run Code Online (Sandbox Code Playgroud)

  • 使用 JavaFX 和 Dagger 几年后,我认为这是目前的最佳实践。感谢您分享。我什至不使用“@Provides”而是使用“@Binds”来进一步简化它,就像我现在[在这个项目中]所做的那样(https://github.com/cryptomator/cryptomator/blob/4e2122a64fd33f6b0146d72bd4e2fa7e7df16d1c/main/ui /src/main/java/org/cryptomator/ui/preferences/PreferencesModule.java#L61-L84)。 (2认同)