You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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) {

        }
    }

}

错误分析

  1. 空ActionEvent触发空指针:手动创建的ActionEvent未关联任何UI组件,调用切换方法时event.getSource()返回null,后续强转Node会抛出空指针异常,且catch块吞掉所有异常,无法定位问题。
  2. 错误获取导航控制器实例:sceneController是独立的导航控制器,并非Book.fxml的控制器,通过FXMLLoader.getController()获取的是Book页面的控制器,不是所需的导航实例。
  3. 严重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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.24 11:57:08