自定义注解和使用

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();
}
}
}
posted @ 2019-12-20 10:38  好记性不如烂笔头=>  阅读(823)  评论(0)    收藏  举报