All Downloads are FREE. Search and download functionalities are using the official Maven repository.

cn.ishow.starter.rpc.aop.RpcClientAdvice Maven / Gradle / Ivy

There is a newer version: 2.2.0
Show newest version
package cn.ishow.starter.rpc.aop;


import cn.ishow.starter.common.util.AopTargetUtils;
import cn.ishow.starter.common.util.ExceptionUtils;
import cn.ishow.starter.common.util.java.FieldUtils;
import cn.ishow.starter.rpc.annotation.Decode;
import cn.ishow.starter.rpc.annotation.Options;
import cn.ishow.starter.rpc.constant.RpcConst;
import cn.ishow.starter.rpc.context.RpcContext;
import feign.Feign;
import feign.Request;
import feign.Target;
import feign.codec.DecodeException;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import org.springframework.aop.support.AopUtils;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.core.env.Environment;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;

/**
 * 拦截Feign接口上面方法,并动态设置HTTP的options和自定义解码器
 *
 * @author bucheng
 * @create 2022/5/15 11:29
 */
@Slf4j
public class RpcClientAdvice implements MethodInterceptor {
    public static final String TARGET = "target";
    private final RpcContext rpcContext;
    private final Environment environment;
    private final ConcurrentHashMap> methodTargetMap = new ConcurrentHashMap<>();


    public RpcClientAdvice(RpcContext rpcContext, Environment environment) {
        this.rpcContext = rpcContext;
        this.environment = environment;
    }

    @Override
    public Object invoke(MethodInvocation invocation) throws Throwable {
        try {
            rpcContext.preProcess();
            parseAndSaveConfig(invocation);
            return invocation.proceed();
        } catch (DecodeException exception) {
            throw ExceptionUtils.getRealException(exception);
        } finally {
            rpcContext.postProcess();
        }
    }

    /**
     * 保存相关自定义配置
     *
     * @param invocation
     */
    private void parseAndSaveConfig(MethodInvocation invocation) {
        parseAndSaveHttpOptions(invocation);
        parseAndSaveRtDecoder(invocation);
    }

    /**
     * 从方法上面将options保存到threadLocal变量中
     *
     * @param invocation
     */
    private void parseAndSaveHttpOptions(MethodInvocation invocation) {
        Target target = methodTargetMap.computeIfAbsent(invocation.getThis(), o -> getTarget(o));
        //生成的key为:ishow.rpc.${类名}#方法名称(类型名) 这里名称不包含包名称
        String optionKey = Feign.configKey(target.type(), invocation.getMethod());
        if (log.isDebugEnabled()) {
            log.debug("Create method http option key:{}", optionKey);
        }
        //先尝试从配置中获取指定方法相关超时配置
        OptionsBo optionsBo = resolveFromEnv(optionKey);
        //再从方法注解获取超时配置并进行补充
        resolveFromAnnotation(optionsBo, invocation);
        //最后产生从配置中获取全局超时配置
        resolveFromGlobal(optionsBo);
        if (optionsBo.getConnectTimeoutMills() != -1L
                || optionsBo.getReadTimeoutMills() != -1L) {
            rpcContext.addRequestOptions(new Request.Options(optionsBo.getConnectTimeoutMills(), TimeUnit.MILLISECONDS,
                    optionsBo.getReadTimeoutMills(), TimeUnit.MILLISECONDS,
                    true));
        }
    }

    /**
     * 构建http属性
     *
     * @param optionKey
     * @param timeoutKey
     * @return
     */
    private String buildOptionsKey(String optionKey, String timeoutKey) {
        return RpcConst.FEIGN_PREFIX + "." + optionKey + "." + timeoutKey;
    }

    /**
     * 从方法中提前自定义的解码规则
     *
     * @param invocation
     */
    private void parseAndSaveRtDecoder(MethodInvocation invocation) {
        Method method = invocation.getMethod();
        Decode annotation = AnnotationUtils.findAnnotation(method, Decode.class);
        if (annotation == null) {
            Class clazz = AopUtils.getTargetClass(invocation.getThis());
            annotation = AnnotationUtils.findAnnotation(clazz, Decode.class);
        }
        if (annotation != null) {
            rpcContext.addRtDecoder(annotation);
        }
    }

    private Target getTarget(Object proxyTarget) {
        InvocationHandler handler = (InvocationHandler) AopTargetUtils.getJdkDynamicProxyTargetObject(proxyTarget);
        return FieldUtils.get(handler, TARGET);
    }

    private OptionsBo resolveFromEnv(String optionKey) {
        //先尝试从配置中获取指定方法相关超时配置
        Long connectTimeoutMills = environment.getProperty(buildOptionsKey(optionKey, RpcConst.CONNECTION_TIMEOUT), Long.class, -1L);
        Long readTimeoutMills = environment.getProperty(buildOptionsKey(optionKey, RpcConst.READ_TIMEOUT), Long.class, -1L);
        return OptionsBo.builder()
                .connectTimeoutMills(connectTimeoutMills)
                .readTimeoutMills(readTimeoutMills)
                .build();
    }

    private void resolveFromAnnotation(OptionsBo optionsBo, MethodInvocation invocation) {
        Options annotation = AnnotationUtils.findAnnotation(invocation.getMethod(), Options.class);
        if (annotation != null) {
            if (optionsBo.getConnectTimeoutMills() == -1L) {
                optionsBo.setConnectTimeoutMills(annotation.connectTimeoutMills());
            }
            if (optionsBo.getReadTimeoutMills() == -1L) {
                optionsBo.setReadTimeoutMills(annotation.readTimeoutMills());
            }
        }
    }

    private void resolveFromGlobal(OptionsBo optionsBo) {
        if (optionsBo.getConnectTimeoutMills() == -1L) {
            optionsBo.setConnectTimeoutMills(environment.getProperty(RpcConst.GLOBAL_CONNECTION_TIMEOUT, Long.class, -1L));
        }
        if (optionsBo.getReadTimeoutMills() == -1L) {
            optionsBo.setReadTimeoutMills(environment.getProperty(RpcConst.GLOBAL_READ_TIMEOUT, Long.class, -1L));
        }
    }

    /**
     * http请求参数
     */
    @Data
    @Builder
    @NoArgsConstructor
    @AllArgsConstructor
    private static class OptionsBo {
        private Long connectTimeoutMills;
        private Long readTimeoutMills;
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy