Java框架篇_12 AOP+反射+自定义注解应用封装限流注解


限流API

首先我来介绍一个限流的API,是来自google的guava,guava的用法如下:

guava的maven依赖

        <dependency>
            <groupId>com.google.guava</groupId>
            <artifactId>guava</artifactId>
            <version>18.0</version>
        </dependency>

这是一段SpringBoot的项目的controller层代码,这里有使用guava的限流API

@RestController
@RequestMapping("/currentLimiting")
public class CurrentLimitingController {

    /**
     * 每秒生成2.0个令牌
     */
    private RateLimiter rateLimiter = RateLimiter.create(2.0);

    @GetMapping("/getMessage")
    public String getMessage() {
        return rateLimiter.tryAcquire() ? "访问成功!" : "当前访问人数过多,请稍后尝试!";
    }
}

RateLimiter就是限流的API,首先要创建该对象。
然后tryAcquire()会返回一个能否获取到令牌的boolean值。
测试一下,浏览器狂按F5刷新可以看到效果。
在这里插入图片描述

封装限流注解设计

上面的API使用起来还是比较麻烦,比如多种场景(限流规则不一样)下看起来就不是很简洁。我非常赞同使用框架化的思维去简化使用,像现在的Spring Boot框架,都是采用注解化的形式,只需要在目标处打上注解,可以说使用起来非常方便,看起来也很简洁。

所以这篇文章最重要的是如何实现封装注解以及框架化的思想,封装限流注解只不过是个举例罢了。

对于封装限流注解,我们应先缕清实现思路:

  1. 需要自定义一个用于限流的注解;
  2. 限流注解如何生效?

定义一个注解

@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface RequestCurrentLimiting {
    /**
     * 每秒访问的次数
     * 默认可以访问20次
     * @return
     */
    double token() default 20;

    /**
     * 被限流拦截返回客户端的消息
     * @return
     */
    String message() default "无法访问!";
}

AOP的环绕通知

如何判断方法上是否有加上该限流注解,我们可以使用Spring的Aop技术,具体来说是环绕通知,它的通知方式是这样的:

@Component
@Aspect
public class CurrentLimitAop {

    @Around("@annotation(com.symc.util.annotation.RequestCurrentLimiting)") //拦截的注解
    public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
      	//拦截代码(目标方法之前的代码)
        Object proceed = joinPoint.proceed(); //执行目标方法
        //目标方法之后的代码
        return proceed;
    }

}

Aop拦截的目标是定义好的注解。
个人对注解的理解是,一旦你给某个目标打上某注解(这个目标可以是方法、属性、类、注解等等)那么这个目标就会继承这个注解的所有特性。这样的话,当Aop拦截注解时,也就相当于拦截了所有带该注解的目标。

那为什么选用环绕通知呢?原因是非常灵活地控制目标方法是否执行,当拦截的时候,就不允许业务逻辑层的代码执行,而其它的通知方法都难以控制目标方法的执行。

实现限流的代码

下面就是实现限流的具体代码:

@Component
@Aspect
public class CurrentLimitAop {

    private ConcurrentHashMap<String, RateLimiter> rateLimiterMap =
            new ConcurrentHashMap<>();

    @Around("@annotation(com.symc.util.annotation.RequestCurrentLimiting)")
    public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
        //获取拦截的方法名
        MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
        //获取拦截方法上的RequestCurrentLimiting注解对象
        RequestCurrentLimiting requestCurrentLimitingAnnotation =
                methodSignature.getMethod().getAnnotation(RequestCurrentLimiting.class);
        //限流API
        RateLimiter rateLimiter = rateLimiterMap.get(methodSignature.toString());
        //如果没有rateLimiter,则创建一个
        if (rateLimiter==null) {
            //获取该方法的token(限流值)
            rateLimiter = RateLimiter.create(requestCurrentLimitingAnnotation.token());
            rateLimiterMap.put(methodSignature.toString(),rateLimiter);
        }
        //判断是否限流,若限流就return
        if (!rateLimiter.tryAcquire()) {
            return requestCurrentLimitingAnnotation.message();
        }
        //程序执行到这里说明没有被限流拦截,执行目标方法
        Object proceed = joinPoint.proceed();
        return proceed;
    }
}

我们可以先测试一下,我的测试用例:

@RestController
@RequestMapping("/currentLimiting")
public class CurrentLimitingController {

    /**
     * 每秒生成2.0个令牌
     */
    private RateLimiter rateLimiter = RateLimiter.create(2.0);

    @GetMapping("/insert")
    @RequestCurrentLimiting(token = 2.0,message = "insert失败,请稍后尝试!")
    public String insert(){
        return "insert success";
    }

    @GetMapping("/delete")
    @RequestCurrentLimiting(token = 0.5,message = "delete失败,请稍后尝试!")
    public String delete(Integer id){
        return "delete success";
    }
}

用浏览器方法,然后一直刷新就能看到效果啦。

这个代码的实现原理和执行过程是这样的:
首先我们在加注解的时候设置了访问量以及响应消息,当然,如果你的响应消息是JSON格式,你可以在源码上改进一下,这个不难。
当执行方法时,检查到 @RequestCurrentLimiting立马被Aop拦截下来(本质拦截的该注解),然后通过代码中的API获取到目标方法的完整方法名,像这样public void com.symc.controller.CurrentLimitingController.delete(Integer id),你可以自己打印在控制台观察一下。
然后通过反射机制拿到该方法上面的RequestCurrentLimiting注解的对象,后面我们需要从这个对象获取token和message。
之后就用到了guava的限流API,在这之前将限流执行的对象存储起来,创建一个ConCurrentHashMap来存储,目的是希望它能批量地用于多种不同的场景,并且减少创建guavaAPI对象的消耗,存储的key就用上面的方法名,value用guavaAPI对象。
如果被拦截的话,说明被限流了,业务代码不必执行,直接将message返回。否则的话就放行。

限流注解的完整源码

package com.symc.util.annotation;

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

/**
 * @Author: Forward Seen
 * @CreateTime: 2022/05/01 15:52
 * @Description: 限流注解
 * 直接将注解打在控制层的方法上,需要配合Spring框架的Controller注解使用
 * 你在使用的时候可以设置token的值来自定义每秒限制的访问量
 * 还可以设置message的值来自定义返回给前端的消息数据
 * 
 * 该注解依赖Spring的Aop技术、Google的guava限流API技术
 * 实现技术:Aop环绕通知 + guava + 反射 + 注解
 */
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface RequestCurrentLimiting {
    /**
     * 每秒访问的次数
     * 默认可以访问20次
     * @return
     */
    double token() default 20;

    /**
     * 被限流拦截返回客户端的消息
     * @return
     */
    String message() default "无法访问!";
}

package com.symc.aop;

import com.google.common.util.concurrent.RateLimiter;
import com.symc.util.annotation.RequestCurrentLimiting;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;

import java.util.concurrent.ConcurrentHashMap;

/**
 * @Author: Forward Seen
 * @CreateTime: 2022/05/01 16:04
 * @Description: 用于拦截 public @interface com.symc.util.annotation.RequestCurrentLimiting
 */
@Component
@Aspect
public class CurrentLimitAop {

    private ConcurrentHashMap<String, RateLimiter> rateLimiterMap =
            new ConcurrentHashMap<>();

    @Around("@annotation(com.symc.util.annotation.RequestCurrentLimiting)")
    public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
        MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
        RequestCurrentLimiting requestCurrentLimitingAnnotation =
                methodSignature.getMethod().getAnnotation(RequestCurrentLimiting.class);
        RateLimiter rateLimiter = rateLimiterMap.get(methodSignature.toString());
        if (rateLimiter==null) {
            rateLimiter = RateLimiter.create(requestCurrentLimitingAnnotation.token());
            rateLimiterMap.put(methodSignature.toString(),rateLimiter);
        }
        if (!rateLimiter.tryAcquire()) {
            return requestCurrentLimitingAnnotation.message();
        }
        Object proceed = joinPoint.proceed();
        return proceed;
    }

}
  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值