AOP实现方法访问限制

对于一些方法尤其是对接某个API的通常会有对于请求次数,文件大小等的限制,通过java的面向切面变成实现一个访问限制器
1.使用元注解自定义一个注解,并包含使用所需要的一些属性

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface RateLimited {
    int maxTokens(); // 单位时间允许最大请求次数
    long refillInterval(); // 请求刷新时间
    TimeUnit unit(); // 请求刷新时间单位
    long maxFileSize() default -1; // 允许最大的文件大小
    long maxExecutionTime() default -1; // 方法允许的最大执行时间
    int retryTime() default 0;  // 方法重试次数
}

2. 定义一个类,主要保存当前对于该方法请求的状态,以及获得请求令牌和刷新请求令牌的逻辑

public class RateLimiter {
    private final int maxTokens;
    private final long refillInterval;
    private final AtomicInteger tokens;
    private long lastRefillTimestamp;

    public RateLimiter(int maxTokens, long refillInterval, TimeUnit unit) {
        this.maxTokens = maxTokens;
        this.refillInterval = unit.toMillis(refillInterval);
        this.tokens = new AtomicInteger(maxTokens);
        this.lastRefillTimestamp = System.currentTimeMillis();
    }

    public synchronized boolean tryAcquire() {
        refill();
        if (tokens.get() > 0) {
            tokens.decrementAndGet();
            return true;
        }
        return false;
    }

    private void refill() {
        long now = System.currentTimeMillis();
        if (now - lastRefillTimestamp > refillInterval) {
            tokens.set(maxTokens);
            lastRefillTimestamp = now;
        } else {
            long interval = refillInterval / maxTokens;
            if (now - lastRefillTimestamp > interval){
                tokens.addAndGet(1);
            }
        }
    }
}

3.定义切面类,对于请求的过滤的逻辑,以及定义一个环绕通知

@Aspect
@Component
public class RateLimiterAspect {
    private final ConcurrentHashMap<String, RateLimiter> rateLimiters = new ConcurrentHashMap<>();

    @Around("@annotation(rateLimited)")
    public Object rateLimit(ProceedingJoinPoint joinPoint, RateLimited rateLimited) {
        String key = joinPoint.getSignature().toShortString();
        RateLimiter rateLimiter = rateLimiters.computeIfAbsent(key, k -> new RateLimiter(
                rateLimited.maxTokens(),
                rateLimited.refillInterval(),
                rateLimited.unit()
        ));

        if (!rateLimiter.tryAcquire()) {
            throw new RuntimeException("请求次数过多,请稍后重试.");
        }

        for (Object arg : joinPoint.getArgs()) {
            if (arg instanceof Document) {
                Document document = (Document) arg;
                if (rateLimited.maxFileSize() != -1 && document.getFileSize() > rateLimited.maxFileSize()) {
                    String format = String.format("文件大小超过限制,文件大小%skb,允许最大文件为%skb",document.getFileSize(), rateLimited.maxFileSize());
                    throw new RuntimeException(format);
                }
            }
        }

        int retryTimes = rateLimited.retryTime();
        long maxExecutionTime = rateLimited.maxExecutionTime();
        for (int attempt = 0; attempt <= retryTimes; attempt++) {
            ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1);
            Future<Object> future = null;
            ScheduledFuture<?> timeoutFuture = null;
            try {
                future = Executors.newSingleThreadExecutor().submit(() -> {
                    try {
                        return joinPoint.proceed();
                    } catch (Throwable e) {
                        throw new RuntimeException(e);
                    }
                });
                if (maxExecutionTime != -1) {
                    Future<Object> finalFuture = future;
                    timeoutFuture = scheduler.schedule(() -> finalFuture.cancel(true), maxExecutionTime, TimeUnit.SECONDS);
                }
                return future.get();
            } catch (Throwable throwable) {
                if (attempt == retryTimes) {
                    throw new RuntimeException("方法执行时间超过限制,方法允许执行最大时长" + maxExecutionTime + "s");
                }
            } finally {
                if (timeoutFuture != null) {
                    timeoutFuture.cancel(true);
                }
                if (future != null) {
                    future.cancel(true);
                }
                scheduler.shutdown();
            }
        }
        throw new RuntimeException("方法执行出错,请联系管理员");
    }
}

使用的时候只需要在指定方法添加注解@RateLimited就可以
 

@RateLimited(maxTokens = 10, refillInterval = 1, unit = TimeUnit.HOURS, maxFileSize = 10 * 1024, maxExecutionTime = 30 * 60)
public void testRateLimited(Object arg) {
    // your code。。。
}

这个例子就是表示每个小时允许十次对于方法的访问,允许文件最大为10MB,方法允许最长执行时间为30分钟,可重试次数为默认的0次

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值