Guava Eventbus:从线程发布到 UI 时出现 NullPointerException

Guava Eventbus: NullPointerException when Posting from Thread to UI

我正在为自己的学习创建一个简单的聊天应用程序(桌面应用程序),我正在为我的客户端和服务器使用 netty 库。

我从线程启动客户端:new Thread(new Client()).start();,我从我的 Helper Class 执行此操作。 当客户端连接到服务器时,我想访问 MainController 并将其上的 Label 设置为 Connected。 我正在使用 Guava Eventbus 来完成此操作。

我做了下面的代码来实现它。

从我的 MainController 订阅将更改标签文本的函数:

public class MainController implements Initializable{

    @FXML Label label_status;

    public MainController(){}

    @Override
    public void initialize(URL location, ResourceBundle resources) {
            /**Some Code Here...**/
    }

    /**Subscribe Eventbus function**/
    @Subscribe
    public void changeLabelStatus(String status) {
        try{
            label_status.setText(status);
        }catch (Exception e){
            System.out.println(TAG + "Failed to Change the status of Label. >> " + e.toString());
        }
    }
}

从我想 post 客户状态的客户处理程序:

public class ClientHandler extends SimpleChannelInboundHandler<Object>{

    EventBus eventBus;
    MainController mainController;

    public ClientHandler(){
        eventBus = new EventBus();
        mainController = new MainController();
        eventBus.register(mainController);
    }

    /**Change the Status when the Client become connected to Server**/
    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        System.out.println(TAG + "Successfully Connected to Server.);

        eventBus.post("Connected"); /**Post here**/
    }
}

为了检查 EventBus 的这个实现是否有效,我尝试从订阅函数 println 并且它有效, 但是当我尝试 label_status.setText(status); 更改 LabelText 时,我得到 java.lang.NullPointerException 错误。

我不知道为什么,这是我第一次使用这两个库, 我阅读了 EventBus 的指南和示例,并从我的理解中了解了我是如何做到的。 我的代码有什么问题?我怎样才能达到我想要的?

注意:我正在为这个应用程序使用 JavaFX

更新:

我放弃使用Guava Eventbus,我现在使用greenrobot/EventBus with it's latest jar。

@FXML-注入的字段在加载和解析 FXML 文件时由控制器中的 FXMLLoader 初始化。您向事件总线注册的对象不是控制器(它只是您创建的相同 class 的实例),因此 label_status 不会在向事件总线注册的对象中初始化.

您需要向事件总线注册实际控制器,并 post 从您的客户端处理程序注册到该事件总线。您也不应该在客户端处理程序中引用控制器(或其 class):首先使用事件总线的全部意义在于允许您分离应用程序的这些部分。

因此您的客户端处理程序应该类似于

public class ClientHandler extends SimpleChannelInboundHandler<Object>{

    private final EventBus eventBus;

    public ClientHandler(EventBus eventBus){
        this.eventBus = eventBus;
    }

    /**Change the Status when the Client become connected to Server**/
    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        System.out.println(TAG + "Successfully Connected to Server.);

        eventBus.post("Connected"); /**Post here**/
    }
}

然后在您 assemble 您的应用程序时,您将按照以下几行做一些事情:

EventBus eventBus = new EventBus();
ClientHandler clientHandler = new ClientHandler(eventBus);
// ...
FXMLLoader loader = new FXMLLoader(getClass().getResource("/path/to/fxml/file"));
Parent root = loader.load();
MainController controller = loader.getController();
eventBus.register(controller);
Scene scene = new Scene(root);
// put scene in stage and show stage, etc...

将事件总线获取到客户端处理程序可能比上面的代码稍微复杂一些,但它应该为您提供了基本思路。 (如果这里的事情变得太复杂,您可以考虑使用依赖注入框架,例如 Spring 或 Guice 将事件总线注入客户端处理程序,并创建自动注册到事件总线的控制器。)

如果你愿意,你甚至可以更进一步,将客户端处理程序与事件总线分离,只需使用标准 Java API classes(这里的重点是ClientHandler 需要的只是 "something that processes a String"):

public class ClientHandler extends SimpleChannelInboundHandler<Object>{

    private final Consumer<String> statusUpdate ;

    public ClientHandler(Consumer<String> statusUpdate) {
        this.statusUpdate = statusUpdate ;
    }

    /**Change the Status when the Client become connected to Server**/
    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        System.out.println(TAG + "Successfully Connected to Server.);

        statusUpdate.accept("Connected"); /**Post here**/
    }
}

然后

EventBus eventBus = new EventBus();
ClientHandler clientHandler = new ClientHandler(eventBus::post);
// etc ...

最后,请注意,由于您的客户端处理程序似乎是 运行 在后台线程上,您需要在 FX 应用程序线程上安排对标签的更新:

public class MainController implements Initializable{

    @FXML Label label_status;

    @Override
    public void initialize(URL location, ResourceBundle resources) {
            /**Some Code Here...**/
    }

    /**Subscribe Eventbus function**/
    @Subscribe
    public void changeLabelStatus(String status) {
        Platform.runLater(() -> label_status.setText(status));
    }
}