最近整理下一些常用技术的自定义注解实现方式,具体关于自定义注解的概念可以参考如下链接
1. 频控注解@AccessFrequencyAspect
1.1 注解介绍
该注解主要通过aop切面配合redis实现,接口/方法访问的频率限制,可以根据IP和用户id等信息进行限制。
1.2 注解
package com.middlewares.common.redis.annotation;
import com.middlewares.common.core.enums.LimitType;
import java.lang.annotation.*;
import java.util.concurrent.TimeUnit;
/**
* 接口访问频次注解
*
* @author shawn
* @date 2024年 03月 26日 9:02 09:02:40
*/
@Target({ ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface AccessFrequency {
String prefix() default "middlewares:middlewares-common:middlewares-common-redis:";
/**
* 限制模式 用户名限制 IP限制 用户名IP组合限制
* @return {@link LimitType}
*/
LimitType LimitType() default LimitType.USER;
/**
* 访问频率 频次/时间
* @return {@link String}
*/
String frequent() default "60/1";
/**
* 时间单位
* @return {@link TimeUnit}
*/
TimeUnit timeUnit() default TimeUnit.MINUTES;
}
1.3 注解切面
package com.middlewares.common.redis.aspect;
import com.middlewares.common.redis.constant.ExecuteOrder;
import com.middlewares.common.core.constant.SecurityConstants;
import com.middlewares.common.core.exception.AccessFrequencyException;
import com.middlewares.common.core.utils.ServletUtils;
import com.middlewares.common.core.utils.ip.IpUtils;
import com.middlewares.common.redis.annotation.AccessFrequency;
import com.middlewares.common.redis.service.RedisService;
import org.aspectj.lang.ProceedingJoinPoint;
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.core.Ordered;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.util.Objects;
/**
* @author shawn
* @date 2024年 03月 26日 9:59 09:59:24
*/
@Aspect
@Component
public class AccessFrequencyAspect implements Ordered{
private final Logger log = LoggerFactory.getLogger(AccessFrequencyAspect.class);
@Resource
private RedisService redisService;
public AccessFrequencyAspect() {
}
/**
* within:within关键字用来匹配指定包或类下的所有方法。例如,within(com.middlewares.service.*)表示匹配com.middlewares.service包下的所有方法。
* @target注解用于匹配目标对象的类是否具有指定的注解。例如,@target(com.middlewares.annotation.AccessFrequency)表示匹配目标对象的类是否标注了@AccessFrequency注解。
* @annotation注解用于匹配目标方法是否具有指定的注解。例如,@annotation(com.middlewares.annotation.AccessFrequency)表示匹配目标对象的方法是否标注了@AccessFrequency注解。
* execution:execution关键字用于匹配方法的执行。可以指定方法的访问修饰符、返回类型、方法名、参数等信息。例如,execution(* com.middlewares.service..(..))表示匹配com.middlewares.service包下的所有方法。
* bean:bean关键字用于匹配Spring Bean的名称。例如,bean(myService)表示匹配名称为myService的Spring Bean。
* */
@Pointcut("@within(com.middlewares.common.redis.annotation.AccessFrequency) || @annotation(com.middlewares.common.redis.annotation.AccessFrequency)")
public void pointcut()
{
}
@Around("pointcut()")
public Object AccessFrequency(ProceedingJoinPoint point) throws Throwable {
AccessFrequency accessFrequency = null;
if (point.getTarget().getClass().isAnnotationPresent(AccessFrequency.class)) {
/* 首先查看类上有无注解*/
accessFrequency = point.getTarget().getClass().getAnnotation(AccessFrequency.class);
} else {
/* 获取方法上注解 */
MethodSignature signature = (MethodSignature) point.getSignature();
accessFrequency = signature.getMethod().getAnnotation(AccessFrequency.class);
}
if (Objects.isNull(accessFrequency)){
return point.proceed();
}
/* 0. 校验获取频率信息*/
String frequent = accessFrequency.frequent();
if (!frequent.contains("/")) {
log.error("@AccessFrequency frequent format exception , lose '/' to split string , get " + frequent);
return point.proceed();
}
String[] split = frequent.split("/");
if (split.length != 2 || !isNumericArray(split)) {
log.error("@AccessFrequency frequent format exception , expect a number split by '/', but got " + frequent);
return point.proceed();
}
int frequency = Integer.parseInt(split[0]);
int time = Integer.parseInt(split[1]);
String redisKey = "";
/* 1. 获取频率限制模式*/
switch (accessFrequency.LimitType()) {
case IP:
/*1.1 IP限制逻辑*/
{
redisKey = accessFrequency.prefix() + IpUtils.getIpAddr();
}
break;
case USER:
/*1.2 用户限制逻辑*/
String username = Objects.requireNonNull(ServletUtils.getRequest()).getHeader(SecurityConstants.DETAILS_USERNAME);
redisKey = accessFrequency.prefix() + username;
break;
case USER_IP:
// 用户IP限制逻辑
redisKey = accessFrequency.prefix() + IpUtils.getIpAddr() + "-" + Objects.requireNonNull(ServletUtils.getRequest()).getHeader(SecurityConstants.DETAILS_USERNAME);
break;
default:
// 默认逻辑
log.error("unKnow access limit type " + accessFrequency.LimitType());
return point.proceed();
}
/* 2. 获取请求次数*/
Long count = redisService.increment(redisKey);
assert count != null : "Count should not be null";
if (count == 1) {
/* 失效时间内,首次访问,设定失效时间*/
redisService.expire(redisKey, time, accessFrequency.timeUnit());
}
/* 3. 比较访问次数,超次拒绝*/
if (count > frequency) {
throw new AccessFrequencyException("您的访问频次过高,已阻止本次访问!");
}
return point.proceed();
}
private static boolean isNumericArray(String[] array) {
for (String str : array) {
if (!isNumeric(str)) {
return false;
}
}
return true;
}
private static boolean isNumeric(String str) {
if (str == null || str.isEmpty()) {
return false;
}
for (char c : str.toCharArray()) {
if (!Character.isDigit(c)) {
return false;
}
}
return true;
}
@Override
public int getOrder() {
return ExecuteOrder.getOrderByClass(this.getClass());
}
}
2. 分布式id注解@RedisId
2.1 注解介绍
该注解主要通过aop切面配合redis实现在多服务实例下,系统可以共享一套id。
2.2 注解
package com.middlewares.common.redis.annotation;
import java.lang.annotation.*;
/**
* redis分布式id注解
* @author shawn
* @date 2024年 04月 09日 9:53 09:53:16
*/
@Target({ ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RedisId {
/**
* id名称
* @return {@link String}
*/
String key();
/**
* id初始值
* @return long
*/
long initValue() default 1L;
}
2.3 注解切面
package com.middlewares.common.redis.aspect;
import com.middlewares.common.redis.annotation.RedisId;
import com.middlewares.common.redis.constant.ExecuteOrder;
import com.middlewares.common.redis.service.RedisService;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.Ordered;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
/**
* @author shawn
* @date 2024年 04月 09日 9:58 09:58:07
*/
@Aspect
@Component
public class RedisIdAspect implements Ordered {
private final Logger log = LoggerFactory.getLogger(RedisIdAspect.class);
@Resource
private RedisService redisService;
public RedisIdAspect() {
}
// 匹配Controller层的所有方法
@Before("execution(* com.middlewares.*.controller.*.*(..)) && @annotation(org.springframework.web.bind.annotation.PostMapping)")
public void checkRedisIdAnnotation(JoinPoint joinPoint) throws IllegalArgumentException {
// 遍历所有参数
for (Object arg : joinPoint.getArgs()) {
// 如果参数为null,跳过
if (arg == null) {
continue;
}
fillIdValue(arg);
}
}
/**
* 装填id属性
*
* @param arg
*/
private void fillIdValue(Object arg) {
// 获取参数的所有字段
Field[] fields = arg.getClass().getDeclaredFields();
for (Field field : fields) {
// 设置私有属性可访问
field.setAccessible(true);
// 检查字段是否被@RedisId标注
try {
if (field.isAnnotationPresent(RedisId.class)) {
// 执行你的逻辑,例如打印日志、验证字段值等
RedisId redisId = field.getAnnotation(RedisId.class);
Long id = redisService.getId(redisId.key());
if (field.getType().equals(Long.class) || field.getType().equals(long.class)) {
/* 为空则填充*/
Object origin = field.get(arg);
if (Objects.isNull(origin)) {
field.set(arg, id);
}
} else {
// 如果字段类型不是Long或long,打印错误或抛出异常
log.error("Field " + field.getName() + " is not of type Long or long.");
}
} else if (isClass(field.getType())) {
// 如果字段不是基本类型或字符串,尝试递归填充
Object child = field.get(arg);
if (child != null) {
fillIdValue(child); // 递归调用
} else {
// 如果字段是null,尝试创建实例并填充
Class<?> fieldType = field.getType();
Object newInstance = fieldType.newInstance();
field.set(arg, newInstance);
fillIdValue(newInstance); // 递归调用
}
}
} catch (IllegalAccessException | InstantiationException e) {
throw new RuntimeException(e);
}
}
}
/**
* 判断属性是否为一般类
*
* @param clazz
* @return boolean
*/
private boolean isClass(Class<?> clazz) {
/*0.提出基础类型,枚举类*/
if (clazz.isPrimitive() || clazz.isEnum()) {
return false;
}
/*1.判断包装类及特殊类*/
List<Class<?>> classList = Arrays.asList(
Integer.class, Byte.class, Short.class, Boolean.class, Long.class, Float.class, Double.class,
String.class, Enum.class, Class.class, Constructor.class);
for (Class<?> aClass : classList) {
if (aClass.equals(clazz)) {
return false;
}
}
log.info("未过滤类:{}", clazz.getName());
return true;
}
@Override
public int getOrder() {
return ExecuteOrder.getOrderByClass(this.getClass());
}
}
3. 分布式锁注解@RedisLock
3.1 注解介绍
该注解主要通过aop切面配合redis实现在多服务实例并发下,可以实现共享资源的安全访问。该分布式实现两种获取锁策略①获取不到直接放弃执行 ②获取不到在指定获取锁超时时间内重复获取,同时支持锁的自动续约,保证在业务执行完毕前,不丢失锁,从而保证资源的安全访问。
3.2 注解
package com.middlewares.common.redis.annotation;
import com.middlewares.common.core.utils.DateUtils;
import com.middlewares.common.redis.constant.RedisConstant;
import java.lang.annotation.*;
/**
* 方法级分布式锁
* @author shawn
* @date 2024年 04月 10日 20:02 20:02:41
*/
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RedisLock {
/**
* 锁名称
* @return {@link String}
*/
String lockName() default "";
/**
* 锁失效时间,默认12秒
* @return long
*/
long expireTime() default 12000L;
/**
* 默认锁自动续约
* @return boolean
*/
boolean whetherRenewal() default true;
/**
* 默认拒绝策略
*
* @return {@link RedisConstant.ExecutionStrategy}
*/
RedisConstant.ExecutionStrategy strategy() default RedisConstant.ExecutionStrategy.REFUSE;
/**
* 重试超时时间,默认20秒
* @return long
*/
long retryTimeOut() default 20000L;
}
3.3 注解切面
package com.middlewares.common.redis.aspect;
import com.middlewares.common.core.exception.RedisLockException;
import com.middlewares.common.core.utils.StringUtils;
import com.middlewares.common.redis.annotation.RedisLock;
import com.middlewares.common.redis.constant.ExecuteOrder;
import com.middlewares.common.redis.constant.RedisConstant;
import com.middlewares.common.redis.domain.LockRenewalTask;
import com.middlewares.common.redis.service.RedisService;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.Ordered;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.util.Objects;
import java.util.UUID;
/**
* redis分布式锁注解
* @author shawn
* @date 2024年 04月 10日 20:30 20:30:49
*/
@Aspect
@Component
public class RedisLockAspect implements Ordered {
private final static Logger log = LoggerFactory.getLogger(RedisLockAspect.class);
@Resource
private RedisService redisService;
@Resource
public RedisTemplate<String,String> redisTemplate;
@Around("@annotation(redisLock)")
public Object RedisLock(ProceedingJoinPoint pjp, RedisLock redisLock){
/*1. 获取注解信息,校验参数*/
String lockName = redisLock.lockName();
MethodSignature signature = (MethodSignature) pjp.getSignature();
String methodName = signature.getMethod().getName();
if (StringUtils.isEmpty(lockName)){
/* 默认锁名为类全路径+方法名*/
lockName = pjp.getTarget().getClass().getName()+"."+methodName;
}
Object proceed = null;
com.middlewares.common.redis.domain.RedisLock lock = null;
String uuid = UUID.randomUUID().toString();
/*2. 判断策略*/
if (RedisConstant.ExecutionStrategy.REFUSE.equals(redisLock.strategy())){
/*2.1 拒绝策略*/
if (!redisService.lock(lockName,uuid,redisLock.expireTime())) {
throw new RedisLockException("当前系统正忙,请稍后再试!");
}
}else {
/*2.2 重试策略*/
lock = redisService.getLock(lockName, redisLock.expireTime(), redisLock.retryTimeOut());
if (Objects.isNull(lock)){
throw new RedisLockException("当前系统正忙,请稍后再试!");
}
}
if (Objects.nonNull(lock)){
uuid = lock.getValue();
}
log.info("成功获取分布式锁:【{}】",lockName);
/*3.锁续期*/
LockRenewalTask lockRenewalTask = null;
if (redisLock.whetherRenewal()){
lockRenewalTask = new LockRenewalTask(redisTemplate, redisService.getKey(lockName), uuid, redisLock.expireTime());
Thread thread = new Thread(lockRenewalTask);
thread.start();
}
/*4. 获取锁后执行业务*/
try {
proceed = pjp.proceed();
} catch (Throwable e) {
releaseResources(lockName, lock, lockRenewalTask);
log.error(pjp.getClass().getName()+"-"+methodName+"发生未知异常。");
throw new RedisLockException("当前系统正忙,请稍后再试!");
} finally {
/*5.释放资源*/
releaseResources(lockName, lock, lockRenewalTask);
}
/*6. 返回结果*/
return proceed;
}
private void releaseResources(String lockName, com.middlewares.common.redis.domain.RedisLock lock, LockRenewalTask lockRenewalTask) {
/*1. 业务执行完毕,释放锁*/
if (Objects.isNull(lock)){
redisService.unlock(lockName);
}else {
redisService.releaseLock(lock);
}
log.info("成功释放分布式锁:【{}】",lockName);
/*2. 关闭锁续期*/
if (Objects.nonNull(lockRenewalTask)){
lockRenewalTask.stop();
}
}
@Override
public int getOrder() {
return ExecuteOrder.getOrderByClass(this.getClass());
}
}
3.4 jmeter测试结果,基本符合预期