Is it possible to provide a flow-limiting annotation based on the class dimension
- Dominant language
- Java
- Stars
- 23.1k
- Forks
- 8.1k
- PR merge metrics
- No merged PRs in 30d
Description
## Issue Description
Is it possible to provide a flow-limiting annotation based on the class dimension
### Describe what you expected to happen
```java
package com.frxs.trade.user.service.impl;
import com.frxs.trade.user.common.util.sentinel.SentinelLimit;
import com.frxs.trade.user.common.util.sentinel.SentinelLimitParam;
import com.frxs.trade.user.core.service.config.TradeApolloConfig;
import com.frxs.trade.user.core.service.engine.TradeService;
import com.frxs.trade.user.core.service.engine.context.TradeCreateContext;
import com.frxs.trade.user.core.service.engine.context.TradeHideContext;
import com.frxs.trade.user.service.api.domain.enums.TradeActionEnum;
import com.frxs.trade.user.service.api.domain.request.trade.TradeOrderUserRequest;
import com.frxs.trade.user.service.api.domain.request.trade.TradeShieldUserRequest;
import com.frxs.trade.user.service.api.domain.result.OrderCreateResult;
import com.frxs.trade.user.service.api.domain.result.TradeResult;
import com.frxs.trade.user.service.api.facade.TradeFacade;
import com.frxs.trade.user.service.fallback.TradeFacadeFallback;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Slf4j
@Service
@SentinelLimit(TradeFacadeFallback.class)
public class TradeFacadeImpl implements TradeFacade {
@Autowired
private TradeService tradeService;
@Override
@SuppressWarnings("unchecked")
public TradeResult create(TradeOrderUserRequest request) {
return null;
}
@Override
@SuppressWarnings("unchecked")
@SentinelLimitParam(resourceName = "自定义资源名")
public TradeResult shieldOrderUser(TradeShieldUserRequest request) {
return null;
}
}
```
```java
package com.frxs.trade.user.service.fallback;
import com.frxs.trade.user.common.util.errorcode.enums.CreateCodeEnum;
import com.frxs.trade.user.common.util.errorcode.enums.UserHideOrderCodeEnum;
import com.frxs.trade.user.core.service.common.helper.TradeUserResultHelper;
import com.frxs.trade.user.service.api.domain.request.trade.TradeOrderUserRequest;
import com.frxs.trade.user.service.api.domain.request.trade.TradeShieldUserRequest;
import com.frxs.trade.user.service.api.domain.result.OrderCreateResult;
import com.frxs.trade.user.service.api.domain.result.TradeResult;
import com.frxs.trade.user.service.api.facade.TradeFacade;
import lombok.extern.slf4j.Slf4j;
/**
* xxx服务门面 限流类
* @author qiaolin
* @version $Id: TradeFacadeFallback.java,v 0.1 2019年12月27日 10:45 $Exp
*/
@Slf4j
public class TradeFacadeFallback implements TradeFacade {
@Override
public TradeResult create(TradeOrderUserRequest request) {
log.warn("创建订单触发限流 userId:{}", request.getUserId());
return TradeUserResultHelper.fillWithFailure(CreateCodeEnum.SENTINEL_BLOCK_IN_ERROR);
}
@Override
public TradeResult shieldOrderUser(TradeShieldUserRequest request) {
log.warn("删除订单触发限流 orderId:{}, userId:{}", request.getOrderId(), request.getUserId());
return TradeUserResultHelper.fillWithFailure(UserHideOrderCodeEnum.SENTINEL_BLOCK_IN_ERROR);
}
}
```
### Anything else we need to know?
I've implemented it, can I incorporate it into the framework
### The following code
```java
package com.frxs.trade.user.common.util.sentinel;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* 限流注解
* @author qiaolin
* @version $Id: SentinelLimit.java,v 0.1 2019年11月19日 15:03 $Exp
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface SentinelLimit {
/**
* 限流时回掉的类,注意需要和被限流类具有同样签名的方法,推荐两个类实现相同的接口
*/
Class value();
}
```
```java
package com.frxs.trade.user.common.util.sentinel;
import com.alibaba.csp.sentinel.Entry;
import com.alibaba.csp.sentinel.EntryType;
import com.alibaba.csp.sentinel.SphU;
import com.alibaba.csp.sentinel.Tracer;
import com.alibaba.csp.sentinel.slots.block.BlockException;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.ArrayUtils;
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 java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;
/**
* @author qiaolin
* @version $Id: SentinelLimitAspect.java,v 0.1 2019年11月19日 15:06 $Exp
*/
@Slf4j
@Aspect
public class SentinelLimitAspect {
private static final Map, Object> LIMIT_OBJECT_MAP = new HashMap<>();
private static final Map LIMIT_METHOD_MAP = new HashMap<>();
@Pointcut(value = "@within(com.frxs.trade.user.common.util.sentinel.SentinelLimit)")
public void pointcut(){}
@Around("pointcut()")
public Object invoke(ProceedingJoinPoint pjp) throws Throwable {
Class originClass = pjp.getTarget().getClass();
Method originMethod = resolveMethod(pjp, originClass);
SentinelLimit sentinelLimit = originClass.getAnnotation(SentinelLimit.class);
if (sentinelLimit == null) {
// Should not go through here.
throw new IllegalStateException("Wrong state for SentinelLimit annotation");
}
SentinelLimitParam sentinelLimitParam = originMethod.getAnnotation(SentinelLimitParam.class);
String resourceName = getResourceName(originClass, originMethod, sentinelLimitParam);
EntryType entryType = getEntryType(sentinelLimitParam);
Entry entry = null;
try {
entry = SphU.entry(resourceName, entryType, 1, pjp.getArgs());
Object result = pjp.proceed();
return result;
} catch (BlockException ex) {
return handleBlockException(pjp, originMethod, sentinelLimit, ex);
} catch (Throwable ex) {
Tracer.trace(ex);
throw ex;
} finally {
if (entry != null) {
entry.exit();
}
}
}
/**
* 处理被限流的接口,调用我们的限流方法
*/
private Object handleBlockException(ProceedingJoinPoint pjp, Method originMethod, SentinelLimit sentinelLimit, BlockException ex) throws Exception {
Class clz = sentinelLimit.value();
Object o = LIMIT_OBJECT_MAP.get(clz);
if(o == null){
try {
o = clz.newInstance();
} catch (InstantiationException | IllegalAccessException e) {
log.error("实例化限流对象失败!className:{}", clz.getName(), e);
throw new RuntimeException("实例化限流对象失败", e);
}
LIMIT_OBJECT_MAP.put(clz, o);
}
return resolveFallbackMethod(pjp, clz, originMethod).invoke(o, pjp.getArgs());
}
/**
* 获取方法对象
*/
protected Method resolveMethod(ProceedingJoinPoint joinPoint, Class targetClass) {
MethodSignature signature = (MethodSignature)joinPoint.getSignature();
Method method = getDeclaredMethodFor(targetClass, signature.getName(),
signature.getMethod().getParameterTypes());
if (method == null) {
throw new IllegalStateException("Cannot resolve target method: " + signature.getMethod().getName());
}
return method;
}
/**
* 获取限流类的限流方法
*/
private Method resolveFallbackMethod(ProceedingJoinPoint pjp, Class fallbackClass, Method originMethod) {
if(LIMIT_METHOD_MAP.containsKey(originMethod)){
return LIMIT_METHOD_MAP.get(originMethod);
}
MethodSignature signature = (MethodSignature) pjp.getSignature();
Method method = getDeclaredMethodFor(fallbackClass, signature.getName(),
signature.getMethod().getParameterTypes());
if (method == null) {
String methodName = resolveMethodName(fallbackClass, originMethod);
throw new IllegalStateException("在限流类中没有找到方法:" + methodName);
}
LIMIT_METHOD_MAP.put(originMethod, method);
return method;
}
private Method getDeclaredMethodFor(Class clazz, String name, Class... parameterTypes) {
try {
return clazz.getDeclaredMethod(name, parameterTypes);
} catch (NoSuchMethodException e) {
Class superClass = clazz.getSuperclass();
if (superClass != null) {
return getDeclaredMethodFor(superClass, name, parameterTypes);
}
}
return null;
}
/**
* 获取资源名称
*/
private String getResourceName(Class originClass, Method method, SentinelLimitParam sentinelLimitParam){
if (sentinelLimitParam != null) {
return sentinelLimitParam.resourceName();
}
return resolveMethodName(originClass, method);
}
/**
* 获取入口类型,默认从 SentinelLimitParam 中获取,取不到直接返回 IN
*/
private EntryType getEntryType(SentinelLimitParam sentinelLimitParam){
if(sentinelLimitParam != null){
return sentinelLimitParam.entryType();
}
return EntryType.IN;
}
/**
* 根据class对象和方法对象解析资源名
*
格式: 类名:方法名(参数...)
*
例子:java.lang.String:indexOf(int)
*/
private String resolveMethodName(Class originClass, Method method) {
String className = originClass.getName();
String methodName = method.getName();
StringBuilder sb = new StringBuilder();
sb.append(className);
sb.append(":");
sb.append(methodName);
sb.append("(");
Class[] parameterTypes = method.getParameterTypes();
if(ArrayUtils.isNotEmpty(parameterTypes)){
for (Class parameterType : parameterTypes) {
sb.append(parameterType.getSimpleName() );
sb.append(", ");
}
sb.delete(sb.length()-2, sb.length());
}
sb.append(")");
return sb.toString();
}
}
```
```java
package com.frxs.trade.user.common.util.sentinel;
import com.alibaba.csp.sentinel.EntryType;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* 限流具体参数,用于在方法上
* @author qiaolin
* @version $Id: InLimit.java,v 0.1 2019年11月19日 15:23 $Exp
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface SentinelLimitParam {
/**
* 自定义资源名
*/
String resourceName();
/**
* 自定义 限流类型
*/
EntryType entryType() default EntryType.IN;
}
```
### in use
```java
package com.frxs.trade.user.core.service.config;
import com.frxs.trade.user.common.util.sentinel.SentinelLimitAspect;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* sentinel 配置
* @author qiaolin
* @version $Id: SentinelConfig.java,v 0.1 2019年11月19日 17:18 $Exp
*/
@Configuration
public class SentinelConfig {
@Bean
public SentinelLimitAspect sentinelLimitAspect(){
return new SentinelLimitAspect();
}
}
````
Contributor guide
Research direction
Start with SentinelLimit.java, SentinelLimitParam.java, and SentinelLimitAspect.java to understand the existing class- and method-level annotations and interception flow; SentinelConfig.java shows the Spring registration point. Determine how the requested class-dimension flow limiting should fit the framework and what integration behavior must be accepted, using the provided implementation as context.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- java, spring
- Domain
- backend
- Issue type
- Feature
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Stale
- Clarity
- Mostly clear
- Newbie friendliness
- 25/100