I want to set some non-UI fields in the controller before the initialize
method of the controller gets called automatically upon creation. As I understand it, the way to do it is to provide custom ControllerFactory
, since initialize()
gets called after ControllerFactory
returns the created object. I wanted to use the following code as per this answer:
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;
});
However, the ReflectUtil
class (which is used in default setControllerFactory
method) is part of com.sun.reflect.misc
package, which I am not able to use, since compiling fails with error: package sun.reflect.misc does not exist
.
As I understand it, I can't use sun packages, since this is not public API. So the question is: what do I do? I can't find any other examples of this, only the ones with ReflectUtil and, well, I want my ControllerFactory
to comply with default workflow of JavaFX with @FXML annotations and all that, is this possible with some other DI framework like Jodd Petite, for example? Is there some other way to set the field? (other than to synchronize on it and wait in initialize()
until the setter method gets called from other thread).
Full code on github for context.
If you want to create an instance via reflection then you need to use 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;
}
This code requires that your controller class has a public, no-argument constructor. If you want to inject your dependencies through the constructor you could do something like:
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;
}
This code assumes that all subclasses of Swappable
have a public, single-argument constructor that takes a Swapper
.
If you want to get a non-public constructor you'll need to use Constructor.getDeclaredConstructor(Class...)
. Then you'd need to call setAccessible(true)
on the Constructor
before invoking it.
Couple things to remember if using Jigsaw modules (Java 9+) and this controller factory code is not in the same module as the controller class. Let's say the controller factory code is in module foo
and the controller class is in module bar
:
bar
must exports
the controller class' package to at least foo
opens
instead of exports
Otherwise an exception will be thrown.
1. If using a no-argument (not necessarily public) constructor you can bypass getConstructor
and call Class.newInstance()
directly. However, please note that this method has issues and has be deprecated since Java 9.