【问题标题】:How do I add injection of custom fields to the default ControllerFactory of FXMLLoader?如何将自定义字段的注入添加到 FXMLLoader 的默认 ControllerFactory?
【发布时间】:2018-08-13 12:19:49
【问题描述】:

我想在控制器的 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 类(默认使用 setControllerFactorymethod)是 com.sun.reflect.misc 包的一部分,我无法使用它,因为使用 error: package sun.reflect.misc does not exist 编译失败。

据我了解,我不能使用 sun 包,因为这不是公共 API。所以问题是:我该怎么办?我找不到任何其他示例,只有具有 ReflectUtil 的示例,而且,我希望我的 ControllerFactory 符合带有 @FXML 注释的 JavaFX 的默认工作流程等等,这对于其他一些 DI 框架是否可行,例如例如,乔德娇小的?还有其他方法可以设置字段吗? (除了同步它并在initialize() 中等待,直到从其他线程调用setter 方法)。 Full code github 上的上下文。

【问题讨论】:

  • Object controller = param.getConstructor().newInstance()。这使用Class.getConstructor(Class...)Constructor.newInstance(Object...)
  • @Slaw 这行得通,谢谢。如果您将其发布为答案,我会接受它
  • 您真正想要注入哪些对象以及谁来创建它们?在 fxml 中,有一些机制可以创建和注入可能对您有用的对象。
  • 示例中的 @mrmcwolf Swapper 是一个抽象类,有两个重写方法(基本上是监听器),控制器将共享这些方法,并使用这些方法让主控制器知道需要切换场景。我正在为自己制作类似 DataFX 的东西,但反射较少,在后台线程中添加了缓存和加载 FXML。
  • 所以用户创建了一个 Swapper 实例并希望将其注入到控制器中?

标签: java javafx reflection fxml fxmlloader


【解决方案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

如果你想获得一个-public 构造函数,你需要使用Constructor.getDeclaredConstructor(Class...)。然后你需要在调用它之前在Constructor 上调用setAccessible(true)

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

  • 如果使用带有公共构造函数的公共控制器,那么bar 必须将exports 控制器类的包至少封装到foo
  • 如果使用 -public 控制器和/或构造函数,则必须使用 opens 而不是 exports

否则会抛出异常。


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

【讨论】:

    【解决方案2】:

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

    这是使用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 的实例。

    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(如果你有复制构造函数)

    【讨论】:

    • 我想过把它做成一个抽象类,但用户可能想从其他超类扩展他们的控制器,而 Java 不支持多重继承。另外,我不想为用户添加 甚至更多 样板代码以在 每个 他们的控制器类/fxml 文件中编写。而且我不会做任何在 FXMLLoader 中默认构造不会发生的反射。我只是在构造对象之后使用常规设置器,但在调用initialize() 之前,我只需要知道如何通过反射重新实现这个默认构造。所以,我接受了斯劳的回答。
    猜你喜欢
    • 1970-01-01
    • 2018-04-25
    • 2014-03-09
    • 1970-01-01
    • 2021-08-13
    • 2017-03-27
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多