自定义注解和使用

1、使用@interface关键定义注解(RateLimiter.java),如下:

复制代码
package com.vx.servicehi.annotation;

import java.lang.annotation.*;

/**
 * @author wangbs
 * @version 1.0
 * @date 2019/12/16 1:25
 * @className RateLimiter
 * @desc 限流注解
 */

//注解作用域
//        ElementType.TYPE:允许被修饰的注解作用在类、接口和枚举上
//        ElementType.FIELD:允许作用在属性字段上
//        ElementType.METHOD:允许作用在方法上
//        ElementType.PARAMETER:允许作用在方法参数上
//        ElementType.CONSTRUCTOR:允许作用在构造器上
//        ElementType.LOCAL_VARIABLE:允许作用在本地局部变量上
//        ElementType.ANNOTATION_TYPE:允许作用在注解上
//        ElementType.PACKAGE:允许作用在包上
//
//        注解的生命周期
//        RetentionPolicy.SOURCE:当前注解编译期可见,不会写入 class 文件
//    RetentionPolicy.CLASS:类加载阶段丢弃,会写入 class 文件
//    RetentionPolicy.RUNTIME:永久保存,可以反射获取

// 注解的作用域
@Target(ElementType.METHOD)
// 注解的生命周期
@Retention(RetentionPolicy.RUNTIME)
// 允许子类继承
@Inherited
// 生成javadoc的时候生成注解的信息
@Documented
public @interface RateLimiter {

    /**
     * 限流key
     * @return
     */
    String key() default "rate:limiter";
    /**
     * 单位时间限制通过请求数
     * @return
     */
    long limit() default 10;

    /**
     * 过期时间,单位秒
     * @return
     */
    long expire() default 1;

    /**
     * 限流提示语
     * @return
     */
    String message() default "false";
}
复制代码

2、在普通类上使用注解,使用方法

复制代码

package com.vx.servicehi.controller;

import com.vx.servicehi.annotation.RateLimiter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("business")
public class BusinessController {
private static final Logger LOGGER = LoggerFactory.getLogger(BusinessController.class);

private static final String MESSAGE = "{\"code\":\"400\",\"msg\":\"FAIL\",\"desc\":\"触发限流\"}";


@Value("${server.port}")
String port;

/**
* 使用自定义注解 限流
* @param name
* @return
*/
@RequestMapping("/hi")
@RateLimiter(key = "business/hi", limit = 5, expire = 10, message = MESSAGE)
public String home(@RequestParam(value = "name", defaultValue = "forezp") String name) {
return "hi " + name + " ,i am from port:" + port;
}
}
 
复制代码

3. 定义切面

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
package com.vx.servicehi.handler;
 
import com.vx.servicehi.annotation.RateLimiter;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
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.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.io.ClassPathResource;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.scripting.support.ResourceScriptSource;
import org.springframework.stereotype.Component;
 
import javax.annotation.PostConstruct;
import java.util.ArrayList;
import java.util.List;
 
/**
 * @author wangbs
 * @version 1.0
 * @date 2019/12/16 1:17
 * @className RateLimterHandler
 * @desc 限流处理器
 */
@Aspect
@Component
public class RateLimterHandler {
 
    private static final Logger LOGGER = LoggerFactory.getLogger(RateLimterHandler.class);
 
    @Autowired
    RedisTemplate redisTemplate;
 
    private DefaultRedisScript<Long> getRedisScript;
 
    @PostConstruct
    public void init() {
        getRedisScript = new DefaultRedisScript<>();
        getRedisScript.setResultType(Long.class);
        getRedisScript.setScriptSource(new ResourceScriptSource(new ClassPathResource("rateLimter.lua")));
        LOGGER.info("RateLimterHandler[分布式限流处理器]脚本加载完成");
    }
 
    @Pointcut("@annotation(com.vx.servicehi.annotation.RateLimiter)")
    public void rateLimiter() {}
 
    @Around("@annotation(rateLimiter)")
    public Object around(ProceedingJoinPoint proceedingJoinPoint, RateLimiter rateLimiter) throws Throwable {
        if (LOGGER.isDebugEnabled()) {
            LOGGER.debug("RateLimterHandler[分布式限流处理器]开始执行限流操作");
        }
        Signature signature = proceedingJoinPoint.getSignature();
        if (!(signature instanceof MethodSignature)) {
            throw new IllegalArgumentException("the Annotation @RateLimter must used on method!");
        }
        /**
         * 获取注解参数
         */
        // 限流模块key
        String limitKey = rateLimiter.key();
        if(StringUtils.isBlank(limitKey)){
            throw new NullPointerException();
        }
        // 限流阈值
        long limitTimes = rateLimiter.limit();
        // 限流超时时间
        long expireTime = rateLimiter.expire();
        if (LOGGER.isDebugEnabled()) {
            LOGGER.debug("RateLimterHandler[分布式限流处理器]参数值为-limitTimes={},limitTimeout={}", limitTimes, expireTime);
        }
        // 限流提示语
        String message = rateLimiter.message();
        if (StringUtils.isBlank(message)) {
            message = "false";
        }
        /**
         * 执行Lua脚本
         */
        List<String> keyList = new ArrayList();
        // 设置key值为注解中的值
        keyList.add(limitKey);
        /**
         * 调用脚本并执行
         */
        Long result = (Long) redisTemplate.execute(getRedisScript, keyList, expireTime, limitTimes);
        if (result == 0) {
            String msg = "由于超过单位时间=" + expireTime + "-允许的请求次数=" + limitTimes + "[触发限流]";
            LOGGER.debug(msg);
            return message;
        }
        if (LOGGER.isDebugEnabled()) {
            LOGGER.debug("RateLimterHandler[分布式限流处理器]限流执行结果-result={},请求[正常]响应", result);
        }
        return proceedingJoinPoint.proceed();
    }
}

  

4. 在resource 下定义文件 rateLimter.lua

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
--获取KEY
local key1 = KEYS[1]
--给指定的key 值增加一,如果 key 不存在,那么 key 的值会先被初始化为 0 ,然后再执行 INCR 操作
local val = redis.call('incr', key1)
--以秒为单位返回 key 的剩余过期时间
local ttl = redis.call('ttl', key1)
 
--获取ARGV内的参数并打印
local expire = ARGV[1]
local times = ARGV[2]
 
redis.log(redis.LOG_DEBUG,tostring(times))
redis.log(redis.LOG_DEBUG,tostring(expire))
 
redis.log(redis.LOG_NOTICE, "incr "..key1.." "..val);
if val == 1 then
    redis.call('expire', key1, tonumber(expire))
else
    if ttl == -1 then
    --expire当key不存在或者不能为key设置生存时间时返回  0
        redis.call('expire', key1, tonumber(expire))
    end
end
 
if val > tonumber(times) then
    return 0
end
 
return 1

  

 

5、解析注解,通过反射获取类,函数或成员上的运行时注解信息,从而实现动态控制程序运行的逻辑

复制代码

package com.vx.servicehi.annotation;

import java.lang.annotation.Annotation;
import java.lang.reflect.Method;

/**
* 解析注解
* 通过反射获取类,函数或成员上的运行时注解信息,从而实现动态控制程序运行的逻辑
*
* 对于一个类或者接口来说,Class 类中提供了以下一些方法用于反射注解。
getAnnotation:返回指定的注解
isAnnotationPresent:判定当前元素是否被指定注解修饰
getAnnotations:返回所有的注解
getDeclaredAnnotation:返回本元素的指定注解
getDeclaredAnnotations:返回本元素的所有注解,不包含父类继承而来的
*
* @author wangbs
* @date 2019-12-21 22:52:42
* 主要是对自定义注解 RateLimiter 的获取 测试
*
*/
public class ParseDecription {

public static void main(String[] args) {
// TODO Auto-generated method stub
// 1、使用类加载器加载类
try {
Class c = Class.forName("com.vx.servicehi.controller.BusinessController");
System.out.println(c);

// 2、找到类上面的注解
boolean isExist = c.isAnnotationPresent(RateLimiter.class);

if(isExist) {
// 3、拿到注解实例
RateLimiter d = (RateLimiter) c.getAnnotation(RateLimiter.class);
System.out.println("========parse class annotation=========");
System.out.println("desc = " + d.key());
System.out.println("author = " + d.message());
System.out.println("age = " + d.limit());
}

// 4、找到方法上的注解
Method[] ms = c.getMethods();
for (Method m : ms) {
boolean isMExist = m.isAnnotationPresent(RateLimiter.class);
if(isMExist) {
RateLimiter d = m.getAnnotation(RateLimiter.class);
System.out.println("========parse method annotation=========");
System.out.println("desc = " + d.key());
System.out.println("author = " + d.message());
System.out.println("age = " + d.limit());
}
}

// 另外一种解析方法
for (Method m : ms) {
Annotation[] annotations = m.getAnnotations();
for (Annotation annotation : annotations) {
if(annotation instanceof RateLimiter) {
System.out.println("========parse method annotation other way=========");
RateLimiter d = (RateLimiter) annotation;
System.out.println("desc = " + d.key());
System.out.println("author = " + d.message());
System.out.println("age = " + d.limit());
}
}
}

} catch (ClassNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
复制代码
posted @   好记性不如烂笔头=>  阅读(814)  评论(0编辑  收藏  举报
编辑推荐:
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
阅读排行:
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 【译】Visual Studio 中新的强大生产力特性
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 【设计模式】告别冗长if-else语句:使用策略模式优化代码结构
点击右上角即可分享
微信分享提示