自定义注解和使用

摘要:
1.使用@interface键定义注释(RateLimiter.java),如下所示:packagecom。vx。服务hi。注释;importjava.lang.annotation.*;/***@Authorwangbs*@version1.0*@date2019/12/161:25*@classNameRateLimiter*@desc流量限制注释*//annotation-scope//El

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();
}
}
}

免责声明:文章转载自《自定义注解和使用》仅用于学习参考。如对内容有疑问,请及时联系本站处理。

上篇linux学习9 运维基本功-Linux常用基础命令实战应用CentOS中Tomcat关闭后进程还在的解决方案下篇

宿迁高防,2C2G15M,22元/月;香港BGP,2C5G5M,25元/月 雨云优惠码:MjYwNzM=

相关文章

【Redis】Redis5.0的一些新特性

redis5.0总共增加了12项新特性,如下:    1.新增加的Stream(流)数据类型,这样redis就有了6大数据类型,另外五种是String(字符串),Hash(哈希),List(列表),Set(集合)及Zset(sorted set有序集合)。它弥补了其它5种数据类型不能实现的功能,比如List数据类型只能先进先出,或者后进先出,不能从中间去数...

如何运用PHP+REDIS解决负载均衡后的session共享问题

一、为什么要使用Session共享? 稍大一些的网站,通常都会有好几个服务器,每个服务器运行着不同功能的模块,使用不同的二级域名,而一个整体性强的网站,用户系统是统一的,即一套用户名、密码在整个网站的各个模块中都是可以登录使用的。各个服务器共享用户数据是比较容易实现的,只需要在后端放个数据库服务器,各个服务器通过统一接口对用户数据进行访问即可。但还存在一个...

redis sentinel哨兵模式集群搭建教程

1.环境说明 我们将使用192.168.220.128、192.168.220.129两台机器搭建sentinel交叉主从为例 当前我们已在192.168.220.128上按redis安装教程安装了redis,192.168.220.129上没有安装 2. 配置128上的slave cd /usr/myapp/redis-2.8.17/conf #进入...

spring-session之二:简单配置

官方示例:https://docs.spring.io/spring-session/docs/current/reference/html5/#samples 配置Spring Session 在Web项目中配置Spring Session分为四步: 搭建用于Spring Session的数据存储 将Spring Session的jar文件添加到...

spring+redis 集群下的操作

文章就是记录一下工作当中的用到的点,与测试方法以备用,会不断更新。 配置文件spring-redis.xml: <?xml version="1.0" encoding="UTF-8"?><beans xmlns="http://www.springframework.org/schema/beans" xmlns:xs...

Docker Compose

Docker Compose简介 Compose 项目是 Docker 官方的开源项目,负责实现对 Docker 容器集群的快速编排。从功能上看,跟 OpenStack 中的 Heat 十分类似。 其代码目前在 https://github.com/docker/compose 上开源。 Compose 定位是 「定义和运行多个 Docker 容器的应用(...