0
点赞
收藏
分享

微信扫一扫

基于 Spring AOP 和自定义注解的接口调用次数统计功能:限制时间范围,并使用 Redis 存储统计数据


基于 Spring AOP 和自定义注解的接口调用次数统计功能

完善上篇博文中的代码,使其支持 @Countable 注解中的 time 属性,并且只统计指定时间之前的接口调用次数,同时将统计信息存入 Redis,我们需要进行以下改进:

  1. 自定义注解:添加 time 属性。
  2. 引入 Redis 依赖:用于存储和检索调用次数统计。
  3. 调整切面逻辑:在切面中处理时间条件,并使用 Redis 存储统计数据。
  4. 创建 Redis 配置和服务:用于与 Redis 进行交互。

步骤 1:修改自定义注解

我们将在 @Countable 注解中添加一个 time 属性,表示方法调用的截止时间(以毫秒为单位)。

package com.example.demo.annotation;

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

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Countable {
    String value() default "";
    long time() default Long.MAX_VALUE; // 默认值为 Long.MAX_VALUE 表示没有时间限制
}

步骤 2:引入 Redis 依赖

确保你的 pom.xmlbuild.gradle 文件中有 Spring Data Redis 的依赖:

<!-- Maven -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

// Gradle
implementation 'org.springframework.boot:spring-boot-starter-data-redis'

步骤 3:配置 Redis

创建 Redis 配置类来设置连接参数。

package com.example.demo.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.StringRedisSerializer;

@Configuration
public class RedisConfig {

    @Bean
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
        RedisTemplate<String, Object> template = new RedisTemplate<>();
        template.setConnectionFactory(factory);
        template.setKeySerializer(new StringRedisSerializer());
        template.setValueSerializer(new StringRedisSerializer());
        return template;
    }
}

步骤 4:调整切面逻辑

更新切面逻辑以考虑时间条件,并将调用次数保存到 Redis 中。

package com.example.demo.aspect;

import com.example.demo.annotation.Countable;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.data.redis.core.RedisTemplate;

import java.lang.reflect.Method;
import java.time.Instant;
import java.util.concurrent.TimeUnit;

@Aspect
@Component
public class MethodInvocationCounterAspect {

    private final RedisTemplate<String, Object> redisTemplate;

    @Autowired
    public MethodInvocationCounterAspect(RedisTemplate<String, Object> redisTemplate) {
        this.redisTemplate = redisTemplate;
    }

    // 定义切入点,匹配所有带有 @Countable 注解的方法
    @Pointcut("@annotation(com.example.demo.annotation.Countable)")
    public void countableMethods() {}

    // 环绕通知,以便可以在方法执行前后检查时间条件并更新计数
    @Around("countableMethods()")
    public Object countMethodInvocations(ProceedingJoinPoint joinPoint) throws Throwable {
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        Method method = signature.getMethod();
        Countable countable = method.getAnnotation(Countable.class);

        // 获取当前时间和注解中的截止时间
        long currentTime = System.currentTimeMillis();
        long endTime = countable.time();

        if (currentTime <= endTime || endTime == Long.MAX_VALUE) {
            // 执行目标方法
            Object result = joinPoint.proceed();

            // 更新 Redis 中的计数
            String methodName = joinPoint.getTarget().getClass().getName() + "." + method.getName();
            Long currentCount = redisTemplate.opsForValue().increment(methodName, 1);

            // 设置过期时间(可选)
            if (endTime != Long.MAX_VALUE) {
                redisTemplate.expireAt(methodName, Instant.ofEpochMilli(endTime));
            }

            return result;
        } else {
            // 如果超过了截止时间,则不执行方法并返回 null 或抛出异常
            throw new IllegalStateException("Method call is beyond the allowed time.");
        }
    }
}

步骤 5:应用到 Controller

继续在 Controller 中使用 @Countable 注解来标记需要统计的方法,并指定 time 属性。

package com.example.demo.controller;

import com.example.demo.annotation.Countable;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/api")
public class MyController {

    @GetMapping("/hello")
    @Countable(time = 1708000000000L) // 指定时间戳,例如:2024-02-26T00:00:00Z
    public String sayHello() {
        return "Hello, World!";
    }

    @GetMapping("/bye")
    @Countable(value = "Goodbye", time = 1708000000000L)
    public String sayGoodbye() {
        return "Goodbye!";
    }
}

步骤 6:添加控制器获取统计数据

创建一个新的控制器方法来从 Redis 中获取并返回当前的调用次数统计信息。

package com.example.demo.controller;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.Map;
import java.util.Set;

@RestController
@RequestMapping("/stats")
public class StatsController {

    private final RedisTemplate<String, Object> redisTemplate;

    @Autowired
    public StatsController(RedisTemplate<String, Object> redisTemplate) {
        this.redisTemplate = redisTemplate;
    }

    @GetMapping("/method-counts")
    public Map<String, Object> getMethodCounts() {
        Set<String> keys = redisTemplate.keys("*");
        return redisTemplate.opsForValue().multiGet(keys).stream()
                .collect(Collectors.toMap(key -> key, value -> value));
    }
}

总结

通过以上步骤,我们实现了对带有 @Countable 注解的方法进行调用次数统计的功能,并且:

  • 添加了 time 属性来限制统计的时间范围。
  • 使用 Redis 来存储和管理调用次数统计信息。
  • 提供了一个接口来查看这些统计数据。


举报

相关推荐

0 条评论