我试图在JavaFx中使用Spring DI我有一个MainController类,它在AppConfig中作为Bean加载,然后是另一个MenuController类,它将使用MainService。但是,当被调用时,注入的服务为null。
App类:
public class App extends Application {
private static final AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfiguration.class);
@Override
public void start(Stage primaryStage) throws Exception {
MainController mainController = context.getBean(MainController.class);
Scene scene = new Scene(mainController.getView());
primaryStage.setScene(scene);
recursiveWire(context, mainController.getView());
primaryStage.show();
}
public void recursiveWire(AnnotationConfigApplicationContext context, Object root) throws Exception {
context.getAutowireCapableBeanFactory().autowireBean(root);
context.getAutowireCapableBeanFactory().initializeBean(root, null);
for (Field field : root.getClass().getDeclaredFields()) {
if (field.isAnnotationPresent(FXML.class) && !Node.class.isAssignableFrom(field.getType())) {
recursiveWire(context, field.get(root));
}
}
}
public static void main(String[] args) {
launch(args);
}
}
AppConfiguration类:
我已经尝试设置ControllerFactory,因为我看到了一些建议,但它没有改变结果。
@Configuration
public class AppConfiguration {
@Bean
@Scope("prototype")
public MainService mainService() {
return new InMemoryMainService();
}
@Bean
@Scope("prototype")
@DependsOn("mainService")
public MainController mainController() throws IOException {
return (MainController) loadController("/java/com/akos/fxml/Main.fxml");
}
@Bean
@Scope("prototype")
public MenuController menuController() throws IOException {
return (MenuController) loadController("/java/com/akos/fxml/Menu.fxml");
}
protected Object loadController(String url) throws IOException {
InputStream fxmlStream = null;
try {
fxmlStream = getClass().getResourceAsStream(url);
FXMLLoader loader = new FXMLLoader();
loader.setLocation(getClass().getResource(url));
Node view = loader.load(fxmlStream);
AbstractController controller = loader.getController();
loader.setControllerFactory(clazz -> controller);
controller.setView(view);
return controller;
} finally {
if (fxmlStream != null) {
fxmlStream.close();
}
}
}
}
MenuController类:
这是我需要访问mainService的地方,但它是null。
public class MenuController extends AbstractController implements Initializable {
...
@Inject
MainService mainService;
@Override
public void initialize(URL location, ResourceBundle resources) {
disableMenuElements();
mainService.currentProgramProperty().addListener((observable, oldValue, newValue) -> {
...
});
}
}
答案 0 :(得分:1)
问题在于事情发生的顺序。
从Spring的角度来看,当你请求bean时,它是通过在你的应用程序配置中调用menuController()
方法创建的,然后@Inject
- 注释字段被初始化(通过反射),然后它返回bean。
但是,menuController()
方法通过加载fxml文件,然后从FXMLLoader
检索控制器来创建控制器。作为initialize()
进程的一部分,FXMLLoader
调用控制器中的load()
方法。显然,这发生在menuController()
返回之前(因为它发生在loader.load()
之前);所以在Spring有机会初始化注入的字段之前调用initialize()
。
最快的修复方法可能是为服务定义一个setter方法,并在初始化时简单地调用服务上的方法:
public class MenuController extends AbstractController implements Initializable {
// ...
private MainService mainService;
@Inject
public void setMainService(MainService mainService) {
this.mainService = mainService ;
mainService.currentProgramProperty().addListener((observable, oldValue, newValue) -> {
// ...
});
}
@Override
public void initialize(URL location, ResourceBundle resources) {
disableMenuElements();
}
}
当我使用Spring来管理我的JavaFX应用程序时,我倾向于完全使用不同的方法。我只是通过FXMLLoader
告诉controllerFactory
使用Spring来实例化控制器,而不是让控制器访问视图,然后从控制器中检索视图。然后当你在load()
上调用FXMLLoader
时,它从Spring请求控制器作为bean,因此FXMLLoader
接收一个注入了所有依赖项的bean。然后,当它在控制器上调用initialize()
时,依赖关系已经存在。
所以
@Configuration
public class AppConfiguration {
@Bean
// ??? surely a service should be singleton, not prototype, scope...
@Scope("prototype")
public MainService mainService() {
return new InMemoryMainService();
}
@Bean
@Scope("prototype")
public MainController mainController() throws IOException {
return new MainController();
}
@Bean
@Scope("prototype")
public MenuController menuController() throws IOException {
return new MenuController();
}
}
你的菜单控制器和你一样:
public class MenuController extends AbstractController implements Initializable {
// ...
@Inject
MainService mainService;
@Override
public void initialize(URL location, ResourceBundle resources) {
disableMenuElements();
mainService.currentProgramProperty().addListener((observable, oldValue, newValue) -> {
// ...
});
}
}
现在你可以做到
public class App extends Application {
private static final AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfiguration.class);
@Override
public void start(Stage primaryStage) throws Exception {
// should fix name, you should not start your own pacakge names "java"
FXMLLoader loader = new FXMLLoader(getClass().getResource("/java/com/akos/fxml/Main.fxml"));
loader.setControllerFactory(context::getBean);
Scene scene = new Scene(loader.load());
primaryStage.setScene(scene);
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
请注意,您可以通过调用
获取应用程序代码中对控制器的引用MainController mainController = loader.getController();
在之后 您已拨打loader.load()
。这为您提供了FXMLLoader
创建的控制器的引用;即Spring创建的那个(因为控制器工厂指示FXMLLoader
使用Spring)。 (在我看来,你真的不需要对控制器的引用;控制器具体知道如何在视图和模型(服务)之间进行通信;如果你想从外部更改UI,你应该更新模型这样做,然后控制器将观察模型中的变化并更新视图。)
我并不完全清楚“递归布线”应该做什么。如果您通过主fxml文件中的<fx:include>
加载菜单,控制器工厂将传播到包含的fxml文件,因此MenuController
也将从spring上下文实例化,因为它将具有根据需要注入服务。如果要将其加载到其他位置,则只需在加载时设置控制器工厂,如上面的主fxml文件所示。所有这些假设您的控制器在fxml文件中使用<fx:controller>
指定,我认为必须是您的其他代码。