-2

我正在开发一个客户端-服务器信使应用程序以供练习。在我的客户端模块中有 Client 类、ClientController 类、ClientGUI 类和 fxml 文件、Message 类、一个 CSS 文件和 Main 类。我的 ClientGUI 类和 ClientController 类中有一个方法 display(String message) ,它将一个 Message 对象添加到具有 id #messages 的 VBox 中。

这是我的问题:在我的 ClientController 类中有一个 display() 方法,它调用 ClientGUI 的 display() 方法(我知道这似乎是多余的,但这不是问题)。当在我的 ClientGUI 类中调用 display() 方法时,例如在 setOnMouseClick() 方法中,它可以正常工作。但是,当从我的 ClientController 类调用此方法时,我得到一个 NullPointerException,它指向我的 ClientGUI 类的 display() 方法中的 VBox 类型的消息变量。我试过 messages.getChildren().add(new Message(message));VBox test = messages;

后者工作正常,而前者抛出异常。我还尝试在我的消息变量上调用其他方法,所有这些方法都会引发相同的错误。为什么会这样?

(它仍处于开发过程中,因此可能存在其他不相关的问题)

客户:

package client;

import java.io.*;
import java.net.Socket;

/**
 * Gabe Castelli
 * 9/26/2016
 * Description: Represents client and its socket
 */

public class Client implements Serializable {
    private static Client instance;
    private Socket clientSocket;
    private BufferedReader in;
    private PrintWriter out;
    private ObjectOutputStream objectOutputStream;
    private boolean connected = false;
    private static final long serialVersionUID = 0L;

    private Client() {}

    public void connect(String targetAddress, String username_, int port_) {
        try {
            clientSocket = new Socket(targetAddress, port_);
            clientSocket.setReuseAddress(true); // Allows same port to be used successively without cool-down
            in = new BufferedReader(new InputStreamReader(clientSocket.getInputStream()));
            out = new PrintWriter(clientSocket.getOutputStream(), true);
            objectOutputStream = new ObjectOutputStream(clientSocket.getOutputStream());
            objectOutputStream.writeObject(username_);
            ClientController.getInstance().display("Attention: Successfully connected to server");
            connected = true;
        } catch (IOException e) {
            ClientController.getInstance().display("Attention: Unable to connect to server");
        }
    }

    public void disconnect() {
        try {
            if (in != null) {
                in.close();
            }
            if (out != null) {
                out.close();
            }
            if (objectOutputStream != null) {
                objectOutputStream.close();
            }
            if (clientSocket != null) {
                clientSocket.close();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }

        connected = false;
    }

    public boolean connected() {
        return connected;
    }

    public static Client getInstance() {
        if (instance == null) {
            instance = new Client();
        }
        return instance;
    }

    public void sendMessage(String message) {
        out.println(message);
    }
}

客户端控制器:

package client;

import javafx.scene.layout.VBox;

/**
 * Gabe Castelli
 * 9/28/2016
 * Description:
 */

public class ClientController implements Controller {
    private static ClientController instance;

    private ClientController() {}

    public static ClientController getInstance() {
        if (instance == null) {
            instance = new ClientController();
        }
        return instance;
    }

    // Controls

    public void display(String message) {
        ClientGUI.getInstance().display(message);
    }

    public void displayAndSend(String message) {
        display(message);
        Client.getInstance().sendMessage(message);
    }

    public void connect(String address_, String username_, int port_) {
        Client.getInstance().connect(address_, username_, port_);
    }

    public void disconnect() {
        Client.getInstance().disconnect();
    }
}

客户端GUI:

package client;

import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.geometry.Pos;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.TextField;
import javafx.scene.input.KeyCode;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;

/**
 * Gabe Castelli
 * 9/26/2016
 * Description: GUI for client-side using JavaFX
 */

public class ClientGUI extends Application {
    private Scene scene;
    private TextField targetAddress;
    private TextField fieldUsername;
    private TextField fieldPort;
    private VBox messages;
    private TextField input;
    private Button btnConnect;
    private String username = "Anonymous";
    private int port = 2000;

    private static ClientGUI instance;

    public static ClientGUI getInstance() {
        if (instance == null) {
            instance = new ClientGUI();
        }
        return instance;
    }

    @Override
    public void start(Stage stage) throws Exception {
        Parent root = FXMLLoader.load(getClass().getResource("ClientGUI.fxml"));
        scene = new Scene(root, 480, 360);
        stage.setTitle("MyMessenger");
        stage.setScene(scene);
        stage.setMinWidth(660);
        stage.setMinHeight(495);
        stage.setMaxWidth(990);
        stage.show();

        targetAddress = (TextField) scene.lookup("#targetAddress");
        fieldUsername = (TextField) scene.lookup("#fieldUsername");
        fieldPort = (TextField) scene.lookup("#fieldPort");
        messages = (VBox) scene.lookup("#messages");
        input = (TextField) scene.lookup("#input");
        btnConnect = (Button) scene.lookup("#btnConnect");

        input.setOnKeyPressed(event -> {
            if (event.getCode() == KeyCode.ENTER) {
                if (Client.getInstance().connected()) {
                    ClientController.getInstance().displayAndSend(input.getText());
                    input.setText("");
                } else {
                    display("Attention: Not connected to server");
                    input.setText("");
                }
            }
        });

        btnConnect.setOnMouseClicked(event -> {
            if (!Client.getInstance().connected()) {
                if (!targetAddress.getText().equals("")) {
                    if (!fieldUsername.getText().equals("")) {
                        username = fieldUsername.getText();
                    }
                    if (!fieldPort.getText().equals("")) {
                        port = Integer.valueOf(fieldPort.getText());
                    }

                    ClientController.getInstance().connect(targetAddress.getText(), username, port);
                    btnConnect.setText("Disconnect");
                } else {
                    display("Attention: Address required");
                }
            } else {
                ClientController.getInstance().disconnect();
                display("Attention: Disconnecting...");
                btnConnect.setText("Connect");
            }
        });
    }

    public void display(String message) {
        messages.getChildren().add(new Message(message));
    }
}

堆栈跟踪:

Exception in thread "JavaFX Application Thread" java.lang.NullPointerException
    at client.ClientGUI.display(ClientGUI.java:96)
    at client.ClientController.display(ClientController.java:26)
    at client.Client.connect(Client.java:34)
    at client.ClientController.connect(ClientController.java:35)
    at client.ClientGUI.lambda$start$1(ClientGUI.java:81)
    at com.sun.javafx.event.CompositeEventHandler.dispatchBubblingEvent(CompositeEventHandler.java:86)
    at com.sun.javafx.event.EventHandlerManager.dispatchBubblingEvent(EventHandlerManager.java:238)
    at com.sun.javafx.event.EventHandlerManager.dispatchBubblingEvent(EventHandlerManager.java:191)
    at com.sun.javafx.event.CompositeEventDispatcher.dispatchBubblingEvent(CompositeEventDispatcher.java:59)
    at com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(BasicEventDispatcher.java:58)
    at com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(EventDispatchChainImpl.java:114)
    at com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(BasicEventDispatcher.java:56)
    at com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(EventDispatchChainImpl.java:114)
    at com.sun.javafx.event.EventUtil.fireEventImpl(EventUtil.java:74)
    at com.sun.javafx.event.EventUtil.fireEvent(EventUtil.java:54)
    at javafx.event.Event.fireEvent(Event.java:198)
    at javafx.scene.Scene$ClickGenerator.postProcess(Scene.java:3470)
    at javafx.scene.Scene$ClickGenerator.access$8100(Scene.java:3398)
    at javafx.scene.Scene$MouseHandler.process(Scene.java:3766)
    at javafx.scene.Scene$MouseHandler.access$1500(Scene.java:3485)
    at javafx.scene.Scene.impl_processMouseEvent(Scene.java:1762)
    at javafx.scene.Scene$ScenePeerListener.mouseEvent(Scene.java:2494)
    at com.sun.javafx.tk.quantum.GlassViewEventHandler$MouseEventNotification.run(GlassViewEventHandler.java:352)
    at com.sun.javafx.tk.quantum.GlassViewEventHandler$MouseEventNotification.run(GlassViewEventHandler.java:275)
    at java.security.AccessController.doPrivileged(Native Method)
    at com.sun.javafx.tk.quantum.GlassViewEventHandler.lambda$handleMouseEvent$355(GlassViewEventHandler.java:388)
    at com.sun.javafx.tk.quantum.QuantumToolkit.runWithoutRenderLock(QuantumToolkit.java:389)
    at com.sun.javafx.tk.quantum.GlassViewEventHandler.handleMouseEvent(GlassViewEventHandler.java:387)
    at com.sun.glass.ui.View.handleMouseEvent(View.java:555)
    at com.sun.glass.ui.View.notifyMouse(View.java:937)
4

1 回答 1

1

发生空指针异常是因为ClientGUI您从中获取ClientGUI.getInstance()的实例不是作为 JavaFX 启动过程的一部分创建的实例,也不是在其上start()调用该方法的实例。由于messages在 中初始化start(),您从中获取的实例ClientGUI.getInstance()尚未messages初始化。因此,当您调用时ClientGUI.getInstance().display(message),它会调用messages.getChildren()...: messagesis null 在该ClientGUI实例中,并且您会得到一个空指针异常。

这里的根本问题是整体结构。你真的不能制作这些单例——尤其是Application子类——因为它们是由框架实例化的。ClientGUI由 JavaFX 启动过程实例化,并且通过您使用FXMLLoader,的方式ClientController被实例化FXMLLoader.load()(因此ClientController.getInstance()可能也没有为您提供您希望的实例)。

至于为什么要尝试使这些类成为单例,这并不是很清楚,当然也没有任何意义。我的猜测是您正在尝试解决您现有的结构,这完全是非标准的。您定义的方法几乎都在不应该对这些方法实现的功能负责的类中。

特别是:Application子类负责应用程序的生命周期。它应该有一个start简单地加载初始视图(FMXL 文件)、将其放入窗口并显示它的方法。如果您有/需要它们,它也可能会启动其他服务。如果需要init(),它可以选择覆盖。stop()

控制器类是负责更新视图的类。这是您应该引用 FXML 文件中定义的 UI 控件的唯一位置,也是您应该修改显示的唯一位置。

模型类 ( Client) 应该完全不了解视图或控制器。它应该仅代表正在呈现的状态或数据。控制器(实际上是演示者,在某些其他架构中这可以由视图完成)负责根据模型表示的数据更新视图。

因此,您可能希望按照以下几行重构应用程序。这取决于您可能需要的其他东西,但这至少应该让您走上可行的轨道:

客户端GUI:

public class ClientGUI extends Application {

    @Override
    public void start(Stage stage) throws Exception {
        Parent root = FXMLLoader.load(getClass().getResource("ClientGUI.fxml"));
        scene = new Scene(root, 480, 360);
        stage.setTitle("MyMessenger");
        stage.setScene(scene);
        stage.setMinWidth(660);
        stage.setMinHeight(495);
        stage.setMaxWidth(990);
        stage.show();
    }
}

客户端控制器:

public class ClientController implements Controller {

    @FXML
    private TextField targetAddress;
    @FXML
    private TextField fieldUsername;
    @FXML
    private TextField fieldPort;
    @FXML
    private VBox messages;
    @FXML
    private TextField input;
    @FXML
    private Button btnConnect;

    private String username = "Anonymous";
    private int port = 2000;

    private Client client ;

    public void initialize() {
        client = new Client();
    }

    @FXML
    private void connect() {

        if (!client.connected()) {
            if (!targetAddress.getText().equals("")) {
                if (!fieldUsername.getText().equals("")) {
                    username = fieldUsername.getText();
                }
                if (!fieldPort.getText().equals("")) {
                    port = Integer.valueOf(fieldPort.getText());
                }

                try {
                    client.connect(targetAddress.getText(), username, port);
                    btnConnect.setText("Disconnect");
                    display("Attention: Successfully connected to server");
                } catch (IOException exc) {
                    display("Attention: Unable to connect to server");
                }
            } else {
                display("Attention: Address required");
            }
        } else {
            client.disconnect();
            display("Attention: Disconnecting...");
            btnConnect.setText("Connect");
        }

    }

    @FXML
    private void message() {

        if (client.connected()) {
            displayAndSend(input.getText());
            input.setText("");
        } else {
            display("Attention: Not connected to server");
            input.setText("");
        }

    }

    public void display(String message) {
        messages.getChildren().add(new Message(message));
    }

    public void displayAndSend(String message) {
        display(message);
        client.sendMessage(message);
    }

}

接着

// Note: why on earth is this Serializable? What state are you intending to serialize???
public class Client implements Serializable {
    private Socket clientSocket;
    private BufferedReader in;
    private PrintWriter out;
    private ObjectOutputStream objectOutputStream;
    private boolean connected = false;
    private static final long serialVersionUID = 0L;


    public void connect(String targetAddress, String username_, int port_) throws IOException {

        clientSocket = new Socket(targetAddress, port_);
        clientSocket.setReuseAddress(true); // Allows same port to be used successively without cool-down
        in = new BufferedReader(new InputStreamReader(clientSocket.getInputStream()));
        out = new PrintWriter(clientSocket.getOutputStream(), true);
        objectOutputStream = new ObjectOutputStream(clientSocket.getOutputStream());
        objectOutputStream.writeObject(username_);
        connected = true;

    }

    public void disconnect() {
        try {
            if (in != null) {
                in.close();
            }
            if (out != null) {
                out.close();
            }
            if (objectOutputStream != null) {
                objectOutputStream.close();
            }
            if (clientSocket != null) {
                clientSocket.close();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }

        connected = false;
    }

    public boolean connected() {
        return connected;
    }



    public void sendMessage(String message) {
        out.println(message);
    }
}

为了使控制器工作,您需要根元素定义一个fx:controller="client.ClientController"属性,并在其中的控件ClientGUI.fxml定义fx:id与控制器类中的字段名称匹配的属性,并指定事件处理程序方法,例如:

<!-- ... -->

<TextField fx:id="targetAddress" />
<TextField fx:id="fieldUserName" />
<TextField fx:id="fieldPort" />

<VBox fx:id="messages" />

<TextField fx:id="input" onAction="#message" />

<Button text="Connect" fx:id="btnConnect" onAction="#connect" />

<!-- ... -->

(请注意,文本字段会在按下回车时触发ActionEvent,因此无需弄乱按键处理程序等)

您可能需要对此进行一些修改以使应用程序的其余功能正常工作,但只要您坚持基本原则:只有控制器应该修改 UI;Client班级应该不了解演示细节等,那么您将走上正确的道路。如果你做了任何东西 static,当然如果你尝试做任何这些单例,你做错了。

于 2016-10-05T14:38:39.040 回答