How to Build a Clean Business Operation Log System with Spring AOP

This article explains the design and implementation of a business operation logging feature in a Spring Boot application, contrasting flawed hard‑coded approaches with a clean AOP‑based solution, and provides full code snippets, configuration, database schema, and testing tips.

Java Interview Crash Guide
Java Interview Crash Guide
Java Interview Crash Guide
How to Build a Clean Business Operation Log System with Spring AOP

Introduction

Long ago I wanted to write this article but never had time; the requirement was simple: record and query business operation logs. The initial implementation was terrible, and the mistakes are detailed in the anti‑pattern example.

Requirement Description and Analysis

The client needs to log key business functions: who performed which operation, at what time, the request and response payloads, and optionally allow a one‑click rollback.

System Log

System logs capture program execution steps (debug, info, warn, error) for developers or operations staff to troubleshoot issues.

Operation Log

Operation logs record actual user business actions (e.g., menu clicks, configuration changes) and are stored in a database for administrators or users to view.

Key functional requirements:

Record fields: operator, time, function, log type, description, request payload, pre‑operation payload.

Provide a visual page for querying and tracing important operations.

Offer management features such as rollback of erroneous operations.

Anti‑Pattern Implementation

The naive approach adds logging code and exception handling to every controller method, tightly coupling logging with business logic and causing code duplication.

@RestController
@Slf4j
@BusLog(name = "Personnel Management")
@RequestMapping("/person")
public class PersonController2 {
    @Autowired
    private IPersonService personService;
    @Autowired
    private IBusLogService busLogService;
    // Add person
    @PostMapping
    public Person add(@RequestBody Person person) {
        try {
            Person result = this.personService.registe(person);
            this.saveLog(person);
            log.info("// add person completed");
        } catch (Exception e) {
            this.saveExceptionLog(e);
        }
        return result;
    }
}

This hard‑coded method makes logging logic repeat across all endpoints and requires modifying existing controllers to weave in logging.

Design Idea

Using AOP is the optimal solution if you are familiar with it:

Define a custom annotation for business logs, allowing attributes such as function name and description.

Annotate methods (or classes) that need logging.

Define a pointcut and an aspect that intercepts the annotated methods and saves the log information.

Spring AOP

Aspect‑Oriented Programming (AOP) adds cross‑cutting concerns without modifying source code. Spring AOP is a concrete implementation that can intercept methods at any layer (controller, service, etc.) and provides before, after, around, and exception advice.

Filter and HandlerInterceptor

Filters and HandlerInterceptors were not chosen because of their limitations:

Filter

Filters work at the servlet level, can only read and modify request/response streams, and cannot fine‑tune to specific classes or methods. They depend on a servlet container.

Interceptor

HandlerInterceptors are specific to Spring MVC, cannot be used in non‑MVC projects, and share the same granularity limitations as filters.

Comparison of Spring AOP, Filter, and Interceptor

Execution priority when matching the same target: Filter > Interceptor > Spring AOP. Differences include scope, granularity, and ability to modify return values.

Scope: Filters require a servlet container; Interceptors need Spring MVC; Spring AOP works anywhere a pointcut is defined.

Granularity: Filters are coarse; Interceptors offer preHandle, postHandle, afterCompletion; Spring AOP provides before, after, after‑returning, after‑throwing, and around advice.

Implementation Plan

Environment Configuration

JDK 1.8, IntelliJ IDEA 2020.1

Spring Boot 2.3.9.RELEASE

mybatis-spring-boot-starter 2.1.4

Dependency Configuration

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-aop</artifactId>
</dependency>

Table Structure Design

create table if not exists bus_log (
    id bigint auto_increment comment 'auto‑increment id' primary key,
    bus_name varchar(100) null comment 'business name',
    bus_descrip varchar(255) null comment 'business description',
    oper_person varchar(100) null comment 'operator',
    oper_time datetime null comment 'operation time',
    ip_from varchar(50) null comment 'source IP',
    param_file varchar(255) null comment 'parameter file path'
) comment 'business operation log' default charset='utf8';

Code Implementation

1. Define the @BusLog annotation.

/**
 * Business log annotation
 */
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface BusLog {
    String name() default ""; // function name
    String descrip() default ""; // function description
}

2. Annotate controller methods with @BusLog.

@RestController
@Slf4j
@BusLog(name = "Personnel Management")
@RequestMapping("/person")
public class PersonController {
    @Autowired
    private IPersonService personService;
    @PostMapping
    @BusLog(descrip = "Add a single person")
    public Person add(@RequestBody Person person) {
        Person result = this.personService.registe(person);
        log.info("// add person completed");
        return result;
    }
    // other CRUD methods similarly annotated ...
}

3. Implement the aspect that records logs.

@Component
@Aspect
@Slf4j
public class BusLogAop implements Ordered {
    @Autowired
    private BusLogDao busLogDao;

    @Pointcut("@annotation(com.fanfu.anno.BusLog)")
    public void pointcut() {}

    @Around("pointcut()")
    public Object around(ProceedingJoinPoint pjp) throws Throwable {
        log.info("----BusAop around start");
        Object result = null;
        try {
            result = pjp.proceed();
        } catch (Throwable t) {
            t.printStackTrace();
            throw t;
        }
        // collect annotation data
        Object target = pjp.getTarget();
        MethodSignature sig = (MethodSignature) pjp.getSignature();
        BusLog classAnno = target.getClass().getAnnotation(BusLog.class);
        BusLog methodAnno = sig.getMethod().getAnnotation(BusLog.class);
        BusLogBean bean = new BusLogBean();
        bean.setBusName(classAnno != null ? classAnno.name() : "");
        bean.setBusDescrip(methodAnno != null ? methodAnno.descrip() : "");
        bean.setOperPerson("fanfu");
        bean.setOperTime(new Date());
        // serialize parameters to file
        String json = new ObjectMapper().writeValueAsString(pjp.getArgs());
        String filePath = System.getProperty("user.dir") + File.separator +
                new SimpleDateFormat("yyyyMMddHHmmssSSS").format(new Date()) + ".log";
        try (FileOutputStream fos = new FileOutputStream(filePath)) {
            fos.write(json.getBytes(StandardCharsets.UTF_8));
            bean.setParamFile(filePath);
        }
        busLogDao.insert(bean);
        log.info("----BusAop around end");
        return result;
    }

    @Override
    public int getOrder() { return 1; }
}

Testing

Debugging Method

Instead of Postman, you can use IntelliJ IDEA's built‑in Test RESTful Web Service (Tools → HTTP Client → Test RESTful Web) to send requests directly from the IDE.

Another convenient method is to write a few lines in the HTTP client file to batch‑execute multiple requests.

Verification Result

Conclusion

The business operation log records the function name, description, operator, time, and parameter payload. The payload is stored in a file because it is only needed for rollback scenarios, where the previous parameters are used to reverse the operation.

JavaAOPbackend developmentSpring Bootbusiness logging
Java Interview Crash Guide
Written by

Java Interview Crash Guide

Dedicated to sharing Java interview Q&A; follow and reply "java" to receive a free premium Java interview guide.

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.