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

  1. ExcelWriterSheetBuilder sheet method

  2. The build method in the sheet method

  3. Has been down to WriteContextImpl initCurrentWorkbookHolder method in the constructor

  4. WriteWorkbookHolder constructor

  5. 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

  1. initAnnotationConfig()

  2. sortAndClearUpHandler()

    More relevant to this question is the second

    1. If the NotRepeatExecutor interface is implemented, the same unique value command retains an interceptor
    2. 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

  1. Overriding the uniqueValue method for NotRepeatExecutor, set a different value

  2. 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

  1. A custom style policy needs to override the uniqueValue method of NotRepeatExecutor to set a different value
  2. The Order interface is set to the Integer minimum value
  3. When calling the export code, write the sheet method before the registerWriteHandler method

Problem solved!