Vaadin Flow 23网格编辑器快捷键触发偶发NullPointerException问题
Vaadin Flow 23网格编辑器快捷键偶发NullPointerException分析与解决
问题概述
在Vaadin Flow 23应用中,激活网格编辑器并使用快捷键时,测试服务器偶发本地无法复现的空指针异常,异常栈如下:
java.lang.NullPointerException: Cannot invoke "com.vaadin.flow.component.Component.isVisible()" because "component" is null 2023-11-13 08:10:45.030 ERROR 1 --- [nio-8000-exec-9] c.v.flow.server.DefaultErrorHandler : at com.vaadin.flow.internal.nodefeature.ElementListenerMap.lambda$fireEvent$2(ElementListenerMap.java:447) ~[flow-server-23.3.18.jar!/:23.3.18] at com.vaadin.flow.component.ComponentEventBus.lambda$addDomTrigger$dd1b7957$1(ComponentEventBus.java:292) ~[flow-server-23.3.18.jar!/:23.3.18] at com.vaadin.flow.component.ComponentEventBus.handleDomEvent(ComponentEventBus.java:475) ~[flow-server-23.3.18.jar!/:23.3.18] at com.vaadin.flow.component.ComponentEventBus.fireEventForListener(ComponentEventBus.java:233) ~[flow-server-23.3.18.jar!/:23.3.18] at com.vaadin.flow.component.ShortcutRegistration.lambda$updateHandlerListenerRegistration$89e5c563$1(ShortcutRegistration.java:551) ~[flow-server-23.3.18.jar!/:23.3.18] at com.vaadin.flow.component.ShortcutRegistration.fireShortcutEvent(ShortcutRegistration.java:591) ~[flow-server-23.3.18.jar!/:23.3.18] at com.vaadin.flow.component.ShortcutRegistration.ancestorsOrSelfAreVisible(ShortcutRegistration.java:598) ~[flow-server-23.3.18.jar!/:23.3.18] at java.base/java.util.ArrayList.forEach(ArrayList.java:1511) ~[na:na] at com.vaadin.flow.internal.nodefeature.ElementListenerMap.fireEvent(ElementListenerMap.java:447) ~[flow-server-23.3.18.jar!/:23.3.18] at com.vaadin.flow.server.communication.rpc.EventRpcHandler.handleNode(EventRpcHandler.java:62) ~[flow-server-23.3.18.jar!/:23.3.18] at com.vaadin.flow.spring.SpringServlet.service(SpringServlet.java:106) ~[vaadin-spring-23.3.18.jar!/:na] at com.vaadin.flow.server.VaadinServlet.service(VaadinServlet.java:369) ~[flow-server-23.3.18.jar!/:23.3.18] at com.vaadin.flow.server.VaadinService.handleRequest(VaadinService.java:1560) ~[flow-server-23.3.18.jar!/:23.3.18] at com.vaadin.flow.server.SynchronizedRequestHandler.handleRequest(SynchronizedRequestHandler.java:40) ~[flow-server-23.3.18.jar!/:23.3.18] at com.vaadin.flow.server.communication.ServerRpcHandler.handleRpc(ServerRpcHandler.java:320) ~[flow-server-23.3.18.jar!/:23.3.18] at com.vaadin.flow.server.communication.UidlRequestHandler.synchronizedHandleRequest(UidlRequestHandler.java:115) ~[flow-server-23.3.18.jar!/:23.3.18] at com.vaadin.flow.server.communication.ServerRpcHandler.handleInvocations(ServerRpcHandler.java:419) ~[flow-server-23.3.18.jar!/:23.3.18] at com.vaadin.flow.server.communication.ServerRpcHandler.handleInvocationData(ServerRpcHandler.java:438) ~[flow-server-23.3.18.jar!/:23.3.18] at com.vaadin.flow.server.communication.ServerRpcHandler.lambda$handleInvocations$1(ServerRpcHandler.java:419) ~[flow-server-23.3.18.jar!/:23.3.18] at com.vaadin.flow.server.communication.rpc.AbstractRpcInvocationHandler.handle(AbstractRpcInvocationHandler.java:75) ~[flow-server-23.3.18.jar!/:23.3.18]
现有实现方案
为了实现仅在编辑器开启时激活快捷键,先后尝试两种实现:
初始实现:绑定按钮快捷键
editColumn.setEditorComponent(getEditRowButtonsLayout()); private HorizontalLayout getEditRowButtonsLayout() { saveEditButton = new Button(); cancelEditButton = new Button(); saveEditButton.addClickListener(event -> save()); cancelEditButton.addClickListener(event -> editor.cancel()); return new HorizontalLayout(saveEditButton, cancelEditButton); } editor.addOpenListener(event -> registerShortcuts()); editor.addCloseListener(event -> removeShortcuts()); private void registerShortcuts() { enterShortcut = saveEditButton.addClickShortcut(Key.ENTER); numpadEnterShortcut = saveEditButton.addClickShortcut(Key.NUMPAD_ENTER); escapeShortcut = cancelEditButton.addClickShortcut(Key.ESCAPE); } private void removeShortcuts() { enterShortcut.remove(); numpadEnterShortcut.remove(); escapeShortcut.remove(); }
修改后实现:向UI注册全局快捷键
private void registerShortcuts() { enterShortcut = UI.getCurrent().addShortcutListener(this::saveEdited, Key.ENTER); numpadEnterShortcut = UI.getCurrent().addShortcutListener(this::saveEdited, Key.NUMPAD_ENTER); escapeShortcut = UI.getCurrent().addShortcutListener(event -> editor.cancel(), Key.ESCAPE); }
两种实现均偶发相同异常,且本地无法复现。
问题根源分析
从异常栈可以定位,问题出在ShortcutRegistration.ancestorsOrSelfAreVisible()方法中:框架遍历组件祖先链检查可见性时,未处理组件为null的情况,直接调用isVisible()导致NPE。
这属于Vaadin Flow框架的内部bug,触发原因是竞态条件:
- 编辑器关闭时的快捷键移除操作,与客户端触发的快捷键事件在服务器端存在时序差,比如事件到达时快捷键还未完成移除
- 组件实例已被销毁/从DOM移除,但监听器仍被引用,导致检查可见性时组件变为null
本地难以复现是因为本地环境的网络延迟、线程调度速度与测试服务器存在差异,竞态条件触发概率极低。
临时解决方案
可通过以下方式规避异常:
- 添加编辑器状态前置检查:在快捷键回调中先确认编辑器仍处于打开状态,避免无效操作:
private void registerShortcuts() { enterShortcut = UI.getCurrent().addShortcutListener(event -> { if (editor.isOpen()) { saveEdited(); } }, Key.ENTER); numpadEnterShortcut = UI.getCurrent().addShortcutListener(event -> { if (editor.isOpen()) { saveEdited(); } }, Key.NUMPAD_ENTER); escapeShortcut = UI.getCurrent().addShortcutListener(event -> { if (editor.isOpen()) { editor.cancel(); } }, Key.ESCAPE); }
- 安全移除快捷键:移除时增加空指针判断,并置空引用避免内存泄漏:
private void removeShortcuts() { if (enterShortcut != null) { enterShortcut.remove(); enterShortcut = null; } if (numpadEnterShortcut != null) { numpadEnterShortcut.remove(); numpadEnterShortcut = null; } if (escapeShortcut != null) { escapeShortcut.remove(); escapeShortcut = null; } }
- 升级框架版本:该问题在Vaadin Flow 23.3.x的后续补丁版本或24+稳定版中已被官方修复,升级到最新兼容版本可彻底解决。
结论
此问题属于Vaadin Flow框架事件机制的bug,并非业务实现逻辑错误。框架的快捷键可见性检查逻辑未处理组件为null的边界情况,导致竞态条件下触发NPE。通过添加状态检查或升级框架版本可解决该问题。
内容的提问来源于stack exchange,提问作者Ilias
相关产品推荐
相关产品推荐

