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); } }
验证步骤
- 调整日志级别后启动应用;
- 通过Spring容器调用
MyService.someServiceMethod(); - 查看日志是否同时输出
service log和切面的执行统计日志。
内容的提问来源于stack exchange,提问作者Rupesh
相关产品推荐
相关产品推荐

