【发布时间】:2017-03-25 04:08:09
【问题描述】:
由于 JavaFX 运行时想要实例化我的 Application 对象和我的所有控制器对象,我如何将依赖项注入这些对象?
如果对象由 DI 框架(如 Spring)实例化,则该框架将连接所有依赖项。如果我手动实例化对象,我将通过构造函数参数提供依赖关系。但是我在 JavaFX 应用程序中应该做什么?
谢谢!
【问题讨论】:
标签: java javafx dependency-injection
由于 JavaFX 运行时想要实例化我的 Application 对象和我的所有控制器对象,我如何将依赖项注入这些对象?
如果对象由 DI 框架(如 Spring)实例化,则该框架将连接所有依赖项。如果我手动实例化对象,我将通过构造函数参数提供依赖关系。但是我在 JavaFX 应用程序中应该做什么?
谢谢!
【问题讨论】:
标签: java javafx dependency-injection
您可以为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 文件名匹配,一切正常。)
【讨论】:
我使用了 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;
}
【讨论】: