Backend Development 9 min read

Dynamic Operation Logging in Spring Boot 3 Using AOP and SpEL

This tutorial demonstrates how to implement flexible, module‑aware operation logging in Spring Boot 3 by combining custom @Log annotations, AOP aspects, and Spring Expression Language (SpEL) to capture dynamic data such as usernames, product names, and order totals.

Spring Full-Stack Practical Cases
Spring Full-Stack Practical Cases
Spring Full-Stack Practical Cases
Dynamic Operation Logging in Spring Boot 3 Using AOP and SpEL

Operation logging is essential for monitoring, debugging, and security. By configuring a logging framework (Logback or Log4j2) and applying AOP, you can record business actions without invasive code changes.

1. Introduction

Using an @Log annotation on methods and an AOP aspect, you can automatically capture module names and descriptions, producing comprehensive logs for user actions, system state changes, and security audits.

2. Practical Example

2.1 Prepare Environment

Define the annotation

<code>@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Log {
    /** Module */
    String module();
    /** Description */
    String desc() default "";
}</code>

Define basic services

<code>// User module
@Service
public class PersonService {
    private final List&lt;Person&gt; PERSONS = List.of(
        new Person(1L, "张三"),
        new Person(2L, "李四"),
        new Person(3L, "王五"),
        new Person(4L, "赵六")
    );
    public Person save(Person person) { return person; }
    public Person query(Long id) {
        return PERSONS.stream().filter(u -> u.getId() == id).findFirst().orElse(null);
    }
}

// Product module
@Service
public class ProductService {
    private final List&lt;Product&gt; PRODUCTS = List.of(
        new Product(1L, "MySQL从删库到跑路", new BigDecimal("99.6")),
        new Product(2L, "JAVA从入门到放弃", new BigDecimal("77.8")),
        new Product(3L, "精通Spring全家桶", new BigDecimal("100"))
    );
    public void save(Product product) { System.out.println("保存商品"); }
    public Product query(Long id) {
        return PRODUCTS.stream().filter(p -> p.getId() == id).findFirst().orElse(null);
    }
}</code>

2.2 Aspect Definition with SpEL

Root object for SpEL

<code>public class ContextRoot {
    // login user containing username, password, role
    private LoginUser user;
    public ContextRoot(LoginUser user) { this.user = user; }
    public final LoginUser getUser() { return this.user; }
}</code>

Logging aspect

<code>@Aspect
public class LogAspect {
    private static final Logger logger = LoggerFactory.getLogger(LogAspect.class);

    @Pointcut("@annotation(log)")
    private void pclog(Log log) {}

    @Around("pclog(log)")
    public Object around(ProceedingJoinPoint pjp, Log log) throws Throwable {
        // module name
        String module = log.module();
        // description (SpEL expression)
        String desc = log.desc();
        MethodSignature ms = (MethodSignature) pjp.getSignature();
        Method method = ms.getMethod();
        Object[] args = pjp.getArgs();
        DefaultParameterNameDiscoverer discoverer = new DefaultParameterNameDiscoverer();
        // root object provides access without '#'
        ContextRoot rootObject = new ContextRoot(new LoginUser("admin", "123123", "ADMIN"));
        // method‑based context adds method parameters as variables (access with '#')
        MethodBasedEvaluationContext context = new MethodBasedEvaluationContext(rootObject, method, args, discoverer);
        ExpressionParser parser = new SpelExpressionParser();
        desc = parser.parseExpression(desc).getValue(context, String.class);
        // TODO: persist log asynchronously
        logger.info("{}, {}", module, desc);
        return pjp.proceed();
    }
}</code>

2.3 Applying the Annotation

Use SpEL to embed dynamic values:

<code>// User module
@Log(module = "用户模块", desc = "'保存【' + #person.name + '】'")
public Person save(Person person);

@Log(module = "用户模块", desc = "'操作人:' + user.username + ', 根据ID查询【' + #id + '】'")
public Person query(Long id);

// Product module
@Log(module = "商品模块", desc = "'保存【' + #product.name + '】, 商品价格【' + #product.price + '】'")
public void save(Product product);
</code>

Controller example for testing:

<code>@PostMapping("")
public Person save(@RequestBody Person user) {
    return userService.save(user);
}

@GetMapping("/{id}")
public Person queryById(@PathVariable("id") Long id) {
    return userService.query(id);
}</code>

The console output shows logs such as:

By defining a single, generic aspect and leveraging SpEL, you can flexibly log any required data across modules without creating separate aspects for each case.

backendJavaAOPSPELLoggingSpring Boot
Spring Full-Stack Practical Cases
Written by

Spring Full-Stack Practical Cases

Full-stack Java development with Vue 2/3 front-end suite; hands-on examples and source code analysis for Spring, Spring Boot 2/3, and Spring Cloud.

0 followers
Reader feedback

How this landed with the community

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