fegin实现方法级别注解超时配置

fegin实现方法级别注解超时配置

测试的3.18新版本已经支持方法中参数带有Options 也可以自定义配置, Options options = findOptions(argv);;

使用该注解方式需配合AOP使用!

原理是包装自己的client客户端, 替换框架的客户端!
应用到生产环境需自己充验证测试

1.0 注解

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface FeignTimeout {
    /**
     * 连接超时时间
     * @return
     */
    int connectTimeoutMillis() default 10000;

    /**
     * 读取超时时间
     * @return
     */
    int readTimeoutMillis() default 60000;
}

2.0 包装的客户端FeignTimeoutClient

import feign.Client;
import feign.Request;
import feign.Response;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.IOException;
import java.util.Objects;

public class FeignTimeoutClient implements Client {

    private static final Logger log = LoggerFactory.getLogger(FeignTimeoutClient.class);

    private static final ThreadLocal<Request.Options> NOW_OPTIONS = new ThreadLocal<>();

    private final Client delegate;

    private FeignTimeoutClient(Client delegate){
        this.delegate = delegate;
    }

    public static Client wrap(Client client){
        return new FeignTimeoutClient(client);
    }

    public static void nowOptions(Request.Options options){
        NOW_OPTIONS.set(options);
    }

    public static void nowOptionsClear(){
        NOW_OPTIONS.remove();
    }

    @Override
    public Response execute(Request request, Request.Options options) throws IOException {
        // 如果未取到当前上下文的 自定义超时时间 则直接使用默认的,如果取到了则用特殊的
        Request.Options optionsNow = NOW_OPTIONS.get();
        if(Objects.nonNull(optionsNow)){
            log.info("feign命中自定义超时:url="+request.url()+";options.connectTimeoutMillis="+ optionsNow.connectTimeoutMillis()+",options.readTimeoutMillis="+optionsNow.readTimeoutMillis());
            return delegate.execute(request,optionsNow);
        }
        return delegate.execute(request,options);
    }
}

3.0 包装FeignContext

FeignTimeoutFeignContext上下文

import feign.Client;
import feign.Feign;
import lombok.SneakyThrows;
import org.apache.commons.collections4.MapUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.cloud.openfeign.FeignContext;
import org.springframework.util.ReflectionUtils;

import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Map;

public class FeignTimeoutFeignContext extends FeignContext {
    private static final Logger log = LoggerFactory.getLogger(FeignTimeoutFeignContext.class);
    private final FeignContext delegate;

    private FeignTimeoutFeignContext(FeignContext delegate) {
        this.delegate = delegate;
    }

    @Override
    public <T> T getInstance(String name, Class<T> type) {
        T instance = delegate.getInstance(name, type);
        return (T) wrapIfShould(instance);
    }

    @Override
    public <T> Map<String, T> getInstances(String name, Class<T> type) {
        Map<String, T> instances = delegate.getInstances(name, type);
        if (MapUtils.isNotEmpty(instances)) {
            Map<String, T> convertedInstances = new HashMap<>();
            for (Map.Entry<String, T> entry : instances.entrySet()) {
                convertedInstances.put(entry.getKey(), (T) wrapIfShould(entry.getValue()));
            }
            return convertedInstances;
        }

        return instances;
    }

    @SneakyThrows
    private Object wrapIfShould(Object instance) {
        if (instance instanceof Feign.Builder) {
            Field field = instance.getClass().getDeclaredField("client");
            field.setAccessible(true);
            Client client = (Client) ReflectionUtils.getField(field, instance);
            if (client instanceof FeignTimeoutClient) {
                return instance;
            }
            Client wrap = FeignTimeoutClient.wrap(client);
            ReflectionUtils.setField(field, instance, wrap);
        }
        if (instance instanceof Client && !(instance instanceof FeignTimeoutClient)) {
            Object client = instance;
            return FeignTimeoutClient.wrap((Client) client);
        }
        return instance;
    }

    public static FeignTimeoutFeignContext wrap(FeignContext context) {
        return new FeignTimeoutFeignContext(context);
    }
}

bean后置处理器进行包装

package com.zhihao.fegin;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.cloud.openfeign.FeignContext;
public class FeignTimeoutBeanPostProcessor implements BeanPostProcessor {
    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        // 对FeignContext进行包装
        if(bean instanceof FeignContext && !(bean instanceof FeignTimeoutFeignContext)){
            return FeignTimeoutFeignContext.wrap(((FeignContext) bean));
        }
        return bean;
    }

    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        return bean;
    }
}

4.0 配置AOP注解拦截设置超时

package com.zhihao.fegin;
import feign.Request;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import org.springframework.core.annotation.AnnotationUtils;
import java.util.Objects;
public class FeignTimeoutMethodInterceptor implements MethodInterceptor {

    @Override
    public Object invoke(MethodInvocation invocation) throws Throwable {
        try {
            // 先查找当前方法以及方法所在类上的注解
            FeignTimeout feignTimeout = AnnotationUtils.findAnnotation(invocation.getMethod(), FeignTimeout.class);
            if(Objects.nonNull(feignTimeout)){
                FeignTimeoutClient.nowOptions(new Request.Options(feignTimeout.connectTimeoutMillis(), feignTimeout.readTimeoutMillis()));
            }
            return invocation.proceed();
        } finally {
            FeignTimeoutClient.nowOptionsClear();
        }
    }
}

5.0 使用配置类加入IOC容器

@Configuration
// 如果后续有配置需要在FeignClientsConfiguration初始化前使用则需要该注解, 配合@condition使用
// @AutoConfigureBefore(FeignClientsConfiguration.class)
public class FeignConfiguration {

    @Bean
    public FeignTimeoutBeanPostProcessor feignTimeoutBeanPostProcessor(){
        return new FeignTimeoutBeanPostProcessor();
    }

    @Bean
    public DefaultPointcutAdvisor feignTimeoutPointcutAdvisor(){
        FeignTimeoutMethodInterceptor interceptor = new FeignTimeoutMethodInterceptor();
        AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut();
        pointcut.setExpression("@annotation(com.zhihao.spring.cloud.fegin.ext.timeout.FeignTimeout)");
        // 配置增强类advisor
        DefaultPointcutAdvisor advisor = new DefaultPointcutAdvisor();
        advisor.setPointcut(pointcut);
        advisor.setAdvice(interceptor);
        return advisor;
    }
}

6.0 使用

/**
 * @Author: ZhiHao
 * @Date: 2023/8/26 14:33
 * @Description:
 * @Versions 1.0
 **/
@FeignClient(name = "feignTimeOutTest",url = "https://test-zhiha.plus")
public interface FeignTimeOutTest {

    @GetMapping("/masterPriceManager/detail")
    @FeignTimeout(connectTimeoutMillis = 500,readTimeoutMillis = 300)
    Map<String, Object> detail(@RequestParam("masterId") Long masterId);
}

7.0 结果:

fegin实现方法级别注解超时配置_第1张图片

ail")
@FeignTimeout(connectTimeoutMillis = 500,readTimeoutMillis = 300)
Map detail(@RequestParam(“masterId”) Long masterId);
}


7.0 结果:

[[外链图片转存中...(img-YhTVhjsr-1693034378942)]](https://imgse.com/i/pPNDPOK)



1

你可能感兴趣的:(Springcloud,feign,SpringBoot,spring,boot,spring)