本文目录导读:

我来详细讲解Java中实现日志切面的几种常见方式。
基于Spring AOP的实现
1 Maven依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
2 自定义日志注解
import java.lang.annotation.*;
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface LogAnnotation {
/**
* 操作模块
*/
String module() default "";
/**
* 操作类型
*/
String operation() default "";
/**
* 是否记录请求参数
*/
boolean recordParams() default true;
/**
* 是否记录返回结果
*/
boolean recordResult() default false;
}
3 日志切面实现
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.Arrays;
import java.util.UUID;
@Slf4j
@Aspect
@Component
public class LogAspect {
@Autowired
private ObjectMapper objectMapper;
@Pointcut("@annotation(com.example.demo.annotation.LogAnnotation)")
public void logPointcut() {
}
/**
* 环绕通知:最灵活的增强方式
*/
@Around("logPointcut()")
public Object aroundLog(ProceedingJoinPoint joinPoint) throws Throwable {
long startTime = System.currentTimeMillis();
// 获取方法签名
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
Method method = signature.getMethod();
LogAnnotation logAnnotation = method.getAnnotation(LogAnnotation.class);
// 生成请求ID
String requestId = UUID.randomUUID().toString().replace("-", "");
try {
// 记录请求日志
if (logAnnotation.recordParams()) {
logRequest(joinPoint, logAnnotation, requestId);
}
// 执行目标方法
Object result = joinPoint.proceed();
// 记录响应日志
long costTime = System.currentTimeMillis() - startTime;
logResponse(result, logAnnotation, requestId, costTime);
return result;
} catch (Throwable e) {
// 记录异常日志
logError(e, joinPoint, requestId, System.currentTimeMillis() - startTime);
throw e;
}
}
/**
* 前置通知
*/
@Before("logPointcut()")
public void beforeLog(JoinPoint joinPoint) {
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
log.info("【前置通知】开始执行方法: {}", signature.getMethod().getName());
}
/**
* 后置通知(无论是否异常都会执行)
*/
@After("logPointcut()")
public void afterLog(JoinPoint joinPoint) {
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
log.info("【后置通知】方法执行完成: {}", signature.getMethod().getName());
}
/**
* 正常返回通知
*/
@AfterReturning(pointcut = "logPointcut()", returning = "result")
public void afterReturningLog(JoinPoint joinPoint, Object result) {
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
log.info("【返回通知】方法: {}, 返回值: {}",
signature.getMethod().getName(),
result != null ? result.toString() : "null");
}
/**
* 异常通知
*/
@AfterThrowing(pointcut = "logPointcut()", throwing = "exception")
public void afterThrowingLog(JoinPoint joinPoint, Exception exception) {
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
log.error("【异常通知】方法: {}, 异常: {}",
signature.getMethod().getName(),
exception.getMessage(),
exception);
}
/**
* 记录请求日志
*/
private void logRequest(ProceedingJoinPoint joinPoint, LogAnnotation logAnnotation, String requestId) {
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
// 获取HTTP请求信息
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
HttpServletRequest request = attributes != null ? attributes.getRequest() : null;
LogEntry logEntry = new LogEntry();
logEntry.setRequestId(requestId);
logEntry.setTime(LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
logEntry.setModule(logAnnotation.module());
logEntry.setOperation(logAnnotation.operation());
logEntry.setClassName(joinPoint.getTarget().getClass().getName());
logEntry.setMethodName(signature.getMethod().getName());
logEntry.setParams(Arrays.toString(joinPoint.getArgs()));
if (request != null) {
logEntry.setIp(request.getRemoteAddr());
logEntry.setUrl(request.getRequestURL().toString());
logEntry.setHttpMethod(request.getMethod());
logEntry.setUserAgent(request.getHeader("User-Agent"));
}
log.info("请求日志: {}", toJsonString(logEntry));
}
/**
* 记录响应日志
*/
private void logResponse(Object result, LogAnnotation logAnnotation, String requestId, long costTime) {
if (logAnnotation.recordResult()) {
LogEntry logEntry = new LogEntry();
logEntry.setRequestId(requestId);
logEntry.setTime(LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
logEntry.setCostTime(costTime + "ms");
logEntry.setResult(result != null ? toJsonString(result) : "null");
log.info("响应日志: {}", toJsonString(logEntry));
} else {
log.info("方法执行耗时: {}ms", costTime);
}
}
/**
* 记录异常日志
*/
private void logError(Throwable e, ProceedingJoinPoint joinPoint, String requestId, long costTime) {
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
LogEntry logEntry = new LogEntry();
logEntry.setRequestId(requestId);
logEntry.setTime(LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
logEntry.setClassName(joinPoint.getTarget().getClass().getName());
logEntry.setMethodName(signature.getMethod().getName());
logEntry.setErrorType(e.getClass().getName());
logEntry.setErrorMessage(e.getMessage());
logEntry.setCostTime(costTime + "ms");
log.error("异常日志: {}", toJsonString(logEntry), e);
}
/**
* 对象转JSON
*/
private String toJsonString(Object obj) {
try {
return objectMapper.writeValueAsString(obj);
} catch (Exception e) {
log.error("JSON转换异常", e);
return obj != null ? obj.toString() : "null";
}
}
}
4 日志实体类
import lombok.Data;
@Data
public class LogEntry {
private String requestId; // 请求ID
private String time; // 时间
private String module; // 模块
private String operation; // 操作
private String className; // 类名
private String methodName; // 方法名
private String params; // 参数
private String ip; // IP地址
private String url; // URL
private String httpMethod; // HTTP方法
private String userAgent; // 浏览器信息
private String result; // 返回结果
private String errorType; // 异常类型
private String errorMessage; // 异常信息
private String costTime; // 耗时
}
5 使用示例
@RestController
@RequestMapping("/api/user")
@Slf4j
public class UserController {
@PostMapping("/create")
@LogAnnotation(module = "用户管理", operation = "创建用户", recordParams = true, recordResult = false)
public Result createUser(@RequestBody UserDTO userDTO) {
// 业务逻辑
log.info("创建用户: {}", userDTO);
return Result.success();
}
@GetMapping("/query/{id}")
@LogAnnotation(module = "用户管理", operation = "查询用户", recordParams = true, recordResult = true)
public Result queryUser(@PathVariable Long id) {
// 业务逻辑
User user = new User();
user.setId(id);
user.setName("张三");
return Result.success(user);
}
}
基于AspectJ的实现(非Spring环境)
1 切面定义
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
@Aspect
public class AspectJLogAspect {
@Pointcut("execution(* com.example.service.*.*(..))")
public void servicePointcut() {
}
@Around("servicePointcut()")
public Object aroundService(ProceedingJoinPoint joinPoint) throws Throwable {
long start = System.currentTimeMillis();
String methodName = joinPoint.getSignature().toShortString();
System.out.println("[AspectJ] 开始执行: " + methodName);
System.out.println("[AspectJ] 参数: " + Arrays.toString(joinPoint.getArgs()));
Object result = joinPoint.proceed();
long elapsedTime = System.currentTimeMillis() - start;
System.out.println("[AspectJ] 执行完成: " + methodName + ", 耗时: " + elapsedTime + "ms");
return result;
}
}
2 aop.xml配置
<!DOCTYPE aspectj PUBLIC "-//AspectJ//DTD//EN" "http://www.eclipse.org/aspectj/dtd/aspectj.dtd">
<aspectj>
<weaver>
<include within="com.example.service.*"/>
</weaver>
<aspects>
<aspect name="com.example.aspect.AspectJLogAspect"/>
</aspects>
</aspectj>
基于动态代理的实现
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public class LogProxy implements InvocationHandler {
private Object target;
public LogProxy(Object target) {
this.target = target;
}
@SuppressWarnings("unchecked")
public static <T> T createProxy(T target) {
return (T) Proxy.newProxyInstance(
target.getClass().getClassLoader(),
target.getClass().getInterfaces(),
new LogProxy(target)
);
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
String methodName = method.getName();
System.out.println("[动态代理] 开始执行: " + methodName);
System.out.println("[动态代理] 参数: " + Arrays.toString(args));
long start = System.currentTimeMillis();
Object result = method.invoke(target, args);
long elapsedTime = System.currentTimeMillis() - start;
System.out.println("[动态代理] 执行完成: " + methodName + ", 耗时: " + elapsedTime + "ms");
System.out.println("[动态代理] 返回结果: " + result);
return result;
}
}
// 使用示例
public class ProxyDemo {
public static void main(String[] args) {
UserService userService = new UserServiceImpl();
UserService proxy = LogProxy.createProxy(userService);
proxy.createUser("张三");
proxy.getUser(1L);
}
}
最佳实践建议
1 性能优化
@Aspect
@Component
public class LogAspectOptimized {
// 使用异步日志记录,避免影响主流程性能
@Autowired
private AsyncLogger asyncLogger;
@Around("@annotation(logAnnotation)")
public Object aroundLog(ProceedingJoinPoint joinPoint, LogAnnotation logAnnotation) throws Throwable {
long startTime = System.currentTimeMillis();
String requestId = UUID.randomUUID().toString();
try {
// 异步记录请求日志
if (logAnnotation.recordParams()) {
asyncLogger.logRequest(requestId, joinPoint, logAnnotation);
}
Object result = joinPoint.proceed();
// 异步记录响应日志
long costTime = System.currentTimeMillis() - startTime;
asyncLogger.logResponse(requestId, result, logAnnotation, costTime);
return result;
} catch (Throwable e) {
asyncLogger.logError(requestId, joinPoint, e, System.currentTimeMillis() - startTime);
throw e;
}
}
}
2 日志级别动态控制
@Slf4j
@Aspect
@Component
public class DynamicLogLevelAspect {
@Around("@annotation(logAnnotation)")
public Object dynamicLog(ProceedingJoinPoint joinPoint, LogAnnotation logAnnotation) throws Throwable {
if (log.isDebugEnabled()) {
log.debug("详细日志记录...");
} else if (log.isInfoEnabled()) {
log.info("基本信息记录...");
}
return joinPoint.proceed();
}
}
3 启用Spring AOP
@SpringBootApplication
@EnableAspectJAutoProxy // 启用AOP
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
实现日志切面的主要方式:
- Spring AOP - 最常用,简单易用,适合Spring项目
- AspectJ - 功能更强大,支持编译时织入
- 动态代理 - 轻量级,适合简单场景
建议使用Spring AOP方式,配合自定义注解实现灵活控制。