Issue Description
Is it possible to provide a flow-limiting annotation based on the class dimension
Describe what you expected to happen
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<OrderCreateResult> create(TradeOrderUserRequest request) {
return null;
}
@Override
@SuppressWarnings("unchecked")
@SentinelLimitParam(resourceName = "自定义资源名")
public TradeResult<String> shieldOrderUser(TradeShieldUserRequest request) {
return null;
}
}
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<OrderCreateResult> create(TradeOrderUserRequest request) {
log.warn("创建订单触发限流 userId:{}", request.getUserId());
return TradeUserResultHelper.fillWithFailure(CreateCodeEnum.SENTINEL_BLOCK_IN_ERROR);
}
@Override
public TradeResult<String> 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
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();
}
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<Class<?>, Object> LIMIT_OBJECT_MAP = new HashMap<>();
private static final Map<Method, Method> 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对象和方法对象解析资源名
* <p>格式: 类名:方法名(参数...)
* <p>例子: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();
}
}
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
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();
}
}
Issue Description
Is it possible to provide a flow-limiting annotation based on the class dimension
Describe what you expected to happen
Anything else we need to know?
I've implemented it, can I incorporate it into the framework
The following code
in use