Aop实现注解限流和Redis缓存

编程

限流注解实现

业务系统中某些接口需要进行限流的时候在spring家族中可以采用RateLimiter进行接口限流,减轻服务器的压力。实现思路如下:

RateLimit 注解

/**

* @description: 限流注解

* @author: lilang

* @version:

* @modified By:1170370113@qq.com

*/

@Retention(RetentionPolicy.RUNTIME)

@Target(ElementType.METHOD)

@Documented

public @interface RateLimit {

double limitNum() default 20; //默认每秒放入桶中的token

//获取令牌的等待时间

int timeOut() default 0;

//等待时间单位

TimeUnit timeUnit() default TimeUnit.MILLISECONDS;

}

注解RateLimit AOP实现类

@Component

@Aspect

public class RateLimitAspect {

private Logger log = LoggerFactory.getLogger(this.getClass());

//用来存放不同接口的RateLimiter(key为接口名称,value为RateLimiter)

private ConcurrentHashMap<String, RateLimiter> map = new ConcurrentHashMap<>();

private static ObjectMapper objectMapper = new ObjectMapper();

private RateLimiter rateLimiter;

@Pointcut("@annotation(com.itstyle.mail.common.aop.RateLimit)")

public void serviceLimit() {

}

@ResponseBody

@Around("serviceLimit()")

public Object around(ProceedingJoinPoint joinPoint) throws NoSuchMethodException {

Object obj = null;

//获取拦截的方法名

Signature sig = joinPoint.getSignature();

//获取拦截的方法名

MethodSignature msig = (MethodSignature) sig;

//返回被织入增加处理目标对象

Object target = joinPoint.getTarget();

//为了获取注解信息

Method currentMethod = target.getClass().getMethod(msig.getName(), msig.getParameterTypes());

//获取注解信息

RateLimit annotation = currentMethod.getAnnotation(RateLimit.class);

double limitNum = annotation.limitNum(); //获取注解每秒加入桶中的token

TimeUnit timeUnit = annotation.timeUnit();//获取时间单位

String functionName = msig.getName(); // 注解所在方法名区分不同的限流策略

int timeOut = annotation.timeOut();

//获取rateLimiter

if(map.containsKey(functionName)){

rateLimiter = map.get(functionName);

}else {

map.put(functionName, RateLimiter.create(limitNum));

rateLimiter = map.get(functionName);

}

try {

if (rateLimiter.tryAcquire(timeOut,timeUnit)) {

//执行方法

obj = joinPoint.proceed();

} else {

return Result.error("服务器繁忙,请稍后再试....");

}

} catch (Throwable throwable) {

throwable.printStackTrace();

}

return obj;

}

}

使用方式 比如在controller层进行控制

@RateLimit(limitNum = 10,timeOut = 1,timeUnit = TimeUnit.SECONDS)

@GetMapping("limit/go")

public void queryFromMysql(){

//数据库操作逻辑

}

Redis缓存实现

缓存注解定义:

@Retention(RetentionPolicy.RUNTIME)

@Target(ElementType.METHOD)

@Documented

public @interface RedisCache {

String prefix() default "";

int expire() default 1;

TimeUnit TIME_UNIT() default TimeUnit.DAYS;

//缓存反序列化获取的对象

Class clazz() default Object.class;

//序列化后的对象是否是jsonarry 比如 List<Object>

boolean isArray() default false;

}

缓存切面类实现:

@Component

@Aspect

public class RedisCacheAspect {

private static Logger logger = LoggerFactory.getLogger(RedisCacheAspect.class);

@Autowired

private RedisTemplate redisTemplate ;

/**

* 分隔符 生成key 格式为 类全类名|方法名|参数所属类全类名

**/

private static final String DELIMITER = "-";

/**

* Service层切点 使用到了我们定义的 RedisCacheAspect 作为切点表达式。

* 而且我们可以看出此表达式基于 annotation。

* 并且用于内建属性为查询的方法之上

*/

@Pointcut("@annotation(com.itstyle.mail.common.aop.RedisCache)")

public void redisCacheAspect() {

}

/**

* Around 手动控制调用核心业务逻辑,以及调用前和调用后的处理,

* <p>

* 注意:当核心业务抛异常后,立即退出,转向AfterAdvice 执行完AfterAdvice,再转到ThrowingAdvice

*

*/

@Around(value = "redisCacheAspect()")

public Object aroundAdvice(ProceedingJoinPoint joinPoint) throws Throwable {

// 得到类名、方法名和参数

String clazzName = joinPoint.getTarget().getClass().getName();

String methodName = joinPoint.getSignature().getName();

Object[] args = joinPoint.getArgs();

// 根据类名、方法名和参数生成Key

logger.info("key参数: " + clazzName + "." + methodName);

String key = getKey(clazzName, methodName, args);

if (logger.isInfoEnabled()) {

logger.info("生成key: " + key);

}

// 得到被代理的方法

Method method = ((MethodSignature) joinPoint.getSignature()).getMethod();

//redis 前缀

String prefix = method.getAnnotation(RedisCache.class).prefix();

int expire = method.getAnnotation(RedisCache.class).expire();

TimeUnit timeUnit = method.getAnnotation(RedisCache.class).TIME_UNIT();

Class objectType = method.getAnnotation(RedisCache.class).clazz();

boolean isArray=method.getAnnotation(RedisCache.class).isArray();

// 检查Redis中是否有缓存

String value = (String) redisTemplate.opsForValue().get(prefix);

// 得到被代理方法的返回值类型

// Class returnType = ((MethodSignature) joinPoint.getSignature()).getReturnType();

// result是方法的最终返回结果

Object result = null;

try {

if (null == value) {

logger.info("缓存未命中");

// 调用数据库查询方法

result = joinPoint.proceed(args);

// 结果放入缓存

redisTemplate.opsForValue().set(prefix, JSON.toJSONString(result),expire,timeUnit);

} else {

/**

* 可以直接针对mapper进行缓存,如果mapper查询返回的List<DemoObjec> 需要isArray 为true 否则转换异常

*/

if (isArray){

return JSON.parseArray(value,objectType);

}else {

return JSON.parseObject(value,objectType);

}

}

} catch (Throwable e) {

logger.error("程序异常",e.getMessage());

throw e;

}

return result;

}

/**

* * 根据类名、方法名和参数生成Key

* * @param clazzName

* * @param methodName

* * @param args

* * @return key格式:全类名|方法名|参数类型

*

*/

private String getKey(String clazzName, String methodName, Object[] args) {

StringBuilder key = new StringBuilder(clazzName);

key.append(DELIMITER);

key.append(methodName);

key.append(DELIMITER);

key.append(Arrays.stream(args).map(x->x.toString()).collect(Collectors.joining(DELIMITER)));

return key.toString();

}

}

使用方式 :

以下是放在mapper层加入的注解,实际项目中可以根据自己的需求注解加在任意位置。

@Mapper

public interface DemoMapper {

@RedisCache(expire = 1,clazz = DemoObject.class,isArray = true)

public List<DemoObject> queryFromMysql();

@RedisCache(expire = 1,clazz = DemoObject.class)

public DemoObject queryFromMysql();

}

使用如上方式,我们便可以在实际项目中无侵入的实现业务限流和业务缓存。

以上是 Aop实现注解限流和Redis缓存 的全部内容, 来源链接: utcz.com/z/511538.html

回到顶部