使用ControlsFX AutoCompletionBinding时出现IllegalAccessError的解决方法
解决JDK17下ControlsFX AutoCompletionBinding的IllegalAccessError模块化问题
问题描述
使用ControlsFX的TextFields.bindAutoCompletion()时触发IllegalAccessError,错误信息如下:
class org.controlsfx.control.textfield.AutoCompletionBinding (in module org.controlsfx.controls) cannot access class com.sun.javafx.event.EventHandlerManager (in module javafx.base) because module javafx.base does not export com.sun.javafx.event to module org.controlsfx.controls
业务场景代码(实时从数据库搜索学生姓名并绑定自动补全):
try { SearchSession searchSession = Search.session(JpaUtil.getEntityManager()); SearchResult<Student> result = searchSession.search(Student.class) .where(f -> f.match() .field("firstName") .matching(search)) .fetch(20); List<Student> list = result.hits(); TextFields.bindAutoCompletion(searchField, list); } catch (Exception ex) { ex.printStackTrace(); }
原因分析
JDK9+引入的模块化系统(JPMS)限制了模块间的内部包访问,com.sun.javafx.event是JavaFX的内部私有包,默认不会对外暴露。旧版本的ControlsFX在实现AutoCompletionBinding时直接调用了这个内部包的EventHandlerManager类,导致JDK17下触发访问权限错误。
解决方案
方案1:添加VM启动参数(快速临时修复)
在JVM启动参数中强制让javafx.base模块向org.controlsfx.controls模块开放内部包:
--add-exports javafx.base/com.sun.javafx.event=org.controlsfx.controls
IDE运行时直接在Run Configuration的VM Options中添加;打包后运行则在启动命令中追加该参数。
方案2:升级ControlsFX到兼容JDK17的版本
ControlsFX从11.1.2版本开始修复了JPMS模块化兼容问题,不再依赖JavaFX的内部私有API。直接升级依赖:
- Maven:
<dependency> <groupId>org.controlsfx</groupId> <artifactId>controlsfx</artifactId> <version>11.1.4</version> <!-- 推荐使用最新稳定版 --> </dependency>
- Gradle:
implementation 'org.controlsfx:controlsfx:11.1.4'
方案3:切换为非模块化运行(如果项目未使用JPMS)
如果项目未显式声明module-info.java,可将项目从modulepath切换为classpath运行,绕过JPMS的权限检查:
- IDE中:在Project Structure里将依赖从Module Libraries改为Global Libraries,或调整编译选项为使用classpath。
- 构建工具:Maven/Gradle中移除模块化相关配置,确保编译和运行时使用classpath而非modulepath。
优化建议
针对实时搜索场景,建议使用AutoCompletionBinding的异步数据源版本,避免数据库查询阻塞UI线程:
TextFields.bindAutoCompletion(searchField, param -> { // 异步执行数据库搜索 CompletableFuture<List<String>> future = CompletableFuture.supplyAsync(() -> { try (SearchSession searchSession = Search.session(JpaUtil.getEntityManager())) { SearchResult<Student> result = searchSession.search(Student.class) .where(f -> f.match().field("firstName").matching(param.getUserText())) .fetch(20); return result.hits().stream().map(Student::getFirstName).toList(); } catch (Exception e) { e.printStackTrace(); return Collections.emptyList(); } }); return future.thenApply(CompletionStage::toList); });
内容的提问来源于stack exchange,提问作者Marko
相关产品推荐
相关产品推荐

