最近在刷牛客:使用Spring AOP实现性能监控时

题目:4.使用Spring AOP实现性能监控时,下列哪种方式无法获取方法执行时间?

A在@Around增强中通过ProceedingJoinPoint.proceed()调用前后计算时间差

B在@AfterReturning增强中通过JoinPoint获取方法开始时间戳

C使用@Before记录开始时间并存入ThreadLocal,在@After中计算耗时

D通过实现MethodInterceptor接口在invoke方法中计算执行时间

项目目标

  • 使用 Spring Boot

  • 演示 4 种 AOP 方式

  • 验证 哪一种不能获取方法执行时间

  • 结构清晰,适合练习和面试


一、项目结构(强烈建议你照着建)

复制代码
spring-aop-demo
├── pom.xml
└── src
    └── main
        └── java
            └── com
                └── example
                    └── aopdemo
                        ├── AopDemoApplication.java
                        ├── service
                        │   └── UserService.java
                        ├── aspect
                        │   ├── TimeAspect.java
                        │   └── TimeInterceptorConfig.java
                        └── interceptor
                            └── TimeMethodInterceptor.java

二、pom.xml(完整)

XML 复制代码
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
         https://maven.apache.org/xsd/maven-4.0.0.xsd">

    <modelVersion>4.0.0</modelVersion>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>3.2.1</version>
        <relativePath/>
    </parent>

    <groupId>com.example</groupId>
    <artifactId>spring-aop-demo</artifactId>
    <version>1.0-SNAPSHOT</version>

    <dependencies>
        <!-- Web -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <!-- AOP -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-aop</artifactId>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
</project>

三、启动类

java 复制代码
package com.example.aopdemo;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class AopDemoApplication {

    public static void main(String[] args) {
        SpringApplication.run(AopDemoApplication.class, args);
    }
}

四、业务类(被代理对象)

java 复制代码
package com.example.aopdemo.service;

import org.springframework.stereotype.Service;

@Service
public class UserService {

    public void login() throws InterruptedException {
        Thread.sleep(300); // 模拟方法执行
        System.out.println("执行 login 方法");
    }
}

五、AOP 切面(核心)

java 复制代码
package com.example.aopdemo.aspect;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;

@Aspect
@Component
public class TimeAspect {

    /* ========== A. @Around(能统计时间) ========== */
    @Around("execution(* com.example.aopdemo.service..*(..))")
    public Object around(ProceedingJoinPoint pjp) throws Throwable {
        long start = System.currentTimeMillis();
        Object result = pjp.proceed();
        long end = System.currentTimeMillis();
        System.out.println("[Around] 耗时:" + (end - start));
        return result;
    }

    /* ========== B. @AfterReturning(不能统计时间) ========== */
    @AfterReturning("execution(* com.example.aopdemo.service..*(..))")
    public void afterReturning(JoinPoint jp) {
        System.out.println("[AfterReturning] 无法获取开始时间");
    }

    /* ========== C. @Before + @After + ThreadLocal(能统计时间) ========== */
    private static final ThreadLocal<Long> TIME =
            ThreadLocal.withInitial(System::currentTimeMillis);

    @Before("execution(* com.example.aopdemo.service..*(..))")
    public void before() {
        TIME.set(System.currentTimeMillis());
    }

    @After("execution(* com.example.aopdemo.service..*(..))")
    public void after() {
        long cost = System.currentTimeMillis() - TIME.get();
        System.out.println("[Before+After] 耗时:" + cost);
        TIME.remove();
    }
}

六、MethodInterceptor 方式(能统计时间)

1. 拦截器

java 复制代码
package com.example.aopdemo.interceptor;

import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;

public class TimeMethodInterceptor implements MethodInterceptor {

    @Override
    public Object invoke(MethodInvocation invocation) throws Throwable {
        long start = System.currentTimeMillis();
        Object result = invocation.proceed();
        long end = System.currentTimeMillis();
        System.out.println("[MethodInterceptor] 耗时:" + (end - start));
        return result;
    }
}

2. 配置类

java 复制代码
package com.example.aopdemo.aspect;

import com.example.aopdemo.interceptor.TimeMethodInterceptor;
import org.springframework.aop.framework.ProxyFactoryBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class TimeInterceptorConfig {

    @Bean
    public ProxyFactoryBean userServiceProxy(UserService userService) {
        ProxyFactoryBean factory = new ProxyFactoryBean();
        factory.setTarget(userService);
        factory.addAdvice(new TimeMethodInterceptor());
        return factory;
    }
}

七、测试方式(最简单)

启动项目后访问:

复制代码
GET http://localhost:8080/test

或者直接写测试类:

java 复制代码
@SpringBootTest
class AopDemoApplicationTests {

    @Autowired
    private UserService userService;

    @Test
    void testAop() throws InterruptedException {
        userService.login();
    }
}

八、你运行后会看到类似输出

复制代码
[Around] 耗时:302
[Before+After] 耗时:302
[MethodInterceptor] 耗时:301
[AfterReturning] 无法获取开始时间
执行 login 方法

✅ 一眼就能验证 B 是错误的

相关推荐
葫芦和十三5 小时前
图解 MongoDB 04|索引模型:每建一个索引,就是在 B+-tree 森林里多栽一棵
后端·mongodb·agent
用户47949283569156 小时前
claude Fable用不了?把Gpt 5.5pro接到你的claude code里
前端·后端
GetcharZp8 小时前
告别 Nginx 复杂配置!这款带 Web 面板的万能代理神器,让端口转发变得如此简单
后端
IT_陈寒10 小时前
React的useState居然还有这种坑?我差点删库跑路
前端·人工智能·后端
nanxun88611 小时前
记一次诡异的 Docker 容器"串包"故障排查
java
Pedantic11 小时前
SwiftUI 手势笔记
前端·后端
金銀銅鐵11 小时前
[Python] 从《千字文》中随机挑选汉字
后端·python
用户15630681035114 小时前
Day01 | Java 基础(Java SE)
java
飘尘14 小时前
前端转型全栈(Java后端)的快速上手指引
前端·后端·全栈