1. 描述
部分场景需要动态修改注解的值。例如,我们使用自定义注解控制接口流量,如果需要动态修改流量值,可以使用反射的方法实现。
2. 步骤
- 获取注解
- 从注解中获取memberValues属性(map)
- 使用put方法更新对象的值
3. 代码实现
该部分代码主要是基于流量控制的功能demo,使用反射动态修改@RateLimit注解达到动态修改流量的目的。此章节节选了反射修改值的代码予以分享。
3.1 主工具类
import com.hz.common.aop.limit.RateLimit;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
import java.util.Map;
/**
* @author pp_lan
* @date 2024/2/3
*/
public class ReflectUtils {
private ReflectUtils() {
}
public static void dynamicLocation(RateLimit rateLimit, String fileName, Object fieldValue) throws IllegalAccessException, NoSuchFieldException {
if (rateLimit == null) {
return;
}
InvocationHandler invocationHandler = Proxy.getInvocationHandler(rateLimit);
Class<? extends InvocationHandler> aClass = invocationHandler.getClass();
Field memberValues = aClass.getDeclaredField("memberValues");
memberValues.setAccessible(true);
Map<String, Object> menberValueMap = (Map<String, Object>) memberValues.get(invocationHandler);
menberValueMap.put(fileName, fieldValue);
}
}
3.2 依赖
3.2.1 RateLimit
import java.lang.annotation.*;
/**
* @author pp_lan
*/
@Documented
@Inherited
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface RateLimit {
/**
* 名称
*
* @return
*/
String name();
/**
* 每分钟限流数量
*
* @return
*/
int limitNum();
}
3.2.2 切面处理
切面中需要动态获取注解
@Component
public class RateLimitAspect {
private static final Logger LOGGER = LoggerFactory.getLogger(RateLimitAspect.class);
private ConcurrentHashMap<String, RateLimiter> limitMap = new ConcurrentHashMap<>();
@Pointcut("@annotation(com.hz.common.aop.limit.RateLimit) && @annotation(rateLimit)")
public void pointCut(RateLimit rateLimit){}
@Around(value = "pointCut(rateLimit)")
public Object around(ProceedingJoinPoint joinPoint, RateLimit rateLimit) throws Throwable {
// 此处动态获取注解值取代初始化的RateLimit值
Object target = joinPoint.getTarget();
MethodSignature sig = (MethodSignature) joinPoint.getSignature();
Method currentMethod = target.getClass().getMethod(sig.getName(), sig.getParameterTypes());
RateLimit newRateLimit = currentMethod.getAnnotation(RateLimit.class);
LOGGER.info("[限流器{}]{}", newRateLimit.name(), newRateLimit.limitNum());
boolean isLimited = limitByKey(newRateLimit.name(), newRateLimit.limitNum());
if (isLimited) {
throw new RateLimitException(String.format("【限流了】%s", newRateLimit.name()));
}
return joinPoint.proceed();
}
/**
* 是否被限流
*
* @param key
* @param limitNum
* @return
*/
private boolean limitByKey(String key, Integer limitNum) {
...
}
}
3.3 使用
5-7行获取注解,并修改注解中属性的值
@RequestMapping("/updateLimitRate")
public Response editLimitRate(Integer methodType, Integer limitNum) throws NoSuchMethodException, NoSuchFieldException, IllegalAccessException {
String methodName = methodType == 1 ? "queryAllUser": "test";
Method method = UserService.class.getMethod(methodName);
RateLimit annotation = method.getAnnotation(RateLimit.class);
ReflectUtils.dynamicLocation(annotation, "limitNum", limitNum);
return Response.ok();
}
4. 效果
4.1 初始化的桶大小
4.2 限流提示
4.3 动态更改流量值
4.4 重新访问
不再限流,接口可以继续正常访问了。