簡體   English   中英

如何將自定義字段的注入添加到FXMLLoader的默認ControllerFactory?

[英]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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM