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. 定义切面
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
--获取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();
}
}
}