测试的
3.18
新版本已经支持方法中参数带有Options
也可以自定义配置,Options options = findOptions(argv);
;使用该注解方式需配合AOP使用!
原理是包装自己的client客户端, 替换框架的客户端!
应用到生产环境需自己充验证测试
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface FeignTimeout {
/**
* 连接超时时间
* @return
*/
int connectTimeoutMillis() default 10000;
/**
* 读取超时时间
* @return
*/
int readTimeoutMillis() default 60000;
}
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);
}
}
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;
}
}
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();
}
}
}
@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;
}
}
/**
* @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
}
7.0 结果:
[[外链图片转存中...(img-YhTVhjsr-1693034378942)]](https://imgse.com/i/pPNDPOK)
1