最近在刷牛客:使用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 是错误的

相关推荐
砚底藏山河13 小时前
容错重试与指数退避:网络抖动手抖不再丢数据(魔码量化实战 #04)
java·数据库·python·金融
Charlie_Byte13 小时前
Integer 缓存机制(JDK 17 源码)
后端
梨涡泥窝13 小时前
JavaWeb——基于 Spring Boot + Vue 的图书管理系统的设计与实现
vue.js·spring boot·后端
2501_9304724414 小时前
云安全自查清单:用腾讯云助手 30 分钟完成一次权限与密钥体检
java·腾讯云
jsl_jsl_jsl14 小时前
LLM工具调用速记
后端
YIAN14 小时前
手撸社区后端:一套可落地的用户 - 文章 - 互动体系 MySQL 表设计与优化思路
后端·mysql
ThinkerQAQ_14 小时前
并发编程(一):先谈硬件——从 count++ 到原子性、可见性与有序性
后端
letisgo514 小时前
JAVA 高级进阶05篇《Spring容器机制:Bean生命周期与三级缓存全解》
java·spring·面试·ioc·源码分析
岁月如歌778614 小时前
分布式锁完全指南:从数据库到 Redisson 的演进
java·后端·架构
技术猫14 小时前
记一次因线程池不合理使用导致生产拨测告警
后端