java - Guava 事件总线 : NullPointerException when Posting from Thread to UI

标签 java multithreading javafx guava event-bus

我正在为自己的学习创建一个简单的聊天应用程序(桌面应用程序),并且我正在为我的客户端和服务器使用 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());
        }
    }
}

从我想发布客户端状态的客户端处理程序:

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 的此实现是否有效,我尝试从 Subscribe 函数中执行 println 并且它有效, 但是当我尝试 label_status.setText(status); 更改 LabelText 时,出现 java.lang.NullPointerException 错误。

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

注意:我在此应用程序中使用 JavaFX

更新:

我放弃使用Guava Eventbus,我用了greenrobot/EventBus与它的latest现在 jar 。

最佳答案

当加载和解析 FXML 文件时,

@FXML 注入(inject)的字段由 Controller 中的 FXMLLoader 初始化。您向事件总线注册的对象不是 Controller (它只是您创建的同一类的实例),因此 label_status 不会在向事件总线注册的对象中进行初始化。

您需要向事件总线注册实际 Controller ,并从客户端处理程序发布到该事件总线。您也不应该在客户端处理程序中引用 Controller (或其类):首先使用事件总线的全部目的是允许您解耦应用程序的这些部分。

所以你的客户端处理程序应该类似于

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**/
    }
}

然后,在组装应用程序时,您将执行以下操作:

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...

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

如果您愿意,您甚至可以更进一步,仅使用标准 Java API 类将客户端处理程序与事件总线解耦(这里的重点是 ClientHandler 需要的只是“处理 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));
    }
}

关于java - Guava 事件总线 : NullPointerException when Posting from Thread to UI,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/47508225/

相关文章:

java - 多线程httpClient

java - 生产者/消费者线程根本没有输出数据

带有自定义单元格工厂的 javafx ListView 不保留所选单元格

java - 单链表递归快速排序的基本情况

java - MVC : Value in dropdown menu doesn't set to selected value - remains 0

java - 如何向我的股票投资组合添加头寸,目前收到有关不兼容类型无法转换为 int 或 String 的错误

java - 将Java连接到MySQL数据库

两个线程可以写入同一数组的不同元素吗?

java - 调用 setGridLinesVisible(true) 时,GridPane 布局调试行未按预期显示

java - 如何设置 ScheduledExecutorService 在用户退出程序时结束?