如何将自定义字段的注入添加到FXMLLoader的默认ControllerFactory?

时间:2018-08-13 12:19:49

标签: java javafx reflection fxml fxmlloader

我想在控制器的initialize方法在创建时自动被调用之前在控制器中设置一些非UI字段。据我了解,这样做的方法是提供自定义ControllerFactory,因为initialize()在之后被称为 ControllerFactory返回创建的对象。我想根据this答案使用以下代码:

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 method中使用)是com.sun.reflect.misc包的一部分,由于编译失败,因此我无法使用它与error: package sun.reflect.misc does not exist

据我了解,我不能使用sun软件包,因为它不是公共API。所以问题是:我该怎么办?我找不到任何其他示例,只有带有ReflectUtil的示例,而且,我希望我的ControllerFactory遵守带有@FXML批注的JavaFX的默认工作流,所有其他DI都可以做到这一点例如Jodd Petite这样的框架?还有其他设置字段的方法吗? (除了对其进行同步并在initialize()中等待,直到从其他线程调用setter方法为止)。 Full code在github上获取上下文。

2 个答案:

答案 0 :(得分:1)

如果要通过反射创建实例,则需要使用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的公共单参数构造函数。

如果要获取 non -public构造函数,则需要使用Constructor.getDeclaredConstructor(Class...)。然后,您需要在调用Constructor上调用setAccessible(true)

如果使用Jigsaw模块(Java 9+)并且此控制器工厂代码与控制器类不在同一个模块中,则需要记住的事情。假设控制器工厂代码在模块foo中,而控制器类在模块bar中:

  • 如果将公共控制器与公共构造函数一起使用,则bar必须exports控制器类的程序包至少包含foo
  • 如果使用 non 公共控制器和/或构造函数,则必须使用opens而不是exports
  • 进行相同的操作

否则将引发异常。


1。如果使用无参数(不一定是公共)构造函数,则可以绕过getConstructor并直接调用Class.newInstance()。但是,请注意,此方法存在问题,自Java 9起已弃用。

答案 1 :(得分:0)

就个人而言,对我自己的代码使用反射是不良设计的标志。

以下是使用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();
    }
}

在两个版本中,用户仅需创建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:referencefx:copy(如果您有复制构造函数)通过​​