Skip to content

Is it possible to provide a flow-limiting annotation based on the class dimension #1598

@qiaolin-li

Description

@qiaolin-li

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();
    }
}

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions