我想在控制器的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上获取上下文。
答案 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
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:reference
或fx:copy
(如果您有复制构造函数)通过