1. 添加依赖

1.1 Maven依赖

<dependencies>
    <!-- Spring Boot Starter AOP -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-aop</artifactId>
    </dependency>
    
    <!-- Lombok (可选,简化日志代码) -->
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <optional>true</optional>
    </dependency>
</dependencies>

1.2 Gradle依赖

dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-aop'
    compileOnly 'org.projectlombok:lombok'
    annotationProcessor 'org.projectlombok:lombok'
}

2. 启用AOP

在Spring Boot主类上添加 @EnableAspectJAutoProxy 注解:

@SpringBootApplication
@EnableAspectJAutoProxy
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}

3. 基础日志切面

3.1 简单的日志切面

@Aspect
@Component
@Slf4j
public class LoggingAspect {
    
    // 定义切点:拦截所有Controller方法
    @Pointcut("execution(* com.example.controller.*.*(..))")
    public void controllerPointcut() {}
    
    // 定义切点:拦截所有Service方法
    @Pointcut("execution(* com.example.service.*.*(..))")
    public void servicePointcut() {}
    
    // 前置通知:方法执行前
    @Before("controllerPointcut() || servicePointcut()")
    public void logBefore(JoinPoint joinPoint) {
        String methodName = joinPoint.getSignature().getName();
        String className = joinPoint.getTarget().getClass().getSimpleName();
        Object[] args = joinPoint.getArgs();
        
        log.info("开始执行: {}.{} - 参数: {}", 
                className, methodName, Arrays.toString(args));
    }
    
    // 后置通知:方法执行后(无论成功失败)
    @After("controllerPointcut() || servicePointcut()")
    public void logAfter(JoinPoint joinPoint) {
        String methodName = joinPoint.getSignature().getName();
        String className = joinPoint.getTarget().getClass().getSimpleName();
        
        log.info("执行完成: {}.{}", className, methodName);
    }
    
    // 返回通知:方法正常返回后
    @AfterReturning(pointcut = "controllerPointcut() || servicePointcut()", 
                   returning = "result")
    public void logAfterReturning(JoinPoint joinPoint, Object result) {
        String methodName = joinPoint.getSignature().getName();
        String className = joinPoint.getTarget().getClass().getSimpleName();
        
        log.info("方法返回: {}.{} - 返回值: {}", 
                className, methodName, result);
    }
    
    // 异常通知:方法抛出异常后
    @AfterThrowing(pointcut = "controllerPointcut() || servicePointcut()", 
                  throwing = "exception")
    public void logAfterThrowing(JoinPoint joinPoint, Exception exception) {
        String methodName = joinPoint.getSignature().getName();
        String className = joinPoint.getTarget().getClass().getSimpleName();
        
        log.error("方法异常: {}.{} - 异常信息: {}", 
                 className, methodName, exception.getMessage(), exception);
    }
}

4. 环绕通知增强版

4.1 使用环绕通知记录执行时间

@Aspect
@Component
@Slf4j
public class PerformanceLoggingAspect {
    
    @Pointcut("execution(* com.example.service.*.*(..))")
    public void servicePointcut() {}
    
    @Around("servicePointcut()")
    public Object logAround(ProceedingJoinPoint joinPoint) throws Throwable {
        long startTime = System.currentTimeMillis();
        String methodName = joinPoint.getSignature().getName();
        String className = joinPoint.getTarget().getClass().getSimpleName();
        Object[] args = joinPoint.getArgs();
        
        log.info("开始执行: {}.{} - 参数: {}", 
                className, methodName, Arrays.toString(args));
        
        Object result = null;
        try {
            result = joinPoint.proceed();
            long endTime = System.currentTimeMillis();
            long executionTime = endTime - startTime;
            
            log.info("执行完成: {}.{} - 耗时: {}ms - 返回值: {}", 
                    className, methodName, executionTime, result);
            
            return result;
        } catch (Exception e) {
            long endTime = System.currentTimeMillis();
            long executionTime = endTime - startTime;
            
            log.error("执行异常: {}.{} - 耗时: {}ms - 异常: {}", 
                     className, methodName, executionTime, e.getMessage(), e);
            throw e;
        }
    }
}

5. 自定义注解日志

5.1 创建日志注解

@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface LogOperation {
    
    /**
     * 操作描述
     */
    String value() default "";
    
    /**
     * 是否记录参数
     */
    boolean logParams() default true;
    
    /**
     * 是否记录返回值
     */
    boolean logResult() default true;
    
    /**
     * 操作类型
     */
    OperationType operationType() default OperationType.OTHER;
    
    enum OperationType {
        CREATE, UPDATE, DELETE, QUERY, OTHER
    }
}

5.2 基于注解的日志切面

@Aspect
@Component
@Slf4j
public class AnnotationLoggingAspect {
    
    @Pointcut("@annotation(com.example.annotation.LogOperation)")
    public void logOperationPointcut() {}
    
    @Around("logOperationPointcut()")
    public Object logOperation(ProceedingJoinPoint joinPoint) throws Throwable {
        // 获取方法信息
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        Method method = signature.getMethod();
        LogOperation logOperation = method.getAnnotation(LogOperation.class);
        
        // 获取操作信息
        String operation = logOperation.value();
        OperationType operationType = logOperation.operationType();
        boolean logParams = logOperation.logParams();
        boolean logResult = logOperation.logResult();
        
        String methodName = method.getName();
        String className = joinPoint.getTarget().getClass().getSimpleName();
        Object[] args = joinPoint.getArgs();
        
        // 记录开始日志
        log.info("开始执行操作: [{}] {}.{} - 类型: {}", 
                operation, className, methodName, operationType);
        
        if (logParams) {
            log.info("操作参数: {}", Arrays.toString(args));
        }
        
        long startTime = System.currentTimeMillis();
        Object result = null;
        
        try {
            result = joinPoint.proceed();
            long endTime = System.currentTimeMillis();
            long executionTime = endTime - startTime;
            
            // 记录成功日志
            log.info("操作成功: [{}] {}.{} - 耗时: {}ms", 
                    operation, className, methodName, executionTime);
            
            if (logResult && result != null) {
                log.info("操作结果: {}", result);
            }
            
            return result;
            
        } catch (Exception e) {
            long endTime = System.currentTimeMillis();
            long executionTime = endTime - startTime;
            
            // 记录失败日志
            log.error("操作失败: [{}] {}.{} - 耗时: {}ms - 异常: {}", 
                     operation, className, methodName, executionTime, e.getMessage(), e);
            throw e;
        }
    }
}

5.3 使用自定义注解

@RestController
@RequestMapping("/api/users")
public class UserController {
    
    @Autowired
    private UserService userService;
    
    @GetMapping("/{id}")
    @LogOperation(value = "查询用户信息", operationType = OperationType.QUERY)
    public ResponseEntity<User> getUser(@PathVariable Long id) {
        User user = userService.findById(id);
        return ResponseEntity.ok(user);
    }
    
    @PostMapping
    @LogOperation(value = "创建用户", operationType = OperationType.CREATE, 
                  logParams = true, logResult = true)
    public ResponseEntity<User> createUser(@RequestBody User user) {
        User createdUser = userService.save(user);
        return ResponseEntity.status(HttpStatus.CREATED).body(createdUser);
    }
    
    @PutMapping("/{id}")
    @LogOperation(value = "更新用户信息", operationType = OperationType.UPDATE)
    public ResponseEntity<User> updateUser(@PathVariable Long id, @RequestBody User user) {
        User updatedUser = userService.update(id, user);
        return ResponseEntity.ok(updatedUser);
    }
    
    @DeleteMapping("/{id}")
    @LogOperation(value = "删除用户", operationType = OperationType.DELETE)
    public ResponseEntity<Void> deleteUser(@PathVariable Long id) {
        userService.delete(id);
        return ResponseEntity.noContent().build();
    }
}

6. 数据库日志记录

6.1 创建日志实体类

@Entity
@Table(name = "sys_operation_log")
@Data
public class OperationLog {
    
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    
    /**
     * 操作类型
     */
    private String operationType;
    
    /**
     * 操作描述
     */
    private String description;
    
    /**
     * 请求方法
     */
    private String method;
    
    /**
     * 请求参数
     */
    private String params;
    
    /**
     * 返回结果
     */
    private String result;
    
    /**
     * 执行时间(毫秒)
     */
    private Long executionTime;
    
    /**
     * 操作状态
     */
    private String status;
    
    /**
     * 错误信息
     */
    private String error;
    
    /**
     * 操作用户
     */
    private String operator;
    
    /**
     * 操作IP
     */
    private String ip;
    
    /**
     * 操作时间
     */
    private LocalDateTime createTime;
}

6.2 数据库日志切面

@Aspect
@Component
@Slf4j
public class DatabaseLoggingAspect {
    
    @Autowired
    private OperationLogService operationLogService;
    
    @Pointcut("@annotation(com.example.annotation.LogOperation)")
    public void logOperationPointcut() {}
    
    @Around("logOperationPointcut()")
    public Object logToDatabase(ProceedingJoinPoint joinPoint) throws Throwable {
        OperationLog operationLog = new OperationLog();
        long startTime = System.currentTimeMillis();
        
        try {
            // 设置基本信息
            setBasicInfo(joinPoint, operationLog);
            
            // 执行方法
            Object result = joinPoint.proceed();
            
            // 设置成功信息
            long endTime = System.currentTimeMillis();
            operationLog.setExecutionTime(endTime - startTime);
            operationLog.setStatus("SUCCESS");
            operationLog.setResult(JSON.toJSONString(result));
            
            return result;
            
        } catch (Exception e) {
            // 设置失败信息
            long endTime = System.currentTimeMillis();
            operationLog.setExecutionTime(endTime - startTime);
            operationLog.setStatus("FAILED");
            operationLog.setError(e.getMessage());
            
            throw e;
        } finally {
            // 异步保存日志
            saveLogAsync(operationLog);
        }
    }
    
    private void setBasicInfo(ProceedingJoinPoint joinPoint, OperationLog operationLog) {
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        Method method = signature.getMethod();
        LogOperation logOperation = method.getAnnotation(LogOperation.class);
        
        operationLog.setOperationType(logOperation.operationType().name());
        operationLog.setDescription(logOperation.value());
        operationLog.setMethod(joinPoint.getTarget().getClass().getSimpleName() + "." + method.getName());
        operationLog.setParams(JSON.toJSONString(joinPoint.getArgs()));
        operationLog.setOperator(getCurrentUser());
        operationLog.setIp(getClientIp());
        operationLog.setCreateTime(LocalDateTime.now());
    }
    
    @Async
    private void saveLogAsync(OperationLog operationLog) {
        try {
            operationLogService.save(operationLog);
        } catch (Exception e) {
            log.error("保存操作日志失败", e);
        }
    }
    
    private String getCurrentUser() {
        // 从SecurityContext或其他地方获取当前用户
        return "anonymous";
    }
    
    private String getClientIp() {
        // 从RequestContextHolder或其他地方获取客户端IP
        return "127.0.0.1";
    }
}

7. 请求响应日志

7.1 请求日志切面

@Aspect
@Component
@Slf4j
public class RequestLoggingAspect {
    
    @Pointcut("execution(* com.example.controller.*.*(..))")
    public void controllerPointcut() {}
    
    @Around("controllerPointcut()")
    public Object logRequestResponse(ProceedingJoinPoint joinPoint) throws Throwable {
        HttpServletRequest request = getCurrentRequest();
        
        // 记录请求信息
        String requestId = UUID.randomUUID().toString().substring(0, 8);
        String method = request.getMethod();
        String url = request.getRequestURI();
        String queryString = request.getQueryString();
        String remoteAddr = request.getRemoteAddr();
        
        log.info("[{}] {} {}?{} - IP: {}", requestId, method, url, queryString, remoteAddr);
        
        // 记录请求头
        logHeaders(requestId, request);
        
        // 记录请求体
        if ("POST".equals(method) || "PUT".equals(method)) {
            logRequestBody(requestId, joinPoint.getArgs());
        }
        
        long startTime = System.currentTimeMillis();
        Object result = null;
        
        try {
            result = joinPoint.proceed();
            long endTime = System.currentTimeMillis();
            
            // 记录响应信息
            log.info("[{}] 响应成功 - 耗时: {}ms", requestId, endTime - startTime);
            logResponse(requestId, result);
            
            return result;
            
        } catch (Exception e) {
            long endTime = System.currentTimeMillis();
            
            log.error("[{}] 响应失败 - 耗时: {}ms - 异常: {}", 
                     requestId, endTime - startTime, e.getMessage(), e);
            throw e;
        }
    }
    
    private HttpServletRequest getCurrentRequest() {
        RequestAttributes attributes = RequestContextHolder.getRequestAttributes();
        return attributes != null ? 
               ((ServletRequestAttributes) attributes).getRequest() : null;
    }
    
    private void logHeaders(String requestId, HttpServletRequest request) {
        Enumeration<String> headerNames = request.getHeaderNames();
        StringBuilder headers = new StringBuilder();
        
        while (headerNames.hasMoreElements()) {
            String headerName = headerNames.nextElement();
            String headerValue = request.getHeader(headerName);
            headers.append(headerName).append(": ").append(headerValue).append(", ");
        }
        
        log.debug("[{}] 请求头: {}", requestId, headers.toString());
    }
    
    private void logRequestBody(String requestId, Object[] args) {
        try {
            log.info("[{}] 请求体: {}", requestId, JSON.toJSONString(args));
        } catch (Exception e) {
            log.debug("[{}] 请求体序列化失败", requestId, e);
        }
    }
    
    private void logResponse(String requestId, Object result) {
        try {
            log.info("[{}] 响应体: {}", requestId, JSON.toJSONString(result));
        } catch (Exception e) {
            log.debug("[{}] 响应体序列化失败", requestId, e);
        }
    }
}

8. 配置优化

8.1 日志配置

# application.yml
logging:
  level:
    com.example.aspect: INFO
    org.springframework.aop: INFO
  pattern:
    console: "%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level [%X{requestId}] %logger{36} - %msg%n"
    file: "%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level [%X{requestId}] %logger{36} - %msg%n"

8.2 AOP配置

@Configuration
@EnableAspectJAutoProxy(proxyTargetClass = true)
public class AopConfig {
    
    @Bean
    public DefaultPointcutAdvisor defaultPointcutAdvisor() {
        LogOperationPointcut pointcut = new LogOperationPointcut();
        AnnotationLoggingAspect advice = new AnnotationLoggingAspect();
        DefaultPointcutAdvisor advisor = new DefaultPointcutAdvisor();
        advisor.setPointcut(pointcut);
        advisor.setAdvice(advice);
        return advisor;
    }
}

9. 性能优化

9.1 异步日志记录

@Configuration
@EnableAsync
public class AsyncConfig {
    
    @Bean(name = "logExecutor")
    public TaskExecutor logExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(2);
        executor.setMaxPoolSize(5);
        executor.setQueueCapacity(100);
        executor.setThreadNamePrefix("Log-");
        executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
        executor.initialize();
        return executor;
    }
}

@Aspect
@Component
@Slf4j
public class AsyncLoggingAspect {
    
    @Async("logExecutor")
    public void saveLogAsync(OperationLog log) {
        // 异步保存日志
    }
}

9.2 条件日志记录

@Aspect
@Component
@Slf4j
public class ConditionalLoggingAspect {
    
    @Value("${app.logging.enabled:true}")
    private boolean loggingEnabled;
    
    @Value("${app.logging.slow-query-threshold:1000}")
    private long slowQueryThreshold;
    
    @Around("execution(* com.example.service.*.*(..))")
    public Object conditionalLog(ProceedingJoinPoint joinPoint) throws Throwable {
        if (!loggingEnabled) {
            return joinPoint.proceed();
        }
        
        long startTime = System.currentTimeMillis();
        Object result = joinPoint.proceed();
        long executionTime = System.currentTimeMillis() - startTime;
        
        // 只记录慢查询
        if (executionTime > slowQueryThreshold) {
            String methodName = joinPoint.getSignature().getName();
            log.warn("慢查询检测: {} - 耗时: {}ms", methodName, executionTime);
        }
        
        return result;
    }
}

10. 最佳实践

10.1 避免日志过多

  • 合理设置日志级别
  • 避免在循环中记录大量日志
  • 使用异步日志记录

10.2 敏感信息脱敏

private String maskSensitiveData(String data) {
    if (data == null) return null;
    
    // 脱敏手机号
    data = data.replaceAll("(\\d{3})\\d{4}(\\d{4})", "$1****$2");
    
    // 脱敏身份证号
    data = data.replaceAll("(\\d{6})\\d{8}(\\d{4})", "$1********$2");
    
    // 脱敏邮箱
    data = data.replaceAll("(\\w{2})\\w*(\\w@\\w+\\.\\w+)", "$1***$2");
    
    return data;
}

10.3 日志格式统一

public class LogFormat {
    
    public static String formatOperation(String operation, String method, Object... args) {
        return String.format("[操作:%s] [方法:%s] [参数:%s]", 
                           operation, method, Arrays.toString(args));
    }
    
    public static String formatPerformance(String method, long time, Object result) {
        return String.format("[性能监控] [方法:%s] [耗时:%dms] [结果:%s]", 
                           method, time, result);
    }
}

11. 总结

Spring Boot AOP日志记录提供了强大的横切关注点处理能力,通过合理使用切面编程可以:

  • 统一管理日志记录逻辑
  • 减少代码重复
  • 提高代码可维护性
  • 实现灵活的日志策略

在实际应用中,需要根据业务需求选择合适的日志记录方式,并注意性能优化和敏感信息保护。