使用自定义注解+AOP实现RateLimiter令牌桶限流

Springboot 2.x + AOP + RateLimiter,通过添加自定义注解,对请求方法做限流控制。

1. 添加依赖

        <dependency>
            <groupId>com.google.guava</groupId>
            <artifactId>guava</artifactId>
            <version>28.0-jre</version>
        </dependency>

2. 自定义注解

通过注解指定参数,可以自定义限流策略。
自定义RateLimit和guava的RateLimiter有点像,注意区别。


@Documented
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface RateLimit {

    /**
     * 每秒创建令牌个数,默认为10
     * @return
     */
    double permitsPerSecond() default 10D;

    /**
     * 获取令牌超时时间
     * @return
     */
    long timeout() default 0;
    
	/**
     * 超时时间单位
     * @return
     */
    TimeUnit timeUnit() default TimeUnit.SECONDS;

}

3. AOP拦截实现

定义切入点@Pointcut,拦截注解@RateLimit 的方法,@Around在目标方法之前织入增强,只有获取到令牌才能执行目标方法。

@Aspect
@Component
public class RateLimiterInterceptor {

    /**
     * 不同的方法存放不同的令牌桶
     */
    private final Map<String, RateLimiter> map = new ConcurrentHashMap<>();

    /**
     * 定义切入点,自定义RateLimit
     */
    @Pointcut("@annotation(com.coco.annotation.RateLimit)")
    public void pointCut() {}

    @Around(value = "pointCut()")
    public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
        System.out.println("进入限流控制");
        Object obj = null;
        JSONObject jsonResult = new JSONObject();
        Signature signature = joinPoint.getSignature();
        String methodName = signature.getName();
        Method method = ((MethodSignature) signature).getMethod();
        //获取注解对象
        RateLimit rateLimit = method.getAnnotation(RateLimit.class);
        if (rateLimit != null) {
            double permitsPerSecond = rateLimit.permitsPerSecond();
            long timeout = rateLimit.timeout();
            TimeUnit timeUnit = rateLimit.timeUnit();
            RateLimiter rateLimiter = null;
            if (!map.containsKey(methodName)) {
                // 创建令牌桶
                rateLimiter = RateLimiter.create(permitsPerSecond);
                map.put(methodName, rateLimiter);
            }
            rateLimiter = map.get(methodName);
            if (rateLimiter.tryAcquire(timeout, timeUnit)) {
                System.out.println("获取令牌成功,开始执行");
                obj = joinPoint.proceed();
                return obj;
            } else {
                System.out.println("手速不够");
                jsonResult.put("resultMsg", "手速不够");
                return jsonResult;
            }
        } else {
            System.out.println("未知错误");
            jsonResult.put("resultMsg", "未知错误");
            return jsonResult;
        }
    }

}

4. Controller入口方法

通过添加@RateLimit注解实现限流控制,相关参数可自定义。

    @RequestMapping(value = "/getHero/{heroName}", method = RequestMethod.GET)
    @ApiOperation(value = "查看英雄详情")
    @RateLimit(permitsPerSecond = 10D, timeout = 0, timeUnit = TimeUnit.SECONDS)
    public JSONObject getHero(@PathVariable String heroName) {
        JSONObject jsonResult = new JSONObject();
        jsonResult.put("resultMsg", heroName);
        return jsonResult;
    }

5. 限流测试

说明:项目启动后,开启多条线程发送http请求同时访问添加@RateLimite注解的方法。使用CountdownLatch计数器模拟多线程并发:调用await()方法阻塞当前线程,当计数完成后,唤醒所有线程并发执行。

    public static void main(String[] args) throws InterruptedException {
        ExecutorService exec = Executors.newCachedThreadPool();
        RestTemplate restTemplate = new RestTemplate();
        CountDownLatch countDownLatch = new CountDownLatch(1);
        for (int i = 0; i < 15; i ++){
            Runnable runnable = () -> {
                try {
                    countDownLatch.await();
                    Object obj = restTemplate.getForObject("http://localhost:8762/dota/getHero/coco", Object.class);
                    System.out.println(Thread.currentThread().getName() + ": " + JSONObject.toJSONString(obj));
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            };
            exec.submit(runnable);
        }
        countDownLatch.countDown();
        exec.shutdown();
    }

测试结果:
在这里插入图片描述

要通过自定义注解AOP实现Spring Security配置指定接口不需要Token才能访问,可以按照以下步骤进行操作: 1. 创建一个自定义注解,例如`@NoTokenRequired`,用于标识不需要Token的接口。 ```java @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface NoTokenRequired { } ``` 2. 创建一个切面类,用于拦截带有`@NoTokenRequired`注解的方法,并跳过Spring Security的Token验证。 ```java @Aspect @Component public class TokenValidationAspect { @Before("@annotation(com.example.NoTokenRequired)") public void skipTokenValidation(JoinPoint joinPoint) { // 跳过Spring Security的Token验证逻辑 SecurityContextHolder.getContext().setAuthentication(null); } } ``` 3. 配置Spring Security,将AOP切面类添加到Spring Security的配置中。 ```java @Configuration @EnableWebSecurity public class SecurityConfig extends WebSecurityConfigurerAdapter { @Autowired private TokenValidationAspect tokenValidationAspect; @Override protected void configure(HttpSecurity http) throws Exception { http .authorizeRequests() // 配置需要Token验证的接口 .anyRequest().authenticated() .and() .csrf().disable(); // 将AOP切面类添加到Spring Security的配置中 http.addFilterBefore(tokenValidationAspect, UsernamePasswordAuthenticationFilter.class); } } ``` 4. 在需要不需要Token验证的接口上,添加`@NoTokenRequired`注解。 ```java @RestController public class ExampleController { @NoTokenRequired @GetMapping("/example") public String example() { return "This API does not require Token"; } } ``` 这样配置之后,带有`@NoTokenRequired`注解的接口将不会进行Spring Security的Token验证,即可在没有Token的情况下访问该接口。其他接口仍然需要进行Token验证。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值