JavaFX and Spring: issue with autowiring - java

I am trying to use JavaFX and Spring together. I am using Spring Boot, in particular.
My issue is that I have both Spring autowired fields and JavaFX "autowired" fields in the FXML controller.
I put a breakpoint in the constructor controller and it is actually being invoked twice: one by Spring, which autowires #Autowired fields only, one by JavaFX, which initialize #FXML fields only.
Here's my main code:
#SpringBootApplication
public class MySpringAndJavaFXApp extends AbstractJavaFXAndSpringApplication {
#Override
public void start(Stage window) throws Exception {
Parent root = FXMLLoader.load(getClass().getResource("/fxml/home.fxml"));
window.setTitle("My Title");
window.setScene(new Scene(root, 500, 300));
window.show();
}
public static void main(String[] args) {
launchApp(MySpringAndJavaFXApp.class, args);
}
}
Here's the class I am extending:
public abstract class JavaFXAndSpringApplication extends Application {
protected static void launchApp(Class<? extends JavaFXAndSpringApplication> classePrincipaleJavaFX, String[] args) {
launch(classePrincipaleJavaFX,args);
}
private ConfigurableApplicationContext applicationContext;
#Override
public void init() throws Exception {
applicationContext = SpringApplication.run(getClass());
applicationContext.getAutowireCapableBeanFactory().autowireBean(this);
}
#Override
public void stop() throws Exception {
super.stop();
applicationContext.close();
}
}
An example of a class being Spring-managed and JavaFX-managed:
#Component
public class MixedController {
#FXML
private TextField aTextField;
#Autowired
private MyService myService; // MyService is a #Component
public MixedController() {
System.out.println("I am here"); // debugger goes twice here
}
}
How I can easily fix my issue?
I attempted using a custom FXML loader (e.g. JavaFX and Spring - beans doesn't Autowire). However, if I load the home.fxml file with such loader, I get an error on the data provider (i.e. Spring is not getting my database configuration correctly) and I would prefer another approach.

Related

Constructor Injection together with JDA and Spring Boot [duplicate]

This question already has answers here:
Why is my Spring #Autowired field null?
(21 answers)
Closed last year.
I am struggling with initializing JDA as the "addEventListeners" requires me to input the EventListeners.. However, my EventListeners have "injected constructors" (if that's the correct word) that is grabbing my Dao's.
I would gladly use #Autowire in my EventListeners, but it keeps on giving me a NullPointer.
I think the issue is that JDA extends their EventListener, which basically loads outside of Spring Boot, even though I've added the #Service annotation on the Listener.
You can see my problem below at:
.addEventListeners(new JDAEventListener())
Obviously, I am not able to do new JDAEventListener()as it requires the WixSubscriptionDao. However, I am not able to understand how to initiate the JDAEventListener without WixSubscriptionDao, as I need the Dao for further data handling.
public static void main(String[] args) throws InterruptedException, LoginException {
JDA jda = JDABuilder.createDefault("XXXXXXXXXXXXXXXXXXXXXX")
.enableIntents(GatewayIntent.GUILD_MESSAGES, GatewayIntent.GUILD_MEMBERS)
.setChunkingFilter(ChunkingFilter.ALL)
.setMemberCachePolicy(MemberCachePolicy.ALL)
.addEventListeners(new JDAEventListener())
.build();
DISCORD_CONSTANT.jda = jda.awaitReady();
setupDefault();
}
#Service
public class JDAEventListener implements EventListener {
private final WixSubscriptionDao wixSubscriptionDao;
public JDAEventListener(WixSubscriptionDao wixSubscriptionDao) {
this.wixSubscriptionDao = wixSubscriptionDao;
}
#Override
public void onEvent(#NotNull GenericEvent genericEvent) {
if (genericEvent instanceof ReadyEvent) {
System.out.println("ReadyEvent done");
ArrayList<WixSubscription> wixSubscriptions = wixSubscriptionDao.findAll();
System.out.println(wixSubscriptions.size());
}
}
I would love to do this, but as wrote above, the #Autowired Dao is giving me a NullPointer, even though it's defined in the SpringConfig. (The DAO works perfectly when using the constructor method)
#Service
public class JDAEventListener implements EventListener {
#Autowired
private WixSubscriptionDao wixSubscriptionDao;
public JDAEventListener() {
}
#Override
public void onEvent(#NotNull GenericEvent genericEvent) {
if (genericEvent instanceof ReadyEvent) {
System.out.println("ReadyEvent done");
ArrayList<WixSubscription> wixSubscriptions = wixSubscriptionDao.findAll();
System.out.println(wixSubscriptions.size());
}
}
I suggest that you create class annotated with #Component which implements CommandLineRunner interface. This means that, the run method will be executed when the application starts. Also, you can inject other Spring beans into it, like for example JDAEventListener beans.
#Component
public class JDAInitializer implements CommandLineRunner {
private final JDAEventListener jdaEventListener;
// Constructor injection
public JDAInitializer(JDAEventListener jdaEventListener) {
this.jdaEventListener = jdaEventListener;
}
#Override
public void run(String... args) throws Exception {
JDA jda = JDABuilder.createDefault("XXXXXXXXXXXXXXXXXXXXXX")
.enableIntents(GatewayIntent.GUILD_MESSAGES, GatewayIntent.GUILD_MEMBERS)
.setChunkingFilter(ChunkingFilter.ALL)
.setMemberCachePolicy(MemberCachePolicy.ALL)
.addEventListeners(jdaEventListener)
.build();
DISCORD_CONSTANT.jda = jda.awaitReady();
setupDefault();
}
...
}

How does the boundary class connect with the controller class in Java with JavaFX + Spring Boot?

I'm making a Spring Boot + JavaFX application for a college project. Since we're studying the BCE (Boundary/Control/Entitity) Pattern from the Unified Process, we have to implement a use case with that in mind.
I'm programming the use case in Java, using JavaFX as UI and Spring Boot as a framework.
In the JavaFX fxml file, when you define the controller class attribute, I think that it should be the boundary class, rather than the controller itself because the first method of the app goes to the boundary object. The problem is that later I don't know how to link the boundary class to the controller in the Spring Context because if I define a Controller controller attribute in the boundary class, the program won't compile.
This is my Spring/JavaFX application class:
#SpringBootApplication
public class SpringApp extends Application {
public static ConfigurableApplicationContext applicationContext;
public static Parent root;
public static Stage stage;
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage primaryStage) throws Exception {
applicationContext = SpringApplication.run(SpringApp.class);
FXMLLoader loader = new FXMLLoader(SpringApp.class.getResource("/app.fxml"));
loader.setControllerFactory(applicationContext::getBean);
Scene scene = new Scene(loader.load(), 640, 360, false, SceneAntialiasing.BALANCED);
primaryStage.setScene(scene);
primaryStage.show();
}
}
And this is a portion of the boundary class (defined as a controller in the fxml file):
public class SaveNewClientBoundary implements Initializable {
#FXML
public ComboBox<Client> cboClients;
#Autowired
private ClientRepo clientRepo;
#Override
public void initialize(URL url, ResourceBundle resourceBundle) {
saveNewClientOption();
}
public void saveNewClientOption(){
showClients();
}
public void showClients() {
cboClients.setItems(FXCollections.observableArrayList(clientRepo.findAll()));
}
Now, this works and all, but it's not using the pattern properly (because of the findAll() method). What I'd like to do is to delegate the communication with entity objects to a controller, so a controller would look like this:
#Component
public class SaveNewClientController {
private List<Client> clients;
#Autowired
private ClientRepo clientRepo;
public void saveNewClientOption() {
this.searchAllClients();
}
public void searchAllClients() {
this.clients = clientRepo.findAll();
}
public List<Client> getClients() {
return clients;
}
If I do this, however, I don't get how to let the boundary object access the clients that the controller looks for. Basically, I don't understand how to instantiate the controller and link it to the Boundary class (and link the boundary class to the controller) once the program starts.
I ended up fixing it by implementing a circular dependency between the boundary class and the controller: Setter/Field Injection
Before that, I was trying to create an instance of the controller in the Boundary class initialize() method, but it was throwing a Controller NullPointer exception because that's not how Spring Boot works.
Thanks for the other replies.

JavaFX SpringBoot SpringJDBC SQLite CRUD Application - configuration

I'm developing a JavaFX CRUD application with SpringBoot + SpringJDBC + SQLite . I'm using Eclipse IDE.
STORY:
I'm developing this application as StepByStep process. And I achieved JavaFX+SQLite CRUD application with Old School JDBC Connection. But After Integrating SpringBoot + SpringJDBC I get error. I think the error in passing application configuration to all the files.
Main.Class
#SpringBootApplication
public class Main {
public static void main(String[] args) {
Application.launch(MyApplication.class, args);
}
}
AND MyApplication.Class (has no annotation)
public class MyApplication extends Application {
protected ConfigurableApplicationContext applicationContext;
#Override
public void init() throws Exception {
applicationContext = new AnnotationConfigApplicationContext(AppConfig.class);
}
#Override
public void stop() throws Exception {
applicationContext.close();
Platform.exit();
}
#Override
public void start(Stage stage) throws Exception {
stage.setTitle("ExYouTub");
stage.setOnCloseRequest(x -> {
Platform.exit();
});
stage.setResizable(false);
stage.setScene(new Scene(FXMLLoader.load(getClass().getResource("../Sample.fxml"))));
stage.show();
}
}
AND AppConfig.class
#Configuration
#ConditionalOnClass(DataSource.class)
#Profile("sqlite")
#ComponentScan(basePackages= "com.fz")
#PropertySource("classpath:data/config.properties")
public class AppConfig {
#Autowired
Environment environment;
private final String DB_URL = "fz.db.url";
#Bean
DataSource dataSource() {
final DriverManagerDataSource driverManagerDataSource = new DriverManagerDataSource();
driverManagerDataSource.setUrl(environment.getProperty(DB_URL));
return driverManagerDataSource;
}
}
AND SampleController.class
#Controller
public class SampleController implements Initializable {
//-- un-necessary lines are ignored to copy
#Autowired
#Qualifier("studentsDAOImpl")
private StudentsDAO studentsDAO;
#Override
public void initialize(URL location, ResourceBundle resources) {
tableViewList.getSelectionModel().setSelectionMode(SelectionMode.MULTIPLE);
setColumnProperties();
loadStudentsDetails();
}
private void loadStudentsDetails() {
studentsList.clear();
studentsList.addAll(studentsDAO.getAllStudents()); // this is line 83
tableViewList.setItems(studentsList);
}
}
AND Error report
Caused by: java.lang.NullPointerException
at com.fz.SampleController.loadStudentsDetails(SampleController.java:83)
at com.fz.SampleController.initialize(SampleController.java:78)
at javafx.fxml/javafx.fxml.FXMLLoader.loadImpl(FXMLLoader.java:2573)
... 17 more
And Up to now my guess on this error is that, the configuration is not working properly - i think so. I need suggestion and help me improve on this.
The default behaviour for FXMLLoader to create the controller instance is to simply instantiate it, which means no dependencies get injected. To get injection, you rather need the Spring ApplicationContext to manage the instance creation.
Josh Long has issued a Spring Tips installment demonstrating how to do this step by step: https://spring.io/blog/2019/01/16/spring-tips-javafx
However, this is a quite manual and repetitive process. This is what https://github.com/rgielen/javafx-weaver tries to solve. You can find a documentation and example code here: https://github.com/rgielen/javafx-weaver/tree/master/samples/springboot-sample
Disclaimer: I created the library, so I'm biased :)

Spring Boot: how to inject dependencies into a class called by a library?

I'm using Kinesis Client Library (KCL) and Spring boot. To use KCL, I have to implement a class (I named it RecordProcessor) for interface IRecordProcessor. And KCL will call this class and process records from kinesis. But when I tried to use dependency injection, I found it was not succeeded.
Here's the snippet for RecordProcessor:
#Component
public class RecordProcessor implements IRecordProcessor {
#Autowired
private SingleRecordProcessor singleRecordProcessor;
#Override
public void initialize(String shardId) {
...
}
#Override
public void processRecords(List<Record> records, IRecordProcessorCheckpointer checkpointer) {
...
}
}
I use Class SingleRecordProcessor to process single each record from kinesis. And this is my SingleRecordProcessor class snippet:
#Component
public class SingleRecordProcessor {
private Parser parser;
private Map<String, Table> tables;
public SingleRecordProcessor() {
}
#Autowired
private void setParser(Parser parser) {
this.parser = parser;
}
#Autowired
private void setTables(Map<String, Table> tables) {
this.tables = tables;
}
public void process(String record) {
...
}
}
I want to let spring framework automatically inject the SingleRecordProcessor instance into the class and use it. But I found that the field singleRecordProcessor is null.
Any idea why the dependency injection is failed? Or is it impossible to inject dependencies into a class which is called by other framework (in this case it's KCL)? Any suggestions will be appreciated! Really need some help please!!
[UPDATE]:
Sorry for not expressing the error clearly. The error was NullPointerException. I tried to inject singleRecordProcessor and call method process() on it. I think the injection was not successful so the instance singleRecordProcessor is null and there comes the NullPointerException.
More information is as follows:
I have a major class called Application
#SpringBootApplication
public class Application{
public static void main(String[] args) {
SpringApplication application = new SpringApplication(Application.class);
application.addListeners(new ApplicationPidFileWriter("./app.pid"));
ConfigurableApplicationContext ctx = application.run(args);
}
}
And I have the MainProcessor class which will call KCL.
#Service
public final class MainProcessor {
#EventListener(ApplicationReadyEvent.class)
public static void startConsumer() throws Exception {
init();
IRecordProcessorFactory recordProcessorFactory = new RecordProcessorFactory();
Worker worker = new Worker(recordProcessorFactory, kinesisClientLibConfiguration);
...
worker.run(); // this line will call KCL library and eventually call ProcessorRecord class.
}
}
[UPDATE2]
RecordProcessorFactory only has one method like this
#Component
public class RecordProcessorFactory implements IRecordProcessorFactory {
#Autowired
RecordProcessor recordProcessor;
#Override
public IRecordProcessor createProcessor() {
return recordProcessor;
}
}
It creates a new RecordProcessor instance for KCL to use it.
You should autowire an instance of this into your MainProcessor:
#Component
public class RecordProcessorFactory {
#Lookup IRecordProcessor createProcessor() { return null; }
}
Spring will instantiate a RecordProcessorFactory for you, and replace the implementation of createProcessor() in it with one that will return a new IRecordProcessor each time it's called. Both the factory and the processors will be Spring beans - which is what you want.

JavaFX fxml - How to use Spring DI with nested custom controls?

I've been through a number of tutorials on integrating Spring DI with JavaFx but I've hit a wall that the simple examples dont cover (and I cant figure out).
I want clean separation between the view and presentation layers. I would like to use fxml to define composable views and Spring to wire it all together. Here's a concrete example:
Dashboard.fxml:
<GridPane fx:id="view"
fx:controller="com.scrub.presenters.DashboardPresenter"
xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml">
<children>
<TransactionHistoryPresenter fx:id="transactionHistory" />
</children>
</GridPane>
Main.java:
public void start(Stage primaryStage) throws Exception{
try {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppFactory.class);
SpringFxmlLoader loader = context.getBean(SpringFxmlLoader.class);
primaryStage.setScene(new Scene((Parent)loader.load("/views/dashboard.fxml")));
primaryStage.setTitle("Hello World");
primaryStage.show();
} catch(Exception e) {
e.printStackTrace();
}
}
SpringFxmlLoader.java:
public class SpringFxmlLoader {
#Autowired
ApplicationContext context;
public Object load(String url) {
try {
FXMLLoader loader = new FXMLLoader(getClass().getResource(url));
loader.setControllerFactory(new Callback<Class<?>, Object>() {
#Override
public Object call(Class<?> aClass) {
return context.getBean(aClass);
}
});
return loader.load();
} catch(Exception e) {
e.printStackTrace();
throw new RuntimeException(String.format("Failed to load FXML file '%s'", url));
}
}
}
So when DashboardPresenter gets loaded the SpringFxmlLoader correctly injects the controller with the loader.setControllerFactory.
However, the custom TransactionHistoryPresenter control is loaded with a new instance and not from the spring context. It must be using its own FXMLLoader?
Any ideas how to make custom controls play nice with Spring? I really dont want to go down the path of having the controllers / presenters manually wiring them up.
The main problem here, is make sure that Spring is initialized on the same thread of the JavaFX application. This usually means that Spring code must be executed on the JavaFX application thread; other time-consuming jobs can of course be executed on their own thread.
This is the solution I put together using this tutorial and my own knowledge of Spring Boot:
#SpringBootApplication
#ImportResource("classpath:root-context.xml")
public class JavaFXSpringApplication extends Application {
private static final Logger log = LoggerFactory.getLogger(JavaFXSpringApplication.class);
private Messages messages;
private static String[] args;
#Override
public void start(final Stage primaryStage) {
// Bootstrap Spring context here.
ApplicationContext context = SpringApplication.run(JavaFXSpringApplication.class, args);
messages = context.getBean(Messages.class);
MainPaneController mainPaneController = context.getBean(MainPaneController.class);
// Create a Scene
Scene scene = new Scene((Parent) mainPaneController.getRoot());
scene.getStylesheets().add(getClass().getResource("/css/application.css").toExternalForm());
// Set the scene on the primary stage
primaryStage.setScene(scene);
// Any other shenanigans on the primary stage...
primaryStage.show();
}
public static void main(String[] args) {
JavaFXSpringApplication.args = args;
launch(args);
}
}
This class is both a JavaFX application entry point and a Spring Boot initialization entry point, hence the passing around of varargs. Importing an external configuration file makes it easier to keep the main class uncluttered while getting other Spring-related stuff ready (i.e. setting up Spring Data JPA, resource bundles, security...)
On the JavaFX "start" method, the main ApplicationContext is initialized and lives. Any bean used at this point must be retrieved via ApplicationContext.getBean(), but every other annotated bean (provided it is in a descendant package of this main class) will be accessible as always.
In particular, Controllers are declared in this other class:
#Configuration
#ComponentScan
public class ApplicationConfiguration {
#Bean
public MainPaneController mainPaneController() throws IOException {
return (MainPaneController) this.loadController("path/to/MainPane.fxml");
}
protected Object loadController(String url) throws IOException {
InputStream fxmlStream = null;
try {
fxmlStream = getClass().getResourceAsStream(url);
FXMLLoader loader = new FXMLLoader();
loader.load(fxmlStream);
return loader.getController();
} finally {
if (fxmlStream != null) {
fxmlStream.close();
}
}
}
}
You can see any Controller (I have just one, but it can be many) is annotated with #Bean and the whole class is a Configuration.
Finally, here is MainPaneController.
public class MainPaneController {
#Autowired
private Service aService;
#PostConstruct
public void init() {
// ...stuff to do with components...
}
/*
* FXML Fields
*/
#FXML
private Node root;
#FXML
private TextArea aTextArea;
#FXML
private TextField aTextField;
#FXML
private void sayButtonAction(ActionEvent event) {
aService.doStuff(aTextArea, aTextField);
}
}
This Controller is declared as a #Bean, so it can be #Autowired with and from any other #Beans (or Services, Components, etc.). Now for example you can have it answer to a button press and delegate logic performed on its fields to a #Service. Any component declared into the Spring-created Controllers will be managed by Spring and thus aware of the context.
It is all quite easy and straightforward to configure. Feel free to ask if you have any doubts.
It is possible.
Create custom BuilderFactory that delivers spring beans. Then assign it to the FXMLLoader fxmlLoader.setBuilderFactory(beanBuilderFactory);
#Component
public class BeanBuilderFactory implements BuilderFactory {
#Autowired
private ConfigurableApplicationContext context;
public BeanBuilderFactory() {
}
#Override
public Builder<?> getBuilder(Class<?> type) {
try {
Object bean = this.context.getBean(type);
if (bean.getClass().isAssignableFrom(type))
return new Builder() {
#Override
public Object build() {
return bean;
}
};
else
return null;
} catch (BeansException e) {
return null;
}
}
}

Categories