How to Build an Asynchronous Excel Upload Service with AOP and Thread Pools

This article explains how to handle time‑consuming Excel uploads by using a template‑method design, wrapping the process with an AOP aspect, and executing the actual upload asynchronously in a thread pool, while recording logs and error details for later inspection.

Programmer DD
Programmer DD
Programmer DD
How to Build an Asynchronous Excel Upload Service with AOP and Thread Pools

Background

Many systems allow users to upload Excel files, which are parsed on the backend, validated, and stored in the database.

If the Excel contains many rows or complex parsing logic, the parsing and validation can become time‑consuming, causing synchronous APIs to time out and preventing error messages from being returned to the front end.

A common solution is to start a child thread for parsing, return immediately from the main thread, and let the child thread record the upload status and validation results in the database, while a separate page queries the status.

Writing such thread‑pool and logging code for every upload task is repetitive and pollutes business logic, reducing readability.

From a generic perspective, this scenario fits the Template Method design pattern: define an abstract class with an abstract upload method and provide common logging functionality.

However, the template approach still has two drawbacks: the upload method’s parameter structure is fixed, making changes difficult; and each upload service still needs to extend the abstract class, which is not very convenient.

To address these issues, the author proposes using an AOP‑based wrapper that treats the upload process similarly to a transaction: perform initialization before the actual operation, and handle success or failure afterwards.

Implementation

First, define a log entity:

public class FileUploadLog {
    private Integer id;
    private String batchNo; // unique code
    private String key; // file key on file server
    private String fileName; // error log file name
    private Integer status; // upload status
    private String createName; // uploader
    private String uploadType;
    private Date endTime;
    private Date startTime;
}

Then define an enum to record the upload type:

public enum UploadType {
    UNKNOWN(1, "未知"),
    TYPE2(2, "类型2"),
    TYPE1(3, "类型1");
    private int code;
    private String desc;
    private static Map<Integer, UploadType> map = new HashMap<>();
    static {
        for (UploadType v : UploadType.values()) {
            map.put(v.code, v);
        }
    }
    UploadType(int code, String desc) { this.code = code; this.desc = desc; }
    public int getCode() { return code; }
    public String getDesc() { return desc; }
    public static UploadType getByCode(Integer code) { return map.get(code); }
}

Define an annotation to mark upload methods:

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
public @interface Upload {
    UploadType type() default UploadType.UNKNOWN;
}

Implement an aspect that creates a thread pool, records a log entry, executes the annotated method asynchronously, and writes success or failure information back to the database:

@Component
@Aspect
@Slf4j
public class UploadAspect {
    public static ThreadFactory commonThreadFactory = new ThreadFactoryBuilder()
        .setNameFormat("upload-pool-%d")
        .setPriority(Thread.NORM_PRIORITY)
        .build();
    public static ExecutorService uploadExecuteService = new ThreadPoolExecutor(
        10, 20, 300L, TimeUnit.SECONDS,
        new LinkedBlockingQueue<>(1024), commonThreadFactory,
        new ThreadPoolExecutor.AbortPolicy());

    @Pointcut("@annotation(com.aaa.bbb.Upload)")
    public void uploadPoint() {}

    @Around("uploadPoint()")
    public Object uploadControl(ProceedingJoinPoint pjp) {
        MethodSignature signature = (MethodSignature) pjp.getSignature();
        Upload annotation = signature.getMethod().getAnnotation(Upload.class);
        UploadType type = annotation == null ? UploadType.UNKNOWN : annotation.type();
        String batchNo = UUID.randomUUID().toString().replace("-", "");
        writeLogToDB(batchNo, type, new Date());
        uploadExecuteService.submit(() -> {
            try {
                String errorMessage = (String) pjp.proceed();
                if (StringUtils.isEmpty(errorMessage)) {
                    writeSuccessToDB(batchNo);
                } else {
                    fail(errorMessage, batchNo);
                }
            } catch (Throwable e) {
                LOGGER.error("Import failed:", e);
                fail(e.toString(), batchNo);
            }
        });
        return new Object();
    }

    private void fail(String message, String batchNo) {
        String s3Key = UUID.randomUUID().toString().replace("-", "");
        String fileName = "错误日志_" + DateUtil.dateToString(new Date(), "yyyy年MM月dd日HH时mm分ss秒") + ExportConstant.txtSuffix;
        String filePath = "/home/xxx/xxx/" + fileName;
        File file = new File(filePath);
        OutputStream os = null;
        try {
            os = new FileOutputStream(file);
            os.write(message.getBytes());
        } catch (Exception e) {
            LOGGER.error("Write file error", e);
        } finally {
            try { if (os != null) os.close(); } catch (Exception e) { LOGGER.error("Close error", e); }
        }
        upFileToS3(file, s3Key);
        writeFailToDB(batchNo, s3Key, fileName);
        deleteFile(file);
    }
}

Finally, use the annotation on a service method; the method only needs to return an error string when validation fails:

@Upload(type = UploadType.TYPE1)
public String upload(List<ClassOne> items) {
    if (items == null || items.isEmpty()) {
        return;
    }
    // validation
    String error = uploadCheck(items);
    if (StringUtils.isNotEmpty(error)) {
        return error;
    }
    deleteAll();
    batchInsert(items);
}

Conclusion

The AOP‑based “upload wheel” simplifies asynchronous Excel uploads, improves team productivity, and keeps business code clean.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

JavaaopAsynchronousfile uploadthread poolTemplate Method
Programmer DD
Written by

Programmer DD

A tinkering programmer and author of "Spring Cloud Microservices in Action"

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.