[英]How do I add injection of custom fields to the default ControllerFactory of FXMLLoader?
我想在控制器的initialize
方法在創建時自動調用之前,在控制器中設置一些非UI字段。 據我了解,這樣做的方法是提供自定義ControllerFactory
,因為在 ControllerFactory
返回創建的對象之后會調用initialize()
。 我想根據此答案使用以下代碼:
FXMLLoader loader = new FXMLLoader(mainFXML); // some .fxml file to load
loader.setControllerFactory(param -> {
Object controller = null;
try {
controller = ReflectUtil.newInstance(param); // this is default behaviour
} catch (InstantiationException | IllegalAccessException e) {
e.printStackTrace();
}
if (controller instanceof Swappable) {
((Swappable) controller).setSwapper(swapper); // this is what I want to add
}
return controller;
});
但是, ReflectUtil
類(在默認的setControllerFactory
方法中使用 )是com.sun.reflect.misc
包的一部分,我無法使用它,因為編譯失敗並error: package sun.reflect.misc does not exist
。
據我了解,我不能使用sun軟件包,因為它不是公共API。 所以問題是:我該怎么辦? 我找不到其他示例,只有那些帶有ReflectUtil的示例,好吧,我希望我的ControllerFactory
符合帶有@FXML批注的JavaFX的默認工作流,而所有其他DI框架(如Jodd Petite)都可以做到這一點, 例如? 還有其他設置字段的方法嗎? (除了對其進行同步並等待initialize()
直到從其他線程調用setter方法之外)。 github上的完整代碼用於上下文。
如果要通過反射創建實例,則需要使用Class.getConstructor(Class...)
1,然后使用Constructor.newInstance(Object...)
。
FXMLLoader loader = new FXMLLoader(/* some location */);
loader.setControllerFactory(param -> {
Object controller;
try {
controller = param.getConstructor().newInstance();
} catch (ReflectiveOperationException ex) {
throw new RuntimeException(ex);
}
if (controller instanceof Swappable) {
((Swappable) controller).setSwapper(swapper);
}
return controller;
}
此代碼要求您的控制器類具有公共的,無參數的構造函數。 如果要通過構造函數注入依賴項,則可以執行以下操作:
FXMLLoader loader = new FXMLLoader(/* some location */);
loader.setControllerFactory(param -> {
Object controller;
try {
if (Swappable.class.isAssignableFrom(param)) {
controller = param.getConstructor(Swapper.class).newInstance(swapper);
} else {
controller = param.getConstructor().newInstance();
}
} catch (ReflectiveOperationException ex) {
throw new RuntimeException(ex);
}
return controller;
}
此代碼假定Swappable
所有子類都有一個采用Swapper
的公共單參數構造函數。
如果要獲取非公共構造函數,則需要使用Constructor.getDeclaredConstructor(Class...)
。 然后,您需要在調用Constructor
之前調用setAccessible(true)
。
如果使用Jigsaw模塊(Java 9+),並且此控制器工廠代碼與控制器類不在同一模塊中,則要記住一些事情。 假設控制器工廠代碼在模塊foo
,而控制器類在模塊bar
:
bar
必須將控制器類的包exports
到至少foo
opens
而不是exports
否則將引發異常。
1.如果使用無參數(不一定是公共)構造函數,則可以繞過getConstructor
並直接調用Class.newInstance()
。 但是,請注意,此方法存在問題,並且自Java 9開始不推薦使用。
就個人而言,對我自己的代碼使用反射是不良設計的標志。
這是使用FXML
機制注入對象的用戶實例的建議。 為此,將創建一個對象,該對象描述應用程序所處的上下文。 對象用戶實體在該對象中注冊。 這對用戶施加了一些約束,使其不能實現直接接口,而要繼承抽象類,該抽象類將實現在上下文中注冊實例的邏輯。
public interface Swapper {
}
public abstract class AbstractSwapper implements Swapper {
public AbstractSwapper() {
ApplicationContext.getInstance().setSwapper(this);
}
}
public class ApplicationContext {
private static ApplicationContext instance;
private Swapper swapper;
private ApplicationContext() {
}
public synchronized static ApplicationContext getInstance() {
if(instance == null) {
instance = new ApplicationContext();
}
return instance;
}
public synchronized static Swapper swapperFactory() {
Swapper swapper = getInstance().getSwapper();
if(swapper == null) {
swapper = new AbstractSwapper() {
};
getInstance().setSwapper(swapper);
}
return swapper;
}
public Swapper getSwapper() {
return swapper;
}
public void setSwapper(Swapper swapper) {
this.swapper = swapper;
}
}
在這種情況下,可以將FXML
文件用於fx:factory
以使用ApplicationContext
注冊的交換器實例。 因此, FXMLLoader
將實例直接注入到控制器中。
<GridPane fx:controller="sample.Controller" xmlns:fx="http://javafx.com/fxml" >
<fx:define>
<ApplicationContext fx:factory="swapperFactory" fx:id="swapper"/>
</fx:define>
</GridPane>
和sample.Controller
public class Controller {
@FXML
private Swapper swapper;
}
控制器的另一種解決方案是直接使用ApplicationContext
初始化字段。 因此, swapper
字段不會綁定到FXML
文件。
public class Controller {
private Swapper swapper;
@FXML
private void initialize() {
swapper = ApplicationContext.swapperFactory();
}
}
在這兩個版本中,用戶只需要在使用FXMLLoader
之前創建AbstractSwapper
的實例FXMLLoader
。
public class Main extends Application {
@Override
public void start(Stage primaryStage) throws Exception{
AbstractSwapper s = new AbstractSwapper() {
};
Parent root = FXMLLoader.load(getClass().getResource("sample.fxml"));
primaryStage.setTitle("Hello World");
primaryStage.setScene(new Scene(root, 300, 275));
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
此外,還有一個使用FXMLLoader注入對象的選項。 在這種情況下,它通過fx:reference
或通過fx:copy
(如果您具有復制構造函數)
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.