I have a java FX basic application which has a simple Scene(a form). I have a Mysql Db and I am using Spring JPA (spring data jpa i.e repository/entities) to interact with the same.
Now, since we know that javaFx has some lifecycle hooks namely: init() start() and stop().
Let's say I want to insert data in Database using JPA save() method. Usually, if it was my controller, a normal DB injection like:
@Autowired
EmployeeRepo employeeRepo;
Would have worked. But, I am not able to access this (or any Autowired Injection) inside the lifecycle methods.
public void start(Stage primaryStage) throws Exception {
// Some Code
employeeRepo.findAll() <- This is returning null
However, when I add a test method and use the same, It works fine:
@PostConstruct
public void test() {
// Repo object is not giving null
}
Is there a way I can manually inject the dependencies inside my button listener or pass it to the launch method.
Please let me know if there is a solution as I am new to JavaFX
There are numerous ways to get dependency injection into a JavaFX application. For example Gluon have a project called Gluon Ignite which enables JavaFX application for various dependency injection frameworks, such as Guice, Spring and Dagger.
As you have chosen Spring for your dependency injection framework and you wish to use a bunch of other Spring facilities such as Spring Data repositories, you may wish to consider using a SpringBoot application.
You could make your JavaFX application a SpringBoot application (though this isn't strictly necessary just to get dependency injection) in order to get a bunch of Spring facilities available within your application. There are some tutorials on that on the web if you search around.
Here is an example of a tutorial on integrating JavaFX with a SpringBoot application:
A critical part of that example is the init() method of the application (which I have just copy and pasted and reproduced here for reference):
@SpringBootApplication
public class DemoApplication extends Application {
private ConfigurableApplicationContext springContext;
private Parent root;
@Override
public void init() throws Exception {
springContext = SpringApplication.run(DemoApplication.class);
FXMLLoader fxmlLoader = new FXMLLoader(getClass().getResource("/sample.fxml"));
fxmlLoader.setControllerFactory(springContext::getBean);
root = fxmlLoader.load();
}
@Override
public void start(Stage primaryStage) throws Exception {
primaryStage.setTitle("Hello World");
Scene scene = new Scene(root, 800, 600);
primaryStage.setScene(scene);
primaryStage.show();
}
@Override
public void stop() throws Exception {
springContext.stop();
}
public static void main(String[] args) {
launch(DemoApplication.class, args);
}
}
The sample app is running the SpringBoot application to startup the Spring system and have it provide an application context in the init method. The app then uses the FXMLLoader setControllerFactory()
method to allow Spring to instantiate FXML controllers and inject references to Spring beans in the application.
For annotated autowiring to work in a Spring Bean, the annotated bean should (by default) be in a sibling or child package to the SpringBootApplication.
To get your JAVAFX FXML controller autowired, in addition to the following call on the FXMLLoader:
fxmlLoader.setControllerFactory(springContext::getBean);
You also need to annotate your class as a Spring @Component
, and @Autowired
in any Spring dependencies you want your controller to use. In this way, the FXMLLoader will inject the @FXML based references to your UI elements and it will also delegate to the spring context to inject the Spring dependencies.
@Component
@Scope("prototype")
public class DemoController {
@FXML
private Label usernameLabel;
@Autowired
public SpringService mySpringService;
public void initialize() {
usernameLabel.setText(
mySpringService.getLoggedInUsername()
);
}
}
Note, Spring has a @Controller
annotation, which could be used to annotate the JavaFX controller rather than the @Component
annotation. But I would recommend avoiding use of @Controller
for that purpose. Instead, only use the @Controller
annotation for Spring REST service endpoint controller definitions.
@Scope("prototype")
is used on the controller because we want to get a new controller to back the new view objects created each time the FXML is loaded, not reuse an existing controller, which is related to a different set of view objects.
One thing you might want to be careful of is that running the SpringBoot application, generates a new instance of the application and you already have a JavaFX application instance launched by the JavaFX system, so that would result in two JavaFX application instances if the SpringBoot application and the JavaFX application are based upon the same class (as shown above), which would potentially be confusing.
It is better to separate the Spring application and the JavaFX application. This enhances the separation of concerns between the UI and service portions of the application and makes for easier testing as the Spring application can be unit tested independently of starting up and shutting down the JavaFX application.
@SpringBootApplication
public class DemoSpringApplication {
public static void main(String[] args) {
SpringApplication.run(DemoSpringApplication.class);
}
}
Then rename DemoApplication
above to DemoFxApplication
.
When running the spring application from the fx application, write:
springContext = SpringApplication.run(DemoSpringApplication.class);
When testing just the Spring functionality without UI components, use DemoSpringApplication
as a main class.
When running your complete application with a UI use DemoFXApplication
as a main class.
Note, using the above setup, it will not auto-wire the JavaFX application class instantiated instance. If you wish to do that, you can use the technique illustrated below to inject beans in the JavaFX instantiated application class:
Place the following code inside your application's init method:
springContext
.getAutowireCapableBeanFactory()
.autowireBeanProperties(
this,
AutowireCapableBeanFactory.AUTOWIRE_BY_TYPE,
true
);
The mvvmFX framework uses a similar method to that outlined above to integrate SpringBoot with JavaFX applications:
To pass arguments from the JavaFX application to the SpringBoot application, use:
SpringApplication.run(
DemoApplication.class,
getParameters().getRaw().toArray(new String[0])
);
If you need, even more control over the startup of the SpringApplication, you can use the SpringApplicationBuilder for example:
ConfigurableApplicationContext startupContext =
new SpringApplicationBuilder(DemoApplication.class)
.web(WebApplicationType.NONE)
.run(args);
This answer is just written to give you hints on how you might approach this problem rather than as a general-purpose guide on how to integrate dependency injection with JavaFX, which could end up being quite a tricky subject to cover comprehensively.