如何将自定义字段注入添加到 FXMLLoader 的默认 ControllerFactory?
How do I add injection of custom fields to the default ControllerFactory of FXMLLoader?
我想在创建时自动调用控制器的 initialize
方法之前在控制器中设置一些非 UI 字段。据我了解,这样做的方法是提供自定义 ControllerFactory
,因为 initialize()
在 ControllerFactory
returns 之后被调用 创建的对象。我想根据 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
class(默认使用 setControllerFactory
method)是 com.sun.reflect.misc
包的一部分,我无法使用使用,因为编译失败 error: package sun.reflect.misc does not exist
.
据我所知,我不能使用 sun 包,因为这不是 public API。所以问题是:我该怎么办?我找不到任何其他示例,只有那些带有 ReflectUtil 的示例,好吧,我希望我的 ControllerFactory
符合带有 @FXML 注释的 JavaFX 的默认工作流等等,这是否可能与其他一些 DI例如,像 Jodd Petite 这样的框架?还有其他方法可以设置字段吗? (除了同步它并在 initialize()
中等待,直到 setter 方法被其他线程调用)。
Full code 关于 github 的上下文。
就个人而言,对我自己的代码使用反射是糟糕设计的标志。
建议使用 FXML
机制注入对象的用户实例。为此,创建了一个描述应用程序工作环境的对象。对象用户实体在该对象中注册。这对用户施加了一些限制,使其不能实现直接接口,而是继承抽象 class 来实现在上下文中注册实例的逻辑。
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
中注册的swapper实例。因此,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
(如果你有复制构造函数)
如果你想通过反射创建一个实例,那么你需要使用Class.getConstructor(Class...)
1 followed by 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;
}
此代码要求您的控制器 class 具有 public 无参数构造函数。如果你想通过构造函数注入依赖项,你可以这样做:
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
的所有子 class 都有一个 public,采用 Swapper
.
的单参数构造函数
如果你想得到一个 non-public 构造函数你需要在调用它之前在 Constructor
上使用 Constructor.getDeclaredConstructor(Class...)
. Then you'd need to call setAccessible(true)
.
如果使用 Jigsaw 模块 (Java 9+) 并且此控制器工厂代码与控制器 class 不在同一个模块中,请记住几件事。假设控制器工厂代码在模块 foo
中,控制器 class 在模块 bar
:
中
- 如果使用带有 public 构造函数的 public 控制器,则
bar
必须 exports
将控制器 class' 封装到至少 foo
- 如果使用 non-public controller and/or controller 那么同样的事情一定会发生但是用
opens
而不是 exports
否则会抛出异常
1.如果使用无参数(不一定是 public)构造函数,您可以绕过 getConstructor
并直接调用 Class.newInstance()
。但是,请注意此方法存在问题,自 Java 9.
以来已被弃用
我想在创建时自动调用控制器的 initialize
方法之前在控制器中设置一些非 UI 字段。据我了解,这样做的方法是提供自定义 ControllerFactory
,因为 initialize()
在 ControllerFactory
returns 之后被调用 创建的对象。我想根据 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
class(默认使用 setControllerFactory
method)是 com.sun.reflect.misc
包的一部分,我无法使用使用,因为编译失败 error: package sun.reflect.misc does not exist
.
据我所知,我不能使用 sun 包,因为这不是 public API。所以问题是:我该怎么办?我找不到任何其他示例,只有那些带有 ReflectUtil 的示例,好吧,我希望我的 ControllerFactory
符合带有 @FXML 注释的 JavaFX 的默认工作流等等,这是否可能与其他一些 DI例如,像 Jodd Petite 这样的框架?还有其他方法可以设置字段吗? (除了同步它并在 initialize()
中等待,直到 setter 方法被其他线程调用)。
Full code 关于 github 的上下文。
就个人而言,对我自己的代码使用反射是糟糕设计的标志。
建议使用 FXML
机制注入对象的用户实例。为此,创建了一个描述应用程序工作环境的对象。对象用户实体在该对象中注册。这对用户施加了一些限制,使其不能实现直接接口,而是继承抽象 class 来实现在上下文中注册实例的逻辑。
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
中注册的swapper实例。因此,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
(如果你有复制构造函数)
如果你想通过反射创建一个实例,那么你需要使用Class.getConstructor(Class...)
1 followed by 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;
}
此代码要求您的控制器 class 具有 public 无参数构造函数。如果你想通过构造函数注入依赖项,你可以这样做:
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
的所有子 class 都有一个 public,采用 Swapper
.
如果你想得到一个 non-public 构造函数你需要在调用它之前在 Constructor
上使用 Constructor.getDeclaredConstructor(Class...)
. Then you'd need to call setAccessible(true)
.
如果使用 Jigsaw 模块 (Java 9+) 并且此控制器工厂代码与控制器 class 不在同一个模块中,请记住几件事。假设控制器工厂代码在模块 foo
中,控制器 class 在模块 bar
:
- 如果使用带有 public 构造函数的 public 控制器,则
bar
必须exports
将控制器 class' 封装到至少foo
- 如果使用 non-public controller and/or controller 那么同样的事情一定会发生但是用
opens
而不是exports
否则会抛出异常
1.如果使用无参数(不一定是 public)构造函数,您可以绕过 getConstructor
并直接调用 Class.newInstance()
。但是,请注意此方法存在问题,自 Java 9.