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();}@Overridepublic 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;}@Overridepublic <T> T getInstance(String name, Class<T> type) {T instance = delegate.getInstance(name, type);return (T) wrapIfShould(instance);}@Overridepublic <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;}@SneakyThrowsprivate 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 {@Overridepublic Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {// 对FeignContext进行包装if(bean instanceof FeignContext && !(bean instanceof FeignTimeoutFeignContext)){return FeignTimeoutFeignContext.wrap(((FeignContext) bean));}return bean;}@Overridepublic 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 {@Overridepublic 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 {@Beanpublic FeignTimeoutBeanPostProcessor feignTimeoutBeanPostProcessor(){return new FeignTimeoutBeanPostProcessor();}@Beanpublic DefaultPointcutAdvisor feignTimeoutPointcutAdvisor(){FeignTimeoutMethodInterceptor interceptor = new FeignTimeoutMethodInterceptor();AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut();pointcut.setExpression("@annotation(com.zhihao.spring.cloud.fegin.ext.timeout.FeignTimeout)");// 配置增强类advisorDefaultPointcutAdvisor 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 结果:
ail")
@FeignTimeout(connectTimeoutMillis = 500,readTimeoutMillis = 300)
Map<String, Object> detail(@RequestParam(“masterId”) Long masterId);
}
7.0 结果:[[外链图片转存中...(img-YhTVhjsr-1693034378942)]](https://imgse.com/i/pPNDPOK)1