在类路径中使用Poiji 4.1.1时,使用自定义填充颜色会导致XSSFColor.getStoredRGB() 抛出AbstractMethodError
问题
我正在尝试在Spring Boot项目中使用Apache POI为 Excel标题单元格设置自定义十六进制颜色(#00458D)。唯一在不抛出错误的情况下可用的方法是使用索引颜色:
headerStyle.setFillForegroundColor(IndexedColors.DARK_BLUE.getIndex());
headerStyle.setFillPattern(FillPatternType.SOLID_FOREGROUND);
但我需要的是确切的颜色 #00458D,所以尝试了以下方法:
尝试1 : ARGB十六进制:
XSSFColor myColor = new XSSFColor();
myColor.setARGBHex("ff00458d");
headerStyle.setFillForegroundColor(myColor);
尝试2 : java.awt.Color:
headerStyle.setFillForegroundColor(new XSSFColor(new java.awt.Color(0, 69, 141), new DefaultIndexedColorMap()));
headerStyle.setFillPattern(FillPatternType.SOLID_FOREGROUND);
尝试3 :带IndexedColorMap的字节数组:
IndexedColorMap colorMap = sheet.getWorkbook().getStylesSource().getIndexedColors();
XSSFColor color = new XSSFColor(new byte[]{0, 69, (byte)141}, colorMap);
headerStyle.setFillForegroundColor(color);
headerStyle.setFillPattern(FillPatternType.SOLID_FOREGROUND);
这三种方法都会抛出相同的错误:
java.lang.AbstractMethodError: Receiver class org.apache.poi.xssf.usermodel.XSSFColor
does not define or inherit an implementation of the resolved method
'abstract byte[] getStoredRGB()' of abstract class org.apache.poi.ss.usermodel.ExtendedColor.
at org.apache.poi.ss.usermodel.ExtendedColor.getRGBOrARGB(ExtendedColor.java:100)
at org.apache.poi.xssf.usermodel.XSSFColor.getARGB(XSSFColor.java:203)
at org.apache.poi.xssf.usermodel.XSSFColor.sameARGB(XSSFColor.java:392)
at org.apache.poi.xssf.usermodel.XSSFColor.equals(XSSFColor.java:423)
at org.apache.poi.xssf.usermodel.extensions.XSSFCellFill.equals(XSSFCellFill.java:186)
at org.apache.poi.xssf.model.StylesTable.putFill(StylesTable.java:529)
at org.apache.poi.xssf.usermodel.XSSFCellStyle.setFillPattern(XSSFCellStyle.java:1002)
根本原因(我认为)
我的classpath上有 Poiji 4.1.1:
<dependency>
<groupId>com.github.ozlerhakan</groupId>
<artifactId>poiji</artifactId>
<version>4.1.1</version>
<scope>compile</scope>
</dependency>
Poiji会在内部捆绑它自己的Apache POI jar。这会造成类路径冲突——在运行时,将加载Poiji较旧版本捆绑的POI的 XSSFColor,它并未实现在较新版本的 ExtendedColor 类中作为抽象方法新增的 getStoredRGB()。因此,任何尝试用自定义RGB颜色实例化 XSSFColor 都会在 setFillPattern() 里的 AbstractMethodError 被触发。
限制
我知道最直接的修复办法是在Poiji依赖中排除POI:
<exclusions>
<exclusion>
<groupId>org.apache.poi</groupId>
<artifactId>poi</artifactId>
</exclusion>
<exclusion>
<groupId>org.apache.poi</groupId>
<artifactId>poi-ooxml</artifactId>
</exclusion>
</exclusions>
然而这是一个已经存在的、在生产中运行着大量Excel功能的大型项目。现在不太可能更改依赖配置。
问题
是否有办法将自定义RGB颜色(#00458D)应用到一个 XSSFCellStyle 填充上,完全避免实例化 XSSFColor 的情况——例如通过原始的 CTFill / CTStylesheet XML操作,或任何其他方法——以便永远不会触发类路径版本冲突?
不胜感激。
解决方案
查看 XSSFCellStyle 的源代码后,可以看到 XSSFColor 仅在 XSSFCellStyle.setFillForegroundColor 中被用来获取 org.openxmlformats.schemas.spreadsheetml.x2006.main.CTColor。而 CTColor 可以从字节数组获取颜色。因此,通过在 XSSFCellStyle 上增加一个方法 setFillForegroundColor(byte[] color) 或 setFillForegroundColor(String color) 来进行扩展似乎很简单,因为从十六进制字符串解码字节数组也不难。
…… 应该是,POI在防止扩展方面并不是特别强健。但庆幸的是,Java提供了 java.lang.reflect。
所以示例:
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.xssf.usermodel.*;
import org.openxmlformats.schemas.spreadsheetml.x2006.main.CTColor;
import org.openxmlformats.schemas.spreadsheetml.x2006.main.CTFill;
import org.openxmlformats.schemas.spreadsheetml.x2006.main.CTPatternFill;
import org.apache.commons.codec.binary.Hex;
import java.lang.reflect.Method;
import java.io.OutputStream;
import java.io.FileOutputStream;
class ApachePOIExtendXSSFCellStyle {
static void setFillForegroundColor(XSSFCellStyle style, String color) throws Exception {
Method getCTFill = XSSFCellStyle.class.getDeclaredMethod("getCTFill");
getCTFill.setAccessible(true);
CTFill ct = (CTFill)getCTFill.invoke(style);
CTPatternFill ptrn = ct.getPatternFill();
if(color == null) {
if(ptrn != null && ptrn.isSetFgColor()) ptrn.unsetFgColor();
} else {
if(ptrn == null) ptrn = ct.addNewPatternFill();
byte[] rgbB = Hex.decodeHex(color);
CTColor ctColor = CTColor.Factory.newInstance();
ctColor.setRgb(rgbB);
ptrn.setFgColor(ctColor);
}
Method addFill = XSSFCellStyle.class.getDeclaredMethod("addFill", CTFill.class);
addFill.setAccessible(true);
addFill.invoke(style, ct);
}
public static void main(String[] args) throws Exception {
Workbook wb = new XSSFWorkbook();
CellStyle style = wb.createCellStyle();
setFillForegroundColor((XSSFCellStyle)style, "FF00458D");
style.setFillPattern(FillPatternType.SOLID_FOREGROUND);
Sheet sheet = wb.createSheet();
Row row = sheet.createRow(1);
Cell cell = row.createCell(1);
cell.setCellStyle(style);
try (OutputStream fileOut = new FileOutputStream("workbook.xlsx")) {
wb.write(fileOut);
}
}
}