Problem descriptions are available in the EasyExcel discussion board
Address: www.yuque.com/easyexcel/t…
solution
Because do not understand EasyExcel internal execution logic, so the idea is to strip down the source code to see the business logic
EasyExcel performs logical analysis
For us users, we’ve only written these few lines of code
@LogT
@PostMapping(value = "/export")
@SneakyThrows
public void export(@RequestBody UserDTO user, HttpServletResponse response){
List<UserVO> userVOList = userService.selectList(user);
ExcelUtil.prepareExport(response, "User List");
EasyExcel.write(response.getOutputStream(), UserVO.class)
.registerWriteHandler(ExcelUtil.defaultCellStyle())
.registerWriteHandler(ExcelUtil.defaultWidthStyle())
.sheet("User List")
.doWrite(userVOList);
}
Copy the code
EasyExcel allows you to create a file, write to a file stream, close a stream, and so on, line by line
write()
public static ExcelWriterBuilder write(OutputStream outputStream, Class head) {
ExcelWriterBuilder excelWriterBuilder = new ExcelWriterBuilder();
excelWriterBuilder.file(outputStream);
if(head ! =null) {
excelWriterBuilder.head(head);
}
return excelWriterBuilder;
}
Copy the code
Got a constructor, presumably knowing that it created a new file and returned the Builder object, so you can use the constructor syntax later
registerWriteHandler()
Register interceptor, this is EasyExcel exposed to user custom processing logic interface, interceptor as the name implies is to execute our defined logic in the specified place, in EasyExcel, there are many interceptor interface for us to implement, here only say write operations
The top-level interface for write operations is WriteHandler, and the interceptor interface for the four dimensions of cell, row, sheet and workbook is easy to understand. I operated HorizontalCellStyleStrategy comes from AbstractCellStyleStrategy, and an abstract class implements CellWriteHandler respectively, WorkbookWriteHandler, NotRepeatExecutor
After all, it’s the interceptor. What did it do
public T registerWriteHandler(WriteHandler writeHandler) {
if (parameter().getCustomWriteHandlerList() == null) {
parameter().setCustomWriteHandlerList(new ArrayList<WriteHandler>());
}
parameter().getCustomWriteHandlerList().add(writeHandler);
return self();
}
Copy the code
Click on it to see that he has added the interceptor object to the WriteBasicParameter maintenance customWriteHandlerList collection
sheet()
This link is very long
-
ExcelWriterSheetBuilder sheet method
-
The build method in the sheet method
-
Has been down to WriteContextImpl initCurrentWorkbookHolder method in the constructor
-
WriteWorkbookHolder constructor
-
Call the parent class of super (writeWorkbook, null, writeWorkbook getConvertAllFiled ());
public AbstractWriteHolder(WriteBasicParameter writeBasicParameter, AbstractWriteHolder parentAbstractWriteHolder, Boolean convertAllFiled) {
/ / to omit...
// Initialization property
this.excelWriteHeadProperty = new ExcelWriteHeadProperty(this, getClazz(), getHead(), convertAllFiled);
// Compatible with old code
compatibleOldCode(writeBasicParameter);
// Create a new interceptor collection
List<WriteHandler> handlerList = new ArrayList<WriteHandler>();
// Divide the attribute resolution of the annotation into unblocked interceptors and put them into the collection
initAnnotationConfig(handlerList, writeBasicParameter);
// Add our custom interceptor to the collection
if(writeBasicParameter.getCustomWriteHandlerList() ! =null
&& !writeBasicParameter.getCustomWriteHandlerList().isEmpty()) {
handlerList.addAll(writeBasicParameter.getCustomWriteHandlerList());
}
// Interceptor processing
Discard duplicate interceptors. 3. Convert each interceptor to a different level
this.ownWriteHandlerMap = sortAndClearUpHandler(handlerList);
Map<Class<? extends WriteHandler>, List<WriteHandler>> parentWriteHandlerMap = null;
if(parentAbstractWriteHolder ! =null) {
parentWriteHandlerMap = parentAbstractWriteHolder.getWriteHandlerMap();
} else {
handlerList.addAll(DefaultWriteHandlerLoader.loadDefaultHandler(useDefaultStyle));
}
this.writeHandlerMap = sortAndClearUpAllHandler(handlerList, parentWriteHandlerMap);
}
Copy the code
As I mentioned above, it does a bunch of preparatory work, and what it ends up getting is sort of a collection of different levels of interceptors, like four row interceptors, five cell interceptors, and so on, all ready to put into the context object, the WriteContext, that it actually uses when it writes
There are two methods that are important
-
initAnnotationConfig()
-
sortAndClearUpHandler()
More relevant to this question is the second
- If the NotRepeatExecutor interface is implemented, the same unique value command retains an interceptor
- If the Order interface is implemented, the Order is prioritized, and the default values generated from annotations and those not implemented with the Order interface are INTERGER minima, meaning that the default processor logic must be executed first, no matter what
write()
Directly into the same point, locating the addContent () the method of excelWriteAddExecutor. Add this method code (data)
public void add(List data) {
if (CollectionUtils.isEmpty(data)) {
data = new ArrayList();
}
WriteSheetHolder writeSheetHolder = writeContext.writeSheetHolder();
int newRowIndex = writeSheetHolder.getNewRowIndexAndStartDoWrite();
if(writeSheetHolder.isNew() && ! writeSheetHolder.getExcelWriteHeadProperty().hasHead()) { newRowIndex += writeContext.currentWriteHolder().relativeHeadRowIndex(); }// BeanMap is out of order,so use sortedAllFiledMap
Map<Integer, Field> sortedAllFiledMap = new TreeMap<Integer, Field>();
int relativeRowIndex = 0;
for (Object oneRowData : data) {
intn = relativeRowIndex + newRowIndex; addOneRowOfDataToExcel(oneRowData, n, relativeRowIndex, sortedAllFiledMap); relativeRowIndex++; }}Copy the code
When we first looked at the source code, we found one class in particular, WriteHandlerUtils, which is similar to our interceptor implementation and is used in EasyExcel to execute the anonymous implementation of the native interceptor
Go directly to the addOneRowOfDataToExcel() method, which handles the row, I want the cell style, should handle that block in the cell, and I DEBUG to show that I annotate @contentStyle and only create workbook interceptors and cell interceptors, I won’t say why these two, but if you look at the source code, you’ll see, so let’s move on to the unit resolution logic, the addBasicTypeToExcel() method, okay
private void addBasicTypeToExcel(List<Object> oneRowData, Row row, int relativeRowIndex) {
if (CollectionUtils.isEmpty(oneRowData)) {
return;
}
Map<Integer, Head> headMap = writeContext.currentWriteHolder().excelWriteHeadProperty().getHeadMap();
int dataIndex = 0;
int cellIndex = 0;
for (Map.Entry<Integer, Head> entry : headMap.entrySet()) {
if (dataIndex >= oneRowData.size()) {
return;
}
cellIndex = entry.getKey();
Head head = entry.getValue();
doAddBasicTypeToExcel(oneRowData, head, row, relativeRowIndex, dataIndex++, cellIndex);
}
// Finish
if (dataIndex >= oneRowData.size()) {
return;
}
if(cellIndex ! =0) {
cellIndex++;
}
int size = oneRowData.size() - dataIndex;
for (int i = 0; i < size; i++) {
doAddBasicTypeToExcel(oneRowData, null, row, relativeRowIndex, dataIndex++, cellIndex++); }}Copy the code
Go to the doAddBasicTypeToExcel() method
private void doAddBasicTypeToExcel(List<Object> oneRowData, Head head, Row row, int relativeRowIndex, int dataIndex,
int cellIndex) {
WriteHandlerUtils.beforeCellCreate(writeContext, row, head, cellIndex, relativeRowIndex, Boolean.FALSE);
Cell cell = WorkBookUtil.createCell(row, cellIndex);
WriteHandlerUtils.afterCellCreate(writeContext, cell, head, relativeRowIndex, Boolean.FALSE);
Object value = oneRowData.get(dataIndex);
CellData cellData = converterAndSet(writeContext.currentWriteHolder(), value == null ? null : value.getClass(),
cell, value, null, head, relativeRowIndex);
WriteHandlerUtils.afterCellDispose(writeContext, cellData, cell, head, relativeRowIndex, Boolean.FALSE);
}
Copy the code
In WriteHandlerUtils. AfterCellDispose performed for annotations cell rendering
Back to the question
OK, why do we encounter the problem, because based on AbstractCellStyleStrategy and the style of the implementation are realized NotRepeatExecutor interface, so that when the only have the same value (I didn’t set the default values, CellStyleStrategy), So it will be overwritten by the default style
understand
So how do we solve this problem now
I tried to
-
Overriding the uniqueValue method for NotRepeatExecutor, set a different value
-
The Order interface is set to the Integer minimum value
As a result, my style overwrites the style of the annotations. Although it is a little more beautiful, it still cannot meet my needs. The most perfect situation is to achieve global style unification through custom style strategy, and then local style adjustment through style annotations
The Debug code finds that the reason for this is that our interceptor has a lower priority than the custom style anonymous implementation class interceptor, so the solution is to adjust the Order of interceptors. When both are the minimum Order value, the last to be added is the last to be executed
After analyzing the code, we found that anonymous inner classes generated by custom annotations are executed first.
But accidentally swapping the sheet() method with the registerHandler() method worked for me, so keep looking at the source code
List<WriteHandler> handlerList = new ArrayList<WriteHandler>();
// Initialization Annotation
initAnnotationConfig(handlerList, writeBasicParameter);
if(writeBasicParameter.getCustomWriteHandlerList() ! =null
&& !writeBasicParameter.getCustomWriteHandlerList().isEmpty()) {
handlerList.addAll(writeBasicParameter.getCustomWriteHandlerList());
}
this.ownWriteHandlerMap = sortAndClearUpHandler(handlerList);
Map<Class<? extends WriteHandler>, List<WriteHandler>> parentWriteHandlerMap = null;
if(parentAbstractWriteHolder ! =null) {
parentWriteHandlerMap = parentAbstractWriteHolder.getWriteHandlerMap();
} else {
handlerList.addAll(DefaultWriteHandlerLoader.loadDefaultHandler(useDefaultStyle));
}
// Reprocess the interceptor chain
this.writeHandlerMap = sortAndClearUpAllHandler(handlerList, parentWriteHandlerMap);
Copy the code
I’m going to go to the sortAndClearUpAllHandler method, which I didn’t look at very carefully before, and for interceptors that are handled both ways, it’s going to do a reprocessing in this method, so let’s go in there
protected Map<Class<? extends WriteHandler>, List<WriteHandler>> sortAndClearUpAllHandler(
List<WriteHandler> handlerList, Map<Class<? extends WriteHandler>, List<WriteHandler>> parentHandlerMap) {
// add
if(parentHandlerMap ! =null) {
List<WriteHandler> parentWriteHandler = parentHandlerMap.get(WriteHandler.class);
if (!CollectionUtils.isEmpty(parentWriteHandler)) {
handlerList.addAll(parentWriteHandler);
}
}
return sortAndClearUpHandler(handlerList);
}
Copy the code
As you can see, it re-fetches the interceptors from parentHandlerMap and then places them in the current interceptor collection. By doing so, it appends the previous interceptors to the new collection, resetting their order, so that it does what I did before
Adjust the order back to Debug, and find that in the previous step, the three levels will be treated as the same level, so the order remains the same
OK, we have a solution
- A custom style policy needs to override the uniqueValue method of NotRepeatExecutor to set a different value
- The Order interface is set to the Integer minimum value
- When calling the export code, write the sheet method before the registerWriteHandler method
Problem solved!