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 :)
Related
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.
Background: I'm trying to set up a code-based data migration system for our Cassandra database. I don't have a ton of experience with Java, but if this were a .NET project I'd set up the migrations as a different project under the same solution. However, based on guidance from other team members that are more experienced, it was recommended that I include the migrations in the same package as the rest of the application (which I'm fine with). It was also suggested that the easiest method would be to run the migrations via a web API endpoint (which I'm more skeptical of). In the interest of avoiding opening up a potential security vulnerability, I thought I'd take a shot at making a command-line utility to execute the migrations.
I have a Spring Boot web application with an entry point class that looks like this:
#Configuration
#SpringBootApplication
#EnableAutoConfiguration
#EnableCaching
#EnableScheduling
public class MyApplication extends SpringBootServletInitializer {
#Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
return application.sources(MyApplication.class);
}
public static void main(String[] args) {
new MyApplication().configure(new SpringApplicationBuilder(MyApplication.class)).run(args);
}
}
However, I'm trying to add the functionality to run a couple migration scripts that are packaged with this application via the command line (e.g. java -jar MyApplication.jar migrate), so I added the following class:
#Configuration
#SpringBootApplication
#EnableAutoConfiguration
public class MigrationRunner implements CommandLineRunner {
#Autowired
Session session;
#Override
public void run(String[] args)
{
MigrationResources mr = new MigrationResources();
mr.addMigration(...);
mr.addMigration(...);
MigrationEngine.withSession(session).migrate(mr);
}
}
And then updated my entry point class like this:
// annotations
public class MyApplication extends SpringBootServletInitializer {
private final static String MIGRATE_COMMAND = "migrate";
#Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
return application.sources(MyApplication.class);
}
public static void main(String[] args) {
if (args.length > 0 && args[0].equalsIgnoreCase(MIGRATE_COMMAND)) {
new SpringApplicationBuilder()
.sources(MigrationRunner.class)
.run(Arrays.copyOfRange(args, 1, args.length));
} else {
new MyApplication().configure(new SpringApplicationBuilder(MyApplication.class)).run(args);
}
}
}
The problem is that when I execute this with the migrate arg, Spring throws this error:
Error creating bean with name 'migrationRunner': Unsatisfied dependency expressed through field 'session'
Error creating bean with name 'session' defined in class path resource [org/springframework/boot/autoconfigure/data/cassandra/CassandraDataAutoConfiguration.class]: Invocation of init method failed
All host(s) tried for query failed (tried: server022/XX.YY.ZZ.022:9042 (com.datastax.driver.core.exceptions.TransportException: [server022/XX.YY.ZZ.022:9042] Connection has been closed), server022/XX.YY.ZZ.020:9042 (com.datastax.driver.core.exceptions.TransportException: [server020/XX.YY.ZZ.020:9042] Connection has been closed), server020/XX.YY.ZZ.021:9042 (com.datastax.driver.core.exceptions.TransportException: [server020/XX.YY.ZZ.021:9042] Connection has been closed))
Running it without the migrate arg still works fine. I suspect that Spring is simply not picking up the correct certificates for this Cassandra server, even though it appears to be getting all the other configuration properties (server name, keyspace, etc.)
Question: How can I make a Spring Boot servlet that also has a command-line mode and can connect to the configured Cassandra server in both modes?
All you need to do is,
#SpringBootApplication
public class MyApplication
public static void main(String[] args) {
SpringApplication.run(MyApplication.class, args);
}
}
You have over complicated the application. If you run the MyApplication.main that will run in port 8080 by default.
Bonus, If you need both to start from same class.
#SpringBootApplication
public class MigrationRunner implements CommandLineRunner {
#Autowired
Session session;
public static void main(String[] args) {
SpringApplication.run(MyApplication.class, args);
}
#Override
public void run(String[] args)
{
MigrationResources mr = new MigrationResources();
mr.addMigration(...);
mr.addMigration(...);
MigrationEngine.withSession(session).migrate(mr);
}
}
I have a particular class used to interface with a service that requires initialization. In the application lifecycle, the only place this makes sense is in the start of the application because the rest of the spring application cannot run without it. I had the idea to do this:
#SpringBootApplication
public class MyApplication {
public static void main(String[] args) {
try {
MyRequiredService mrs = new MyRequiredService();
mrs.connect(); // This will throw if it fails
run(MyApplication.class, args);
} catch(MyException e) {
System.out.println("Failed to connect to MyRequiredService!");
}
}
}
This will launch the service and attempt to connect but I have one big problem. How do I pass this class around the application? I need it's functions in the service endpoints I am writing.
I didn't see anything obvious and searching "passing class instance in spring boot application" turns up a bunch of unrelated topics.
Is there a smart, clean way to do this in spring boot? I apologize for a contrived example. The names of the service are unique enough I didn't want to violate any agreements.
You can make Spring do this for you. First, you need to annotate your class with #Service, so Spring will pick it up when scanning for classes.
Then, define an init() method and annotate it with #PostConstruct. Spring will instantiate your MyRequiredService class and call init()
#Service
public class MyRequiredService {
#PostConstruct
public void init() {
connect();
}
public void connect() {
// ...
}
}
You could call connect() from the constructor, but I don't like to define objects that may throw exceptions out of the constructor.
And then, you can use MyRequiredService in some other class by injecting it via the #Autowired annotation:
#Component
public class MyOtherClass {
private final MyRequiredService service;
public MyOtherClass(final MyRequiredService service) {
this.service = service;
}
// Other methods here.
}
This has the same overall effect as what you're trying to do above. If MyRequiredService fails, the application will not start up.
Make it a bean. Then it will be in the ApplicationContext which then you can pass to your desired other classes through the constructor
#Configuration
public class ApplicationConfiguration
{
#Bean
public MyRequiredService myRequiredService()
{
MyRequiredService mrs = new MyRequiredService();
try {
mrs.connect(); // This will throw if it fails
return mrs;
} catch(MyException e) {
log.error("Failed to connect to MyRequiredService!");
throw new IllegalStateException("MyRequiredService failed connection. Stopping startup");
}
}
#Bean
public SomeOtherService someOtherService(MyRequiredService mrs) {
return new SomeOtherService(mrs);
}
}
IMHO Instead of catching the error and logging it. I would throw it and stop the application from starting, but to keep with your example I added the throw IllegalStateException after the log.
Doing it this way Spring will create your MyRequiredService bean in the ApplicationContext then you can see I added as a parameter needed by the bean below that. Spring will grab that bean out of the ApplicationContext and supply it to the bean. If Spring doesn't find the bean in the ApplicationContext it will throw an error and stop the application from startup.
a class implements BeanFactoryPostProcessor which is init before normal bean
#Configuration
public class MyRequiredService implements BeanFactoryPostProcessor,
PriorityOrdered, InitializingBean {
#Override
public int getOrder() {
return Integer.MIN_VALUE;
}
public void connect() {
// ...
}
#Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
}
#Override
public void afterPropertiesSet() throws Exception {
connect();
}
}
After starting my spring boot application I want to start an customer process like creating required folders, files, etc. For that I'm using ApplicationListener<ApplicationReadyEvent>. This works like expected. But I'm building my spring application context with SpringApplicationBuilder. Every child notifies that the application is started correctly. So my customer post-process startes even more than one time.
#SpringBootApplication
#EnableConfigurationProperties(value = {StorageProperties.class})
#EnableAsync
public class Application {
public static void main(String[] args) {
SpringApplicationBuilder parentBuilder
= new SpringApplicationBuilder(Application.class);
parentBuilder.child(Config1.class)
.properties("server.port:1443")
...
.run(args);
parentBuilder.child(Config2.class)
.properties("server.port:2443")
...
.run(args);
}
}
My first idea was, that I can create manuelly a new Bean with #Bean in Config1 for my Event-Listener. But I was not able to overhand the configuration file StorageProperties.class, which is necessary for this class.
Because the Listener has an constructor based dependency injection:
private final Path mPathTo;
public AfterStart(StorageProperties prop) {
this.mPathTo = Paths.get(prob.getPath());
}
How can I be able to start the listener just once per start?
For everyone who is interested in this question. This solution worked for me:
public void onApplicationEvent(ApplicationReadyEvent e) {
if (e.getApplicationContext().getParent == null) {
System.out.println("******************************");
System.out.println("Post-process begins.");
System.out.println("******************************");
}
}
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.