Spring Boot项目中自定义日志记录注解
本文最后更新于 2025-11-27,文章内容可能已经过时。
- 精准拦截:@annotation + @within 覆盖方法级和类级注解(不用写两套切点)。
- 灵活配置:通过注解参数控制是否记录参数/结果(避免日志爆炸)。
- 日志清晰:输出格式包含类型(类级/方法级)、耗时、参数、结果,一眼看懂。
- 无侵入:只需在方法/类上加@Loggable,不用改业务逻辑。
简单实现代码
1、创建自定义注解(Loggable.java)
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* 自定义日志注解:方法/类级别使用,自动记录调用日志
* 使用示例:
*
* @Loggable // 方法级
* @Loggable // 类级(作用于类中所有方法)
*/
@Target({ElementType.METHOD, ElementType.TYPE}) // 作用于方法或类
@Retention(RetentionPolicy.RUNTIME) // 运行时生效
public @interface Loggable {
boolean recordParams() default true; // 是否记录参数
boolean recordResult() default true; // 是否记录返回值
}
2、使用示例(UserService.java)
import org.springframework.stereotype.Service;
@Service
@Loggable // 类级注解
public class UserService {
@Loggable(recordParams = false) // 方法级覆盖
public String getUserInfo(Long id) { // 自动记录日志
return "User " + id;
}
public String getOrders(Long id) { // 自动记录日志
return "Orders for " + id;
}
}
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class UserController {
@Autowired
private UserService userService;
@GetMapping("/user/{id}")
public String getUser(@PathVariable Long id) {
return userService.getUserInfo(id);
}
@GetMapping("/orders/{id}")
public String getOrders(@PathVariable Long id) {
return userService.getOrders(id);
}
}
3、实现AOP切面(LogAspect.java)
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import java.lang.reflect.Method;
import java.util.Arrays;
/**
* 自定义日志切面:精准实现
* 1. 类上注解 → 类中所有方法自动记录
* 2. 方法上注解 → 仅当前方法记录(覆盖类级配置)
* 3. 优先级:方法级 > 类级
*/
@Aspect
@Component // 让Spring管理这个切面
public class LogAspect {
private static final Logger log = LoggerFactory.getLogger(LogAspect.class);
/**
* 切点:拦截所有被@Loggable标注的方法或类
* @annotation:方法级注解
* @within:类级注解(类上标注@Loggable)
*/
@Around("@annotation(com.nn3n.bootdemolog.anno.Loggable) || @within(com.nn3n.bootdemolog.anno.Loggable)")
public Object logMethodExecution(ProceedingJoinPoint joinPoint) throws Throwable {
// ====== 关键逻辑:优先获取方法级注解,其次类级注解 ======
Loggable loggable = null;
Method method = ((MethodSignature) joinPoint.getSignature()).getMethod();
// 1. 优先尝试获取方法级注解
if (method.isAnnotationPresent(Loggable.class)) {
loggable = method.getAnnotation(Loggable.class);
}
// 2. 如果方法级没有,尝试获取类级注解
else if (joinPoint.getTarget().getClass().isAnnotationPresent(Loggable.class)) {
loggable = joinPoint.getTarget().getClass().getAnnotation(Loggable.class);
}
// 3. 无任何注解 → 跳过日志记录(安全兜底)
if (loggable == null) {
return joinPoint.proceed();
}
// ====== 记录调用信息(按注解配置) ======
long startTime = System.currentTimeMillis();
String className = joinPoint.getTarget().getClass().getSimpleName();
String methodName = method.getName();
// 参数记录(按注解配置)
String params = loggable.recordParams()
? Arrays.toString(joinPoint.getArgs())
: "参数记录已关闭(方法级配置)";
// 执行目标方法
Object result = joinPoint.proceed();
// 返回值记录(按注解配置)
String resultStr = loggable.recordResult()
? (result != null ? result.toString() : "null")
: "结果记录已关闭(方法级配置)";
// ====== 输出日志(关键!区分方法级/类级) ======
long executionTime = System.currentTimeMillis() - startTime;
log.info("【{}】{}.{} | 耗时:{}ms | 参数:{} | 返回:{}",
method.isAnnotationPresent(Loggable.class) ? "方法级" : "类级",
className, methodName,
executionTime, params, resultStr);
return result;
}
}
4、使用效果示例
【类级】UserService.getUserInfo | 耗时:0ms | 参数:[456] | 返回:User 456
【类级】UserService.getOrders | 耗时:0ms | 参数:[456] | 返回:Orders for 456
【方法级】UserService.getUserInfo | 耗时:0ms | 参数:参数记录已关闭(方法级配置) | 返回:User 456
【类级】UserService.getOrders | 耗时:0ms | 参数:[456] | 返回:Orders for 456
本方案通过自定义@Loggable注解结合Spring AOP实现方法调用日志记录功能,支持类级和方法级注解灵活配置。核心逻辑:类上添加注解时自动拦截所有方法,方法上添加注解时仅拦截当前方法且优先级高于类级配置;通过AOP切面精准控制参数/结果记录开关,日志输出包含调用类型(类级/方法级)、耗时、参数及返回值,并提供安全兜底机制防止无注解时触发异常。
- 感谢你赐予我前进的力量
赞赏者名单
因为你们的支持让我意识到写文章的价值🙏
本文是原创文章,采用 CC BY-NC-ND 4.0 协议,完整转载请注明来自 软件从业者Hort
评论
匿名评论
隐私政策
你无需删除空行,直接评论以获取最佳展示效果

