【Java】Spring自定义注解令牌桶

首先准备一些必要的参数

public class Constants {
    //容量
    public static final int CAPACITY = 10;
    //增长速率
    public static final int RISE_COUNT = 1;
    //增长时间间隔(毫秒)
    public static final int fixedRate = 1000;
    //所有的桶名
    public static final String[] BUCKETS = {"demo","demo2","demo3"};
}

构造一个桶类,桶要能生成令牌,存放令牌,取出令牌

public class Bucket {

    private final String name;
    private final int maxCount;

    private int size;
    private final int riseCount;

    public Bucket() {
        maxCount = Constants.CAPACITY;
        riseCount = Constants.RISE_COUNT;
        name = "bucket";
    }
    public Bucket(String name) {
        this.maxCount = Constants.CAPACITY;
        this.riseCount = Constants.RISE_COUNT;
        this.name = name;
    }
    public Bucket(String name, int maxCount, int riseCount) {
        this.name = name;
        this.maxCount = maxCount;
        this.riseCount = riseCount;
    }

    @Override
    public boolean equals(Object obj) {
        if (obj == null)
            return false;
        Bucket bucket = (Bucket) obj;
        return bucket.size == size && bucket.riseCount == riseCount && bucket.maxCount == maxCount;
    }
   
    @Override
    public int hashCode() {
        return Objects.hash(name, maxCount, size, riseCount);
    } 
 
    public String getName() {
        return name;
    }

    public int getSize() {
        return size;
    }

    public boolean isFull() {
        return size == maxCount;
    }

    public void genTokens() {
        for (int i = 0; i < riseCount; i++)
            genToken();
    }

    void genToken() {
        if (!isFull())
            size++;
    }

    public synchronized int getToken() {
        if (size > -1)
            size--;
        return size;
    }

}

创建BucketManager

public class BucketManager {
    private static final BucketManager instance = new BucketManager();

    public static volatile HashMap<String, Bucket> buckets = new HashMap(Constants.CAPACITY);

    public static BucketManager getInstance(){
        return instance;
    }

    public void genBucket(String name){
        Bucket bucket = new Bucket(name);
        buckets.put(name,bucket);
    }

    public synchronized HashMap<String, Bucket> getBuckets(){
        return buckets;
    }
    public synchronized Bucket getBucket(String name) throws BucketManagerException {
        if(buckets.containsKey(name)){
            return buckets.get(name);
        }
        throw new BucketManagerException("not contain key: "+ name);
    }

启动类
添加@EnableScheduling

@EnableScheduling
@SpringBootApplication
public class TokenBucketApplication {

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

		//创建令牌桶
		for (String name: Constants.BUCKETS)
			BucketManager.getInstance().genBucket(name);

	}

	@Scheduled(fixedRate = Constants.fixedRate)
	public void timer() {
		BucketScheduled.genTokens(); //定时生产令牌
	}


}

这样令牌桶就完成了。

接下来创建自定义注解

/**
 * 需要消耗令牌的方法加上该注释 设置桶名
 */
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface RequireToken {
    /**桶名*/
    String BucketName();
}


创建拦截器TokenBucketInterceptor

public class TokenBucketInterceptor implements HandlerInterceptor {

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws BucketManagerException {
        //不相干的
        if (!(handler instanceof HandlerMethod)) {
            return true;
        }

        boolean isAssignableFrom = handler.getClass().isAssignableFrom(HandlerMethod.class);
        //有注解的
        if(isAssignableFrom){
            RequireToken assertTokenBucket = ((HandlerMethod) handler).getMethodAnnotation(RequireToken.class);
            //有RequireToken注解的
            if(assertTokenBucket == null || assertTokenBucket.BucketName().equals("")){
                return true;
            }else {

                int token = BucketManager.getInstance().getBucket(assertTokenBucket.BucketName()).getToken();
                if(token>=0){
                    System.out.println("Bucket:"+ assertTokenBucket.BucketName() +" get token success,surplus:"+token);
                    return true;
                }
                else{
                    System.out.println("Bucket:"+ assertTokenBucket.BucketName()+" none token");
                    return false;
                }
            }
        }
        return true;
    }
}

WebMvcConfigurer添加拦截器

@Configuration
public class WebMvcConfigurer extends WebMvcConfigurationSupport {

   @Override
   public void addInterceptors(InterceptorRegistry registry)
    {
        registry.addInterceptor(tokenBucketInterceptor()).addPathPatterns("/**");
        super.addInterceptors(registry);
    }

    @Bean
    public TokenBucketInterceptor tokenBucketInterceptor()
    {
        return new TokenBucketInterceptor();
    }
}

注解创建完成了。

在需要使用令牌桶的接口方法上加上注解@RequireToken

@RestController
@ResponseBody
public class Api {

    @RequireToken(BucketName="demo")
    @GetMapping("/demo")
    public String demo() {
        System.out.println("demo dosomething");
        return "ok";
    }
}

以1秒生成一个令牌,容量为10 举例
快速请求11次
输出:
Bucket:demo get token success,surplus:9
demo dosomething
Bucket:demo get token success,surplus:8
demo dosomething
Bucket:demo get token success,surplus:7
demo dosomething
Bucket:demo get token success,surplus:6
demo dosomething
Bucket:demo get token success,surplus:5
demo dosomething
Bucket:demo get token success,surplus:4
demo dosomething
Bucket:demo get token success,surplus:3
demo dosomething
Bucket:demo get token success,surplus:2
demo dosomething
Bucket:demo get token success,surplus:1
demo dosomething
Bucket:demo get token success,surplus:0
demo dosomething
Bucket:demo none token

以上是一个基础版
根据业务需要,令牌的生产及管理可以与桶分开,且根据应对高峰情况对容量和增量做动态调整。

完整源码

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值