JavaFX中如何跨控制器调用页面切换函数实现统一导航?
JavaFX单一导航控制器调用失败解决方案
问题场景
我希望项目由一个sceneController统一处理页面导航,在LoginController中调用该控制器的页面切换函数,但当前实现无法正常工作。
原导航控制器代码
package system.test.librarysystem; import javafx.event.ActionEvent; import javafx.fxml.FXMLLoader; import javafx.scene.Node; import javafx.scene.Parent; import javafx.scene.Scene; import javafx.stage.Stage; import java.io.IOException; import java.util.Objects; public class sceneController { private Stage stage; private Scene scene; private Parent root; public void switchToLogin(ActionEvent event) throws IOException { Parent root = FXMLLoader.load(Objects.requireNonNull(getClass().getResource("Login.fxml"))); stage = (Stage)((Node)event.getSource()).getScene().getWindow(); scene = new Scene(root); stage.setScene(scene); stage.show(); } public void switchToBook(ActionEvent event) throws IOException { Parent root = FXMLLoader.load(Objects.requireNonNull(getClass().getResource("Book.fxml"))); stage = (Stage)((Node)event.getSource()).getScene().getWindow(); scene = new Scene(root); stage.setScene(scene); stage.show(); } public void switchToDVD(ActionEvent event) throws IOException { Parent root = FXMLLoader.load(Objects.requireNonNull(getClass().getResource("DVD.fxml"))); stage = (Stage) ((Node) event.getSource()).getScene().getWindow(); scene = new Scene(root); stage.setScene(scene); stage.show(); } }
原LoginController代码
package system.test.librarysystem; import java.io.IOException; import javafx.event.ActionEvent; import javafx.fxml.FXML; import javafx.fxml.FXMLLoader; import javafx.scene.control.Button; import javafx.scene.control.Label; import javafx.scene.control.TextField; import javafx.scene.control.PasswordField; import javafx.scene.input.KeyEvent; import java.sql.Connection; import java.sql.ResultSet; import java.sql.Statement; public class LoginController { @FXML private Label loginLabel; @FXML private TextField usernameEntry; @FXML private PasswordField passwordEntry; public void loginButtonAction(ActionEvent e) { if (usernameEntry.getText().isBlank() == false && passwordEntry.getText().isBlank() == false) { validateLogin(); } else { loginLabel.setText("Please enter username and password"); } } public void validateLogin() { DatabaseConnection connectNow = new DatabaseConnection(); Connection connectDB = connectNow.getConnection(); String verifyLogin = "SELECT count(1) FROM users WHERE username = '" + usernameEntry.getText() + "' AND password = '" + passwordEntry.getText() + "'"; try { Statement statement = connectDB.createStatement(); ResultSet queryResult = statement.executeQuery(verifyLogin); while(queryResult.next()) { if (queryResult.getInt(1) ==1 ) { FXMLLoader loader = new FXMLLoader(getClass().getResource("Book.FXML")); sceneController sceneController = loader.getController(); ActionEvent actionEvent = new ActionEvent(); sceneController.switchToBook(actionEvent); } else { loginLabel.setText("Invalid login"); } } } catch (Exception e) { } } }
错误分析
- 空ActionEvent触发空指针:手动创建的
ActionEvent未关联任何UI组件,调用切换方法时event.getSource()返回null,后续强转Node会抛出空指针异常,且catch块吞掉所有异常,无法定位问题。 - 错误获取导航控制器实例:
sceneController是独立的导航控制器,并非Book.fxml的控制器,通过FXMLLoader.getController()获取的是Book页面的控制器,不是所需的导航实例。 - 严重SQL注入风险:直接拼接用户名、密码到SQL语句中,存在被注入攻击的隐患。
修复方案
1. 重构导航控制器,解除ActionEvent依赖
修改导航方法,支持直接传入Stage,同时保留原方法兼容按钮绑定:
package system.test.librarysystem; import javafx.fxml.FXMLLoader; import javafx.scene.Parent; import javafx.scene.Scene; import javafx.stage.Stage; import java.io.IOException; import java.util.Objects; // 类名首字母大写,符合Java规范 public class SceneController { // 新增直接接收Stage的切换方法 public void switchToLogin(Stage stage) throws IOException { Parent root = FXMLLoader.load(Objects.requireNonNull(getClass().getResource("Login.fxml"))); switchScene(stage, root); } public void switchToBook(Stage stage) throws IOException { Parent root = FXMLLoader.load(Objects.requireNonNull(getClass().getResource("Book.fxml"))); switchScene(stage, root); } public void switchToDVD(Stage stage) throws IOException { Parent root = FXMLLoader.load(Objects.requireNonNull(getClass().getResource("DVD.fxml"))); switchScene(stage, root); } // 提取公共切换逻辑,减少重复代码 private void switchScene(Stage stage, Parent root) { Scene scene = new Scene(root); stage.setScene(scene); stage.show(); } // 保留原方法,兼容FXML按钮绑定 public void switchToBook(javafx.event.ActionEvent event) throws IOException { Stage stage = (Stage) ((javafx.scene.Node) event.getSource()).getScene().getWindow(); switchToBook(stage); } public void switchToLogin(javafx.event.ActionEvent event) throws IOException { Stage stage = (Stage) ((javafx.scene.Node) event.getSource()).getScene().getWindow(); switchToLogin(stage); } public void switchToDVD(javafx.event.ActionEvent event) throws IOException { Stage stage = (Stage) ((javafx.scene.Node) event.getSource()).getScene().getWindow(); switchToDVD(stage); } }
2. 修改LoginController,正确调用导航逻辑
修复登录验证中的导航调用,同时解决SQL注入和异常吞掉的问题:
package system.test.librarysystem; import javafx.event.ActionEvent; import javafx.fxml.FXML; import javafx.scene.control.Label; import javafx.scene.control.PasswordField; import javafx.scene.control.TextField; import javafx.stage.Stage; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; public class LoginController { @FXML private Label loginLabel; @FXML private TextField usernameEntry; @FXML private PasswordField passwordEntry; public void loginButtonAction(ActionEvent e) { if (!usernameEntry.getText().isBlank() && !passwordEntry.getText().isBlank()) { validateLogin(e); } else { loginLabel.setText("Please enter username and password"); } } public void validateLogin(ActionEvent e) { DatabaseConnection connectNow = new DatabaseConnection(); // 使用try-with-resources自动关闭资源 try (Connection connectDB = connectNow.getConnection()) { // 使用PreparedStatement防止SQL注入 String verifyLogin = "SELECT count(1) FROM users WHERE username = ? AND password = ?"; try (PreparedStatement statement = connectDB.prepareStatement(verifyLogin)) { statement.setString(1, usernameEntry.getText()); statement.setString(2, passwordEntry.getText()); ResultSet queryResult = statement.executeQuery(); if (queryResult.next() && queryResult.getInt(1) == 1) { // 获取当前Stage Stage stage = (Stage) ((javafx.scene.Node) e.getSource()).getScene().getWindow(); // 实例化导航控制器并调用切换方法 SceneController sceneController = new SceneController(); sceneController.switchToBook(stage); } else { loginLabel.setText("Invalid login"); } } } catch (Exception ex) { // 打印异常便于调试 ex.printStackTrace(); loginLabel.setText("Login failed: " + ex.getMessage()); } } }
3. 额外注意事项
- 遵循Java命名规范,类名首字母大写(如
sceneController改为SceneController)。 - 永远不要吞掉异常,在catch块中打印或处理异常,便于调试。
- 所有带参数的SQL查询必须使用
PreparedStatement,避免SQL注入漏洞。
内容的提问来源于stack exchange,提问作者john White
相关产品推荐
相关产品推荐

