I have a question from a tutorial by MVP Integrating Spring Boot with JavaFX
GitHub: https://github.com/mvpjava
Youtube: https://www.youtube.com/watch?v=hjeSOxi3uPg
where there is a public method named initialization() that I don't know how it is invoked after spring boot application starts up. I looked everywhere in the classes and fxml files that it could have a reference to it, and I found none.
I want to know how this method is invoked since I also want to do some initialization of JavaFX controls. I tried using #PostConstruct but this is wrong since all beans are created before any JavaFX controls are created thus I get null pointer exception.
I will be very grateful if someone will enlighten me on this matter.
here is the class at which the public method initialization() I mentioned.
#Component
public class ConsoleTabController {
#FXML private TextArea missionOverviewText;
#FXML private ListView<String> missionsList;
#Autowired #Qualifier("stringPrintWriter")
private PrintWriter stackTraceWriter;
#Autowired MissionsService service;
private TabPaneManger tabManager;
public void initialize() {
ObservableList<String> missions = FXCollections.observableArrayList("Apollo", "Shuttle", "Skylab");
missionsList.setItems(missions);
}
#FXML
private void onMouseClicked(MouseEvent event) {
missionOverviewText.clear();
final String selectedItem = missionsList.getSelectionModel().getSelectedItem();
missionOverviewText.positionCaret(0);
missionOverviewText.appendText(getInfo(selectedItem));
}
#Autowired
private void setTabManager(TabPaneManger tabManager){
this.tabManager = tabManager;
}
public String getInfo(String selectedItem) {
String missionInfo = null ;
try {
missionInfo = service.getMissionInfo(selectedItem);
getLog().appendText("Sucessfully retrieved mission info for " + selectedItem + "\n");
} catch (IOException exception) {
exception.printStackTrace (stackTraceWriter);
getLog().appendText(stackTraceWriter.toString() + "\n");
}
return missionInfo;
}
public TextArea getMissionOverviewText() {
return missionOverviewText;
}
public ListView<String> getMissionsList() {
return missionsList;
}
private TextArea getLog(){
return tabManager.getVisualLog();
}
}
The initialize() method (which is what I think you mean) is invoked by the FXMLLoader. In general, for an FXMLLoader, the order of execution is:
FXMLLoader loads the FXML file and parses it
If the root element in the FXML file has a fx:controller attribute, it gets a reference to an instance of that class; it does this by passing the controller class to the controllerFactory, if one is set, or by calling the controller class's default constructor otherwise.
Any elements with fx:id attributes are injected into the controller fields with matching #FXML annotations
The FXMLLoader invokes the controller's initialize() method, if it has one.
In your case, I assume you are setting the controller factory of the FXMLLoader to delegate to the Spring application context, i.e. I assume you have something like
ApplicationContext appContext = ... ; // Spring bean factory
FXMLLoader loader = new FXMLLoader();
loader.setLocation(...);
loader.setControllerFactory(appContext::getBean);
Parent ui = loader.load();
This means that the controller instances will be created by passing the controller class to the Spring bean factory's getBean(...) method. So if the FXML file has fx:controller="ConsoleTabController", the FXMLLoader essentially calls
Object controller = appContext.getBean(ConsoleTabController.class);
in step 2 above. The Spring application context creates a ConsoleTabController instance (assuming you have configured the controller bean as having prototype scope, which you should), injects any #AutoWired-annotated properties, calls any #PostConstruct-annotated methods, and then provides the controller to the FXMLLoader. So the overall order of execution when you use the Spring bean factory as the controller factory is
FXMLLoader loads the FXML file and parses it
If the root element in the FXML file has a fx:controller attribute:
The Spring bean factory creates an instance of the controller class
The Spring bean factory injects any #Autowired-annotated properties into the controller instance
The Spring bean factory invokes any #PostConstruct-annotated methods on the controller instance
The controller instance is returned to the FXMLLoader
Any elements with fx:id attributes are injected into the controller fields with matching #FXML annotations by the FXMLLoader
The FXMLLoader invokes the controller's initialize() method, if it has one.
Note that there are essentially two different kind of injected fields, and two different kinds of "initialization" methods in this scenario. The "Spring injected fields", annotated #Autowired (or #Inject) are injected first, then the "Spring initialization methods" (annotated #PostConstruct) are invoked. After that, the "FXML injected fields" (annotated #FXML) are injected, and then the "FXML initialization method" (the one called initialize()) is invoked. You don't have any #PostConstruct methods in your sample code, but the one thing to be careful of is that those methods would be invoked before the #FXML-annotated fields are initialized.
Related
I have 2 dependent applications. When my child application up we update some more beans and refresh the AnnotationconfigwebapplicationContext object, but after refresh the context my MqttConnection object gets started being connected and disconnect.
I don't want to refresh my Mqttconnection object.
Please suggest how I can ignore/remove the MqttConnection object from AnnotationconfigwebapplicationContext before the refresh.
static{
AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext();
context.regirster(MqttConnection.class);
context.register(Config.class);
if(checkChildServiceup()){
Class<?>[] configClasses = getServletConfigClasses();
if (!ObjectUtils.isEmpty(configClasses)) {
this.context.register(configClasses);
}
this.context.refresh();
}
}
public static Class<?> getServletConfigClasses(){
retrun new Class[]{AppConfig.class,devService.class,DbConfiguration.class};
}
public boolean static checkChildServiceup(){
while(true){
if(up){
return true;
break;
}
}
During the refresh, the following happens in spring:
It scans all the "resources" of configurations (XMLs, java #Config files, classes annotated with #Component and its derivatives and so forth) And creates Bean Definitions which is a metadata for each bean. Note, bean definition is a meta object, its not the bean object itself.
Calls hooks called "BeanFactoryPostProcessor"-s. These allow to alter the bean definition registry - an object that aggregates all the "information" resolved during step
Initializes beans: creates, autowires, etc.
So you might be required to implement your own BeanFactoryPostProcessor that will check the property set dynamically as I see and will remove the bean definition of MqttConnection from the registry.
#Component // (or register in #Configuration annotated class):
// this must be a spring driven bean by itself,
// it resolves it but "puts aside" because it
// recognizes that its a special "hook"
public class SampleBeanFactoryPostProcessor implements BeanFactoryPostProcessor {
#Override
public void postProcessBeanFactory (
ConfigurableListableBeanFactory beanFactory)
throws BeansException {
if(<WHATEVER_PROPERTY_CHANGED__PERFORM_THE CHECK)) {
((BeanDefinitionRegistry) beanFactory).removeBeanDefinition("myBean");
}
}
}
As for actual removal of the bean definition there are many possible options here, make sure you read this SO thread and also this tutorial for examples.
I have an javafx + spring app.
The application listening serial port, reading data and shows it to UI.
The problem caused by NPE with Controller class on outputLoggerFile and serialPort on same class.
This is my configuration file with PropertySource so my environment should know about these propoperties.
SpringConfig
#Configuration
#PropertySource({"classpath:com.properties", "classpath:application.properties"})
#ComponentScan
public class SpringConfig {
#Bean
public SerialPort serialPort(#Value("${serialPort.portName}") String portName){
return new SerialPort(portName);
}
#Bean
public AnnotationMBeanExporter annotationMBeanExporter(){
AnnotationMBeanExporter annotationMBeanExporter = new AnnotationMBeanExporter();
annotationMBeanExporter.addExcludedBean("dataSource");
return annotationMBeanExporter;
}
}
This class class setting my properties to the SerialPort object, injects EventListener class and openning connection. Works fine.
ComReader
#Scope("singletone")
#Component
public class ComReader {
#Autowired
private EventListener eventListener;
#Autowired
public SerialPort serialPort;
#Value("${serialPort.baudRate}")
private int baudRate;
#Value("${serialPort.dataBits}")
private int dataBits;
#Value("${serialPort.stopBits}")
private int stopBits;
#Value("${serialPort.parity}")
private int parity;
#PostConstruct
public void init(){
try {
System.out.println("Opening port: " + serialPort.getPortName());
serialPort.openPort();
serialPort.setParams(baudRate,dataBits,stopBits,parity);
serialPort.addEventListener(eventListener, 1);
} catch (SerialPortException e) {
e.printStackTrace();
}
}
}
The problem class everything work fine except any classes/fields which i want inject here.
Controller
#org.springframework.stereotype.Controller
public class Controller {
#Value("${logger.outputFilePath}")
private String outputLoggerFile;
private SerialPort serialPort;
#Autowired
public void setSerialPort(SerialPort serialPort) {
this.serialPort = serialPort;
}
private static ObservableList<CallDetailRecord> list = FXCollections.observableArrayList();
#FXML
void initialize(){
Timer scheduler = new Timer();
scheduler.schedule(new TimerTask() {
#Override
public void run() {
if (serialPort.isOpened()) circlePortStatus.setFill(Color.GREEN); //(NPE HERE)
else circlePortStatus.setFill(Color.RED);
}
}, 5_000, 60_000);
counterCol.setCellValueFactory(new PropertyValueFactory<>("id"));
startTimeCol.setCellValueFactory(new PropertyValueFactory<>("startTime"));
stopTimeCol.setCellValueFactory(new PropertyValueFactory<>("stopTime"));
numberACol.setCellValueFactory(new PropertyValueFactory<>("numberB"));
numberBCol.setCellValueFactory(new PropertyValueFactory<>("numberA"));
rescodeCol.setCellValueFactory(new PropertyValueFactory<>("resultCode"));
subACol.setCellValueFactory(new PropertyValueFactory<>("subscriberB"));
subBCol.setCellValueFactory(new PropertyValueFactory<>("subscriberA"));
table.setItems(list);
Label webLinkLabel = new Label("Веб ресурс");
AppStart appStart = new AppStart();
webLinkLabel.setOnMouseClicked(event -> appStart.getHostServices().showDocument(getURLPropertie()));
webLink.setGraphic(webLinkLabel);
Label logsLinkLabel = new Label("Логи");
logsLinkLabel.setOnMouseClicked(event -> appStart.getHostServices().showDocument(outputLoggerFile)); //(NPE HERE)
logsLink.setGraphic(logsLinkLabel);
}
public void addCdr(CallDetailRecord cdr){
list.add(cdr);
list.sort(Comparator.comparingInt(CallDetailRecord::getId).reversed());
}
private String getURLPropertie(){
try(InputStream is = new FileInputStream(Objects.requireNonNull(getClass().getClassLoader().getResource("application.properties")).getFile())){
Properties prop = new Properties();
prop.load(is);
return prop.getProperty("url.link");
} catch (IOException e) {
e.printStackTrace();
}
return "https://google.com";
}
}
This is the code that loads and displays the FXML:
this.primaryStage = primaryStage;
Platform.setImplicitExit(false);
Parent root = FXMLLoader.load(getClass().getResource("/primal.fxml"));
primaryStage.setTitle("NIIAR");
primaryStage.getIcons().add(new Image("/icon.png"));
primaryStage.setScene(new Scene(root, 1400, 900));
createTray();
primaryStage.show();
If im trying to debug in other class that using Controller its shows the variable outputLoggerFile contains my propertie. I dont have any idea why.
sources - https://github.com/mindgame73/CDRListener-FX
The default behavior of the FXMLLoader is to create a controller by instantiating the class specified in the fx:controller attribute of the FXML file (invoking its no-argument constructor); then it injects #FXML-annotated fields into the controller, and after it parses the FXML file, it calls the initialize() method (if there is one).
Since the controller is instantiated by directly invoking its constructor, the Spring application context knows nothing about it, and can't inject any #Autowired beans into it.
To fix this, you need to set a controllerFactory on the FXMLLoader, instructing it to "create" (really retrieve) the controller instance from the Spring ApplicationContext. The controller factory is basically just a function (a #FunctionalInterface) which takes a Class<?> and produces an object. Since this is exactly the signature of one of the ApplicationContext.getBean() methods, the code for this looks like:
FXMLLoader loader = new FXMLLoader(getClass().getResource("/primal.fxml"));
loader.setControllerFactory(context::getBean);
Parent root = loader.load();
where context is the Spring ApplicationContext (you may need to jump through some hoops to get a reference to this in the method where you load your FXML file; usually just creating a field for it and annotating the field Autowired works).
I would make a couple of tweaks to the configuration of the controller class, too. By default, Spring manages beans as singleton scope. This is definitely not what you want here: if you were to load the same FXML a second time, you would need a different controller instance (as you would have a different set of UI controls). So you definitely need to scope the controller as a prototype.
Secondly, the Spring #Controller stereotype is intended for controllers in the Spring MVC sense; so I don't think it's really what you want here (though I don't think it does any harm). I would annotate the controller class as
#Component
#Scope(BeanDefinition.PROTOTYPE_SCOPE)
public class Controller { /* ... */ }
I have a JavaFX application that uses FXML alongside a controller class written in Java. In the Java controller I need to take care not to operate on an FXML Node element until it's been initialized (otherwise I'll get a NullPointerException), which isn't guaranteed until the initialize method is run. So I find myself doing this a lot:
The controller is set in the FXML file like this:
<Pane fx:controller="Controller" ...>
...
</Pane>
And then here's the controller in the Java file.
class Controller{
#FXML
Pane aPane;
int globalValue;
public void setSomething(int value){
globalValue = value;
if(!(aPane == null)){ //possibly null if node not initialized yet
aPane.someMethod(globalValue)
}
}
#FXML
void initialize(){
aPane.someMethod(globalValue) //guaranteed not null at this point
}
}
This works, but it's clunky and repetitive. I have to create the globalValue attribute just in case the setSomething method is called before initialize has been called, and I have to make sure the operations in my setSomething method are identical to the operations in initialize.
Surely there's a more elegant way to do this. I know that JavaFX has the Platform.runlater(...) method that guarantees something will be run on the main application thread. Perhpas there's something like Platform.runAfterInitialize(...) that waits until initialization, or runs immediately if initialization already happened? Or if there's another way to do it I'm open to suggestions.
If you specify the controller in the FXML file with fx:controller="Controller", then when you call FXMLLoader.load(...), the FXMLLoader:
parses the FXML file
creates an instance of Controller by (effectively) calling its no-arg constructor (or, in advanced usage, by invoking the controller factory if you set one)
creates the UI elements corresponding to the elements in the FXML file
injects any elements with an fx:id into matching fields in the controller instance
registers event handlers
invokes initalize() on the controller instance (if such a method is defined)
returns the UI element corresponding to the root of the FXML hierarchy
Only after load() completes (i.e. after the #FXML-annotated fields are injected) can you get a reference to the controller with loader.getController(). So it is not possible (aside from doing something extremely unusual in a controller factory implementation) for you to invoke any methods on the controller instance until after the #FXML-injected fields are initialized. Your null checks here are redundant.
On the other hand, if you use FXMLLoader.setController(...) to initialize your controller, in which case you must not use fx:controller, you can pass the values to the constructor. Simply avoiding calling a set method on the controller before passing the controller to the FXMLLoader means you can assume any #FXML-annotated fields are initialized in the controller's public methods:
class Controller{
#FXML
Pane aPane;
int globalValue;
public Controller(int globalValue) {
this.globalValue = globalValue ;
}
public Controller() {
this(0);
}
public void setSomething(int value){
globalValue = value;
aPane.someMethod(globalValue)
}
#FXML
void initialize(){
aPane.someMethod(globalValue) //guaranteed not null at this point
}
}
and
FXMLLoader loader = new FXMLLoader(getClass().getResource("path/to/fxml"));
Controller controller = new Controller(42);
loader.setController(controller);
Node root = loader.load();
The following example shows explicit wiring of dependencies using spring java config that results in a different bean being wired in while using and interface for a spring configuration class.
This seems like it shouldn't occur or at least give the normal warning that there are two beans as candidates for autowiring and it doesn't know which to select.
Any thoughts on this issue? My guess is there is no real name spacing between configuration classes as is implied by the syntax "this.iConfig.a()" Could this be considered a bug (if only for not warning about the 2 candidate beans)?
public class Main
{
public static void main( final String[] args )
{
final ApplicationContext context = new AnnotationConfigApplicationContext( IConfigImpl.class, ServiceConfig.class );
final Test test = context.getBean( Test.class );
System.out.println( test );
}
}
public class Test
{
private final String string;
public Test( final String param )
{
this.string = param;
}
public String toString()
{
return this.string;
}
}
#Configuration
public interface IConfig
{
#Bean
public String a();
}
#Configuration
public class IConfigImpl implements IConfig
{
#Bean
public String a()
{
return "GOOD String";
}
}
#Configuration
public class ServiceConfig
{
#Autowired
IConfig iConfig;
#Bean
Test test()
{
return new Test( this.iConfig.a() );
}
#Bean
String a()
{
return "BAD String";
}
}
In this case, I would expect to have "GOOD String" to be always be wired in the Test object, but flipping the order of IConfigImpl.class, ServiceConfig.class in the context loader changes which string is loaded.
Tested with Spring 4.0.7
EDIT: Further testing shows this has nothing to to with inherented configs. Same thing results if you drop the IConfig interface.
I believe this was a behavior of Spring for years.
If you redefine a bean, the one that is being loaded as last wins.
Another question would be how to control the order of bean loading when java configs are used. Check out this article http://www.java-allandsundry.com/2013/04/spring-beans-with-same-name-and.html which shows you how to do the ordering by using #Import of the other Spring java config.
The solution is actually simple - if you need to override a previously
defined bean(without say the flexibility of autowiring with a
different bean name), either use the XML bean configuration for both
the bean being overridden and the overriding bean or use the
#Configuration. XML bean configuration is the first example in this
entry, the one with #Configuration would be something like this:
#Configuration
public class Context1JavaConfig {
#Bean
public MemberService memberService() {
return new MemberSvcImpl1();
}
}
#Configuration
#Import(Context1JavaConfig.class)
public class Context2JavaConfig {
#Bean
public MemberService memberService() {
return new MemberSvcImpl2();
}
}
Stepan has mentioned the issue of order. The following is about your comment on their answer
Overriding beans of the same name makes sense, but in this case, I'm
specifically referencing the bean as specified in the iConfig
configuration. I would expect to get the one specified there.
In order to implement #Configuration and the caching of beans so that calls like
#Configuration
class Example {
#Bean
public UncaughtExceptionHandler uncaughtExceptionHandler() {
return (thread, throwable) -> System.out.println(thread + " => " + throwable.getMessage());
}
#Bean
#Scope(value = ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public Thread newThread() {
Thread thread = new Thread();
thread.setUncaughtExceptionHandler(uncaughtExceptionHandler()); // <<<<<< allowing this
return thread;
}
}
Spring actually uses CGLIB to create a proxy subtype of the #Configuration annotated class. This proxy maintains a reference to the backing ApplicationContext and uses that to resolve a bean.
So the call in your example
return new Test(this.iConfig.a());
isn't really invoking IConfigImpl#a(). It invokes this code (as of 4.2) from the proxy interceptor. The code uses the corresponding Method to determine the target bean name and uses the ApplicationContext's BeanFactory to resolve the bean. Since the bean definition for a bean named a has already been overriden, that new bean definition gets used. That bean definition is using the ServiceConfig#a() method as its factory method.
This is described in the documentation, here
All #Configuration classes are subclassed at startup-time with CGLIB.
In the subclass, the child method checks the container first for any
cached (scoped) beans before it calls the parent method and creates a
new instance.
Could this be considered a bug [...]?
I don't believe so. The behavior is documented.
I have a FXML controller that has some Spring Bean dependencies. I can't find a way to autowire them in time before the controller is loaded, since I'm using a custom FXML loader:
#Bean
#Scope(proxyMode = ScopedProxyMode.TARGET_CLASS, value = "prototype")
public UserProfile attachDocController() throws IOException {
return (UserProfile) loadController("/myproject/Forms/userProfile.fxml");
}
FXMLLoader loader = null;
protected Object loadController(String url) throws IOException {
loader = new FXMLLoader(getClass().getResource(url));
loader.load();
return loader.getController();
}
Using this approach, I can only autowire the beans by directly injecting them via the #Autowired annotation:
public class UserProfile {
#Autowired
MyDependency myDependency;
This leaves me dependent on Spring, and will leave me with code maintainability issues later on. How can I autowire dependencies from a Spring XML file configuration into a FXML controller class? Something like:
<bean id="UserProfile" class="myproject.controllerinjection.UserProfile" scope="prototype">
<aop:scoped-proxy proxy-target-class="true"/>
<property name="myDependency" ref="myDependency" />
</bean>
<bean id="myDependency" class="myproject.controllerinjection.MyDependency" scope="prototype">
<aop:scoped-proxy proxy-target-class="true"/>
</bean>
This seems like a much better route, with long-term project maintainability in mind, as the project gets larger.
UPDATE:
I'me not really used to Lambda expressions. I've researched a bit, but integrating the suggestion by #James_D as follows:
protected Object loadBeanController(String url) throws IOException {
loader = new FXMLLoader(getClass().getResource(url));
ApplicationContext ctx = WakiliProject.getCtx();
if (ctx != null) {
System.out.println("Load Bean...............");
loader.setControllerFactory(ctx::getBean);
} else {
System.out.println("No App.ctx...............");
}
return loader.getController();
}
gives a null pointer whenever I try calling a method of MyDependency. MyDependency myDependency never gets injected into UserProfile.
When you call FXMLLoader.load(), it loads the FXML file. If there is a fx:controller attribute in the root element, it creates a controller based on the class specified (and injects the fx:id-attributed elements into that controller instance, etc.). The loader then returns the root of the FXML file. The controller is intrinsically linked to that FXML root.
By default, the FXMLLoader maps a controller class to an instance by reflection, calling controllerClass.newInstance() (which effectively invokes the no-arg constructor of the controller class). You can configure this, overriding the default behavior, by specifying a controllerFactory on the FXMLLoader.
The controllerFactory is a function that maps a Class<?> object (constructed from the class name specified in the fx:controller attribute) to the controller instance. If you are using Spring to manage your controller instances, you just need this function to ask the Spring application context (bean factory) to generate the controller instance for you. So you can basically just do fxmlLoader.setControllerFactory(applicationContext::getBean);. With this setup, simply loading the fxml file via the FXMLLoader will cause the FXMLLoader to request the controller class from the application context. The application context can be configured in any of the ways that Spring allows.
So your config can look like
#Configuration
public class Config {
#Bean
#Scope(proxyMode = ScopedProxyMode.TARGET_CLASS, value = "prototype")
public UserProfile attachDocController() throws IOException {
return new UserProfile();
}
}
Of course, you can now inject dependencies in the config class:
#Bean
#Scope(proxyMode = ScopedProxyMode.TARGET_CLASS, value = "prototype")
public UserProfile attachDocController(MyDependency myDependency) throws IOException {
return new UserProfile(myDependency);
}
#Bean
public MyDependency createDependency() {
return new MyDependencyImpl();
}
Then in your UI work you can just do
FXMLLoader loader = new FXMLLoader(getClass().getResource("/myproject/Forms/userProfile.fxml"));
loader.setControllerFactory(applicationContext::getBean);
Parent root = loader.load();
// since everything can be initialized in the controller by D.I., you
// shouldn't need to access it, but if you do for some reason you can do
UserProfile controller = loader.getController();
where applicationContext is your Spring application context. This works whether the application context use XML configuration, annotation-based configuration, or Java configuration.
Update
If, for some reason, you cannot use Java 8 or later, a call to setControllerFactory that is Java 7 compatible looks like:
loader.setControllerFactory(new Callback<Class<?>, Object>() {
#Override
public Object call(Class<?> c) {
return applicationContext.getBean(c);
}
});
You would need applicationContext to be either a field or a final local variable for this to work in Java 7. Note that at the time of writing, Java 7 is not publicly supported by Oracle.