Java案例如何实现日志切面?

wen python案例 2

本文目录导读:

Java案例如何实现日志切面?

  1. 基于Spring AOP的实现
  2. 基于AspectJ的实现(非Spring环境)
  3. 基于动态代理的实现
  4. 最佳实践建议

我来详细讲解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);
    }
}

实现日志切面的主要方式:

  1. Spring AOP - 最常用,简单易用,适合Spring项目
  2. AspectJ - 功能更强大,支持编译时织入
  3. 动态代理 - 轻量级,适合简单场景

建议使用Spring AOP方式,配合自定义注解实现灵活控制。

抱歉,评论功能暂时关闭!