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 重新访问
不再限流,接口可以继续正常访问了。