How to Build a Pluggable Operation Log System with AOP and Annotations in Java
This article explains a complete, reusable solution for recording operation logs in Java backend applications using Spring AOP, custom annotations, callback interfaces, and a user‑adapter to bridge third‑party logging libraries with the main program, enabling flexible storage and auditing.
In common backend admin systems, operation logs are required for quick troubleshooting, regular audit, and to record sensitive actions.
Log Content Requirements
User information (username, role, IP, request path) Operation type Operation content (brief description) Operation time Current menu Method parameters Method response Operation duration
Solution Overview
We can use AOP proxy + annotation to create a generic operation‑log collection scheme. The proxy gathers log data and passes it to a user‑defined saver; it is recommended to store logs in a database using multithreading and then synchronize them to a queue such as Elasticsearch.
Define Annotation and Aspect
The aspect converts the collected runtime parameters into a log object.
package com.su4j.logger;
import com.su4j.util.ExpressionParserUtil;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import java.time.Duration;
import java.time.LocalDateTime;
import java.util.Arrays;
/**
* Created on 2025/2/11 13:56
* @author linshimeng
* @version 1.0.0
* @description
*/
@Slf4j
@Aspect
public class OperationLogAspect {
private final LogCallbackManager logCallbackManager;
private final LogUserInfoAdapter logUserInfoAdapter;
public OperationLogAspect(LogCallbackManager logCallbackManager, LogUserInfoAdapter logUserInfoAdapter) {
this.logCallbackManager = logCallbackManager;
this.logUserInfoAdapter = logUserInfoAdapter;
}
@Around("@annotation(logOperation)")
public Object logOperation(ProceedingJoinPoint joinPoint, LogOperation logOperation) throws Throwable {
// Execute target method
LocalDateTime startTime = LocalDateTime.now();
Object result = null;
try {
result = joinPoint.proceed();
} finally {
// Build log object
LocalDateTime endTime = LocalDateTime.now();
OperationLog log = new OperationLog();
log.setOperationUser(logUserInfoAdapter.getCurrentUser());
log.setMenu(logOperation.menu());
log.setType(logOperation.type());
log.setContent(ExpressionParserUtil.resolveKey(logOperation.content(), joinPoint));
log.setOperationTime(startTime);
log.setParams(Arrays.toString(joinPoint.getArgs()));
log.setResult(result);
log.setSpendTime(Duration.between(startTime, endTime));
// Execute callbacks in order
logCallbackManager.executeCallbacks(log);
}
return result;
}
}Define Callback Interface
After the aspect creates the log object, the callback method receives the operation log.
package com.su4j.logger;
import java.util.List;
/**
* Constructed via dependency injection with a List of LogCallback implementations
*/
public class LogCallbackManager {
private final List<LogCallbackInterface> logCallbackInterfaces;
public LogCallbackManager(List<LogCallbackInterface> logCallbackInterfaces) {
this.logCallbackInterfaces = logCallbackInterfaces;
}
/**
* Execute callbacks in the order of the List
*/
public void executeCallbacks(OperationLog log) {
if (logCallbackInterfaces != null && logCallbackInterfaces.size() > 0) {
for (LogCallbackInterface logCallbackInterface : logCallbackInterfaces) {
// Execute callback
logCallbackInterface.resolveLog(log);
}
}
}
} package com.su4j.logger;
/**
* Parse log
*/
public interface LogCallbackInterface {
void resolveLog(OperationLog operationLog);
}Login User Adapter
Because the logging aspect may reside in a third‑party library and cannot directly obtain the main program’s user context, we define a user adapter to pass external user information to the aspect.
package com.su4j.logger;
/**
* Get current operation user
*/
public interface LogUserInfoAdapter {
OperationUser getCurrentUser();
}
public class DefaultLogUserInfoAdapter implements LogUserInfoAdapter {
@Override
public OperationUser getCurrentUser() {
return null;
}
}Custom Log Parsing
Implement the user adapter and callback in the main program.
@Slf4j
@Component
public class MyLogCallbackImpl implements LogCallbackInterface {
@Override
public void resolveLog(OperationLog operationLog) {
// todo custom save
log.info("operationLog:{},{}", operationLog, operationLog.getSpendTime().toMillis());
}
}
@Component
public class MyLoginLogUserImpl implements LogUserInfoAdapter {
@Override
public OperationUser getCurrentUser() {
// todo simulate user
OperationUser user = new OperationUser();
user.setUserId(UUID.randomUUID().toString());
user.setUsername("test");
user.setRelo("root");
user.setIpAddress("127.0.0.1");
user.setPath("/test");
return user;
}
}Test Usage
Write a controller where the operation content supports Spring EL expressions.
@Slf4j
@RestController
@RequestMapping("/test")
public class LoggerTestController {
@LogOperation(type = "更新用户", menu = "用户管理-更新用户", content = "'发起请求:'+{#user.username}")
@PostMapping("/update")
public String update(@RequestBody OperationUser user) {
// todo simulate operation
return user.getUsername();
}
}Summary
The solution fully exploits Spring AOP and annotations to implement a pluggable, decoupled operation‑log mechanism. Log collection is expressed as annotation + aspect, and the resulting log is handed to business code via callbacks, allowing flexible persistence such as database storage or asynchronous sync to Elasticsearch. Because the aspect resides in an independent third‑party package, a user‑adapter is introduced to pass user context from the main application, breaking runtime boundaries and achieving true separation between logging logic and business logic.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Lin is Dream
Sharing Java developer knowledge, practical articles, and continuous insights into computer engineering.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
