如何用Spring AOP/AspectJ拦截重写方法A()调用的Test类方法并统计耗时?
刚好之前做过类似的需求,给你分两种方案详细说下——Spring AOP和AspectJ,各自适用不同场景,你可以根据项目情况挑:
方案一:Spring AOP(适合大多数Spring项目)
Spring AOP基于动态代理实现,上手简单,但只对Spring管理的Bean生效,且无法拦截同一个类内部的方法调用(比如Test类自己的A()调用自己的b())。步骤如下:
- 引入依赖
如果是Spring Boot项目,直接添加starter依赖即可:
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-aop</artifactId> </dependency>
- 编写切面类
定义一个带@Aspect和@Component注解的切面类,通过调用栈判断目标方法是否被Test类的重写A()调用,然后统计耗时:
import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; import org.springframework.stereotype.Component; import java.lang.reflect.Method; import java.util.Arrays; @Aspect @Component public class MethodInvocationTimeAspect { // 切入点匹配需要统计的b()、c()方法,可根据实际包/类调整 @Around("execution(* com.yourproject..*(..)) && (execution(* *.b(..)) || execution(* *.c(..)))") public Object recordMethodTime(ProceedingJoinPoint joinPoint) throws Throwable { // 检查当前调用栈,判断是否来自Test类的A()方法 StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace(); boolean isInvokedByTestA = Arrays.stream(stackTrace) .anyMatch(element -> "com.yourproject.Test".equals(element.getClassName()) && "A".equals(element.getMethodName())); if (!isInvokedByTestA) { // 不是目标调用链,直接放行 return joinPoint.proceed(); } // 统计耗时 long start = System.nanoTime(); Object result = joinPoint.proceed(); long duration = (System.nanoTime() - start) / 1000000; // 转毫秒 Method targetMethod = ((org.aspectj.lang.reflect.MethodSignature) joinPoint.getSignature()).getMethod(); System.out.printf("[耗时统计] 方法%s被Test类的A()调用,耗时:%d ms%n", targetMethod.getName(), duration); return result; } }
方案二:AspectJ(解决内部调用/非Bean方法场景)
如果你的场景存在同一个类内部调用(比如Test.A()调用Test.b()),或者目标方法不在Spring Bean中,那AspectJ是更好的选择——它通过字节码织入实现,能拦截所有方法调用,不受代理限制。
- 引入依赖
除了Spring AOP的starter,还需要AspectJ的weaver:
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-aop</artifactId> </dependency> <dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjweaver</artifactId> </dependency>
- 开启AspectJ自动代理
在application.properties中添加配置:
spring.aop.proxy-target-class=true spring.aop.auto=true
- 编写AspectJ切面
AspectJ支持更强大的切入点表达式,比如直接用cflow(控制流)匹配被Test.A()调用的方法:
import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; @Aspect public class MethodInvocationTimeAspect { // 切入点:匹配被Test类及其子类的A()方法调用的b()/c()方法 @Around("(call(* *.b(..)) || call(* *.c(..))) && cflow(execution(* com.yourproject.Test+.A(..)))") public Object recordMethodTime(ProceedingJoinPoint joinPoint) throws Throwable { long start = System.nanoTime(); Object result = joinPoint.proceed(); long duration = (System.nanoTime() - start) / 1000000; String methodName = joinPoint.getSignature().getName(); System.out.printf("[耗时统计] 方法%s被Test类的A()调用,耗时:%d ms%n", methodName, duration); return result; } }
这里的Test+表示Test类及其所有子类,刚好适配你"被重写的A()"的需求。
额外注意事项
- 如果需要更严谨的统计,建议用
System.nanoTime()代替System.currentTimeMillis(),前者不受系统时间调整影响。 - 不要直接用
System.out打印,建议接入项目的日志框架(如SLF4J),方便后续排查。 - 如果是编译时织入(CTW),还需要配置AspectJ的Maven/Gradle插件,不过Spring Boot项目一般用加载时织入(LTW),上面的配置就足够了。
内容的提问来源于stack exchange,提问作者Crazy-Coder
相关产品推荐
相关产品推荐

