You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Spring Boot自定义@LogExecutionProfile注解结合Aspect不生效排查

自定义注解@LogExecutionProfile切面不生效排查与修复

问题概述

开发Spring Boot应用时,自定义@LogExecutionProfile注解用于记录方法执行时间和内存占用,但切面逻辑未生效。调用被注解方法时仅输出服务内部日志,未输出切面的执行统计日志。

代码详情

1. build.gradle关键AOP依赖

dependencies {
    // 其他依赖省略...
    implementation 'org.springframework.boot:spring-boot-starter-aop'
    implementation 'org.aspectj:aspectjweaver'
}

2. 自定义注解LogExecutionProfile

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface LogExecutionProfile {}

3. 原日志切面类LoggingAspect

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.springframework.stereotype.Component;

import lombok.extern.slf4j.Slf4j;

@Slf4j
@Aspect
@Component
public class LoggingAspect {

    @Around("@annotation(LogExecutionProfile) || execution(public * @LogExecutionProfile *.*(..)))")
    public Object profileMethod(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
        MethodSignature methodSignature = (MethodSignature) proceedingJoinPoint.getSignature();
        String className = methodSignature.getDeclaringType().getSimpleName();
        String methodName = methodSignature.getName();

        long startTime = System.currentTimeMillis();
        long memoryAtStart = Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory();

        Object result = proceedingJoinPoint.proceed();

        long endTime = System.currentTimeMillis();
        long memoryAtEnd = Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory();

        log.debug("execution time: " + (endTime - startTime) + " ms"
                + " memory used: " + (memoryAtEnd - memoryAtStart) + " bytes of memory");

        return result;
    }
}

4. 服务类MyService

import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;

@Slf4j
@Service
public class MyService {

    @LogExecutionProfile
    public void someServiceMethod(){
        log.debug("service log");
        // 业务逻辑
    }
}

排查要点与修复方案

1. 日志级别配置问题(最易忽略)

切面使用log.debug()输出日志,但Spring Boot默认日志级别为INFO,会过滤DEBUG级别的日志。

修复:在application.properties或application.yml中开启对应包的DEBUG日志:

# 根据切面所在包调整路径
logging.level.com.example.aspect=DEBUG
# 或全局开启DEBUG(仅调试用,生产环境建议关闭)
logging.level.root=DEBUG

2. 切点表达式错误

原切面的切点表达式存在语法错误:execution(public * @LogExecutionProfile *.*(..)))中注解位置错误(不应放在返回值与方法名之间),且多了一个闭合括号。

正确切点写法:
直接匹配注解的最简写法(需指定注解全类名,避免包冲突):

@Around("@annotation(com.example.annotation.LogExecutionProfile)")
public Object profileMethod(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
    // 原有逻辑不变
}

或使用Pointcut拆分(结构更清晰):

@Slf4j
@Aspect
@Component
public class LoggingAspect {

    @Pointcut("@annotation(com.example.annotation.LogExecutionProfile)")
    public void annotatedWithLogExecutionProfile() {}

    @Around("annotatedWithLogExecutionProfile()")
    public Object profileMethod(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
        MethodSignature methodSignature = (MethodSignature) proceedingJoinPoint.getSignature();
        String className = methodSignature.getDeclaringType().getSimpleName();
        String methodName = methodSignature.getName();

        long startTime = System.currentTimeMillis();
        long memoryAtStart = Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory();

        Object result = proceedingJoinPoint.proceed();

        long endTime = System.currentTimeMillis();
        long memoryAtEnd = Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory();

        // 优化日志格式,增加类名和方法名便于定位
        log.debug("{}#{} - execution time: {} ms, memory used: {} bytes",
                className, methodName, (endTime - startTime), (memoryAtEnd - memoryAtStart));

        return result;
    }
}

3. Spring容器扫描范围问题

确保LoggingAspect类上的@Component注解生效,且所在包被Spring的组件扫描覆盖:

  • 主启动类的@SpringBootApplication默认扫描自身所在包及子包,若切面在主类包外,需手动添加@ComponentScan(basePackages = {"com.example", "com.other.aspect"})。

4. 内部方法调用导致代理失效

若someServiceMethod是被同一个类内的其他方法调用(而非通过Spring容器代理对象调用),Spring AOP的动态代理不会生效。

修复:

  • 通过Spring容器获取MyService的代理对象调用方法;
  • 或开启暴露代理:在主启动类添加@EnableAspectJAutoProxy(exposeProxy = true),然后通过AopContext.currentProxy()获取代理对象调用方法。

5. AOP代理方式验证

Spring Boot 2.x+默认会根据目标类是否实现接口自动选择JDK动态代理或CGLIB代理。若目标类无接口但代理失效,可手动指定CGLIB代理:

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

验证步骤

  1. 调整日志级别后启动应用;
  2. 通过Spring容器调用MyService.someServiceMethod();
  3. 查看日志是否同时输出service log和切面的执行统计日志。

内容的提问来源于stack exchange,提问作者Rupesh

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.28 13:35:37