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