问题描述
在 Java 17 环境中使用 EasyPoi 进行 Excel 导入操作时(ExcelImportUtil.importExcelMore),出现以下反射访问异常:
java
java.lang.reflect.InaccessibleObjectException: Unable to make field final boolean java.util.LinkedHashMap.accessOrder accessible: module java.base does not "opens java.util" to unnamed module @6da21078
at java.base/java.lang.reflect.AccessibleObject.checkCanSetAccessible(AccessibleObject.java:354)
at java.base/java.lang.reflect.AccessibleObject.checkCanSetAccessible(AccessibleObject.java:297)
at java.base/java.lang.reflect.Field.checkCanSetAccessible(Field.java:178)
at java.base/java.lang.reflect.AccessibleObject.setAccessible(AccessibleObject.java:130)
at org.apache.commons.lang3.builder.ReflectionToStringBuilder.appendFieldsIn(ReflectionToStringBuilder.java:649)
at org.apache.commons.lang3.builder.ReflectionToStringBuilder.toString(ReflectionToStringBuilder.java:853)
at org.apache.commons.lang3.builder.ReflectionToStringBuilder.toString(ReflectionToStringBuilder.java:387)
at org.apache.commons.lang3.builder.ReflectionToStringBuilder.toString(ReflectionToStringBuilder.java:156)
at cn.afterturn.easypoi.excel.imports.ExcelImportService.importExcel(ExcelImportService.java:270)
at cn.afterturn.easypoi.excel.imports.ExcelImportService.importExcelByIs(ExcelImportService.java:482)
at cn.afterturn.easypoi.excel.ExcelImportUtil.importExcelMore(ExcelImportUtil.java:99)
原因分析:
-
Java 模块系统限制
Java 9 引入了模块系统(JPMS),在 Java 17 中进一步加强了封装性。java.base 模块中的 java.util 包默认不对未命名模块开放反射访问权限。
-
EasyPoi 内部实现
EasyPoi 在处理 Excel 导入时,内部使用了 Apache Commons Lang 的 ReflectionToStringBuilder 进行反射操作,试图访问 LinkedHashMap.accessOrder 字段。
-
依赖版本兼容性
我这边使用的是新版本的 EasyPoi (4.5.0) 和 Commons Lang3 (3.17.0),在某些 Java 17+ 环境中仍然会遇到此问题。
解决方案:
方案一:添加 JVM 参数
在启动命令中添加,详细可看官网对此描述官网
bash
java --add-opens java.management/sun.management=ALL-UNNAMED
方案二:使用其他方式实现,例如Apache POI、Easyexcel等
java
public void import(MultipartFile vettingFile) {
// 创建 DataFormatter 用于格式化单元格值
DataFormatter dataFormatter = new DataFormatter();
InputStream inputStream = null;
Workbook workbook = null;
List<Map<String, Object>> resultList = new ArrayList<>();
try (InputStream inputStream= vettingFile.getInputStream()) {
workbook = new XSSFWorkbook(inputStream);
// 解析 Excel 文件
Sheet sheet = workbook.getSheetAt(0);
// 读取标题行(第5行,索引4)
Row headerRow = sheet.getRow(4);
if (headerRow == null) {
return Result.succeed("analyzing-import-file-failed");
}
// 构建列映射
Map<Integer, String> columnMapping = new HashMap<>();
for (int i = 0; i < headerRow.getLastCellNum(); i++) {
Cell cell = headerRow.getCell(i);
if (cell != null) {
// 清理列名:去除换行符和前后空格
String columnName = dataFormatter.formatCellValue(cell)
.replace("\n", " ")
.replace("\r", "")
.trim();
if (!columnName.isEmpty()) {
columnMapping.put(i, columnName);
}
}
}
// 读取数据行(从第6行开始,索引5)
for (int i = 5; i <= sheet.getLastRowNum(); i++) {
Row row = sheet.getRow(i);
if (row == null) continue;
Map<String, Object> rowData = new HashMap<>();
boolean hasData = false;
for (Map.Entry<Integer, String> entry : columnMapping.entrySet()) {
int colIndex = entry.getKey();
String columnName = entry.getValue();
Cell cell = row.getCell(colIndex);
// 清理单元格值
String cellValue = dataFormatter.formatCellValue(cell)
.replace("\n", " ")
.replace("\r", "")
.trim();
if (!cellValue.isEmpty()) {
hasData = true;
}
rowData.put(columnName, cellValue);
}
if (hasData) {
resultList.add(rowData);
}
}
} catch (Exception ex) {
log.error("Analyze import file exception: ", ex);
} finally {
// 关闭资源
closeResources(workbook, inputStream, poifs);
}
}
// 资源关闭辅助方法
private void closeResources(Workbook workbook, InputStream inputStream) {
try {
if (workbook != null) {
workbook.close();
}
if (inputStream != null) {
inputStream.close();
}
} catch (Exception ex) {
log.error("Close resources exception: ", ex);
}
}