Spring Cloud Feign

类结构

在这里插入图片描述

主要类

  1. FeignAutoConfiguration:创建NamedContextFactory。
  2. FeignContext:NamedContextFactory的实现类,该类中含有FeignClientsConfiguration和FeignClientSpecification属性;FeignClientsConfiguration在创建FeignContext时默认加载到FeignContext中,如下图一所示;FeignClientSpecification包含服务名和对应Feign配置类,如下图二所示。
  3. HttpClientFeignLoadBalancedConfiguration:负载均衡类。

在这里插入图片描述
图一

在这里插入图片描述
图二

流程

通过启动类中增加@EnableFeignClients注解引入Feign

在EnableFeignClients中通过导入FeignClientsRegistrar创建Feign代理
在这里插入图片描述

FeignClientsRegistrar 继承自ImportBeanDefinitionRegistrar,在Spring启动时会调用registerBeanDefinitions方法,在registerBeanDefinitions方法中会向Spring容器中注入FeignClientSpecification配置类和创建FeignClientFactoryBean的类

注:所谓配置信息指的是@FeignClient(value = “tmp-test1”,configuration = )注解中configuration,该configuration引用的类和服务名生成FeignClientSpecification类,最终会放到FeignContext类(具体请看FeignAutoConfiguration),FeignContext在创建时会包含FeignClientsConfiguration和FeignClientSpecification属性,FeignClientsConfiguration和FeignClientSpecification类是用来创建feign.Decoder, feign.Encoder和a feign.Contract,FeignClientsConfiguration为全局配置,为每个Feign客户端创建一个新feign.Encoder和a feign.Contract,而后者FeignClientSpecification是针对特定Feign客户端特定配置,FeignClientSpecification将覆盖FeignClientsConfiguration属性;也可以在配置文件中配置,它们的优先级 【 配置文件>FeignClientSpecification>FeignClientsConfiguration】

class FeignClientsRegistrar implements ImportBeanDefinitionRegistrar, ResourceLoaderAware, EnvironmentAware {
	public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
	// 加载Feign默认配置信息
        this.registerDefaultConfiguration(metadata, registry);
        this.registerFeignClients(metadata, registry);
    }
}

	private void registerDefaultConfiguration(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
        Map<String, Object> defaultAttrs = metadata.getAnnotationAttributes(EnableFeignClients.class.getName(), true);
        if (defaultAttrs != null && defaultAttrs.containsKey("defaultConfiguration")) {
            String name;
            if (metadata.hasEnclosingClass()) {
                name = "default." + metadata.getEnclosingClassName();
            } else {
                name = "default." + metadata.getClassName();
            }

            this.registerClientConfiguration(registry, name, defaultAttrs.get("defaultConfiguration"));
    }
    
	public void registerFeignClients(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
        ClassPathScanningCandidateComponentProvider scanner = this.getScanner();
        scanner.setResourceLoader(this.resourceLoader);
        Map<String, Object> attrs = metadata.getAnnotationAttributes(EnableFeignClients.class.getName());
        AnnotationTypeFilter annotationTypeFilter = new AnnotationTypeFilter(FeignClient.class);
        Class<?>[] clients = attrs == null ? null : (Class[])((Class[])attrs.get("clients"));
        Object basePackages;
        if (clients != null && clients.length != 0) {
            final Set<String> clientClasses = new HashSet();
            basePackages = new HashSet();
            Class[] var9 = clients;
            int var10 = clients.length;

            for(int var11 = 0; var11 < var10; ++var11) {
                Class<?> clazz = var9[var11];
                ((Set)basePackages).add(ClassUtils.getPackageName(clazz));
                clientClasses.add(clazz.getCanonicalName());
            }

            AbstractClassTestingTypeFilter filter = new AbstractClassTestingTypeFilter() {
                protected boolean match(ClassMetadata metadata) {
                    String cleaned = metadata.getClassName().replaceAll("\\$", ".");
                    return clientClasses.contains(cleaned);
                }
            };
            scanner.addIncludeFilter(new FeignClientsRegistrar.AllTypeFilter(Arrays.asList(filter, annotationTypeFilter)));
        } else {
            scanner.addIncludeFilter(annotationTypeFilter);
            basePackages = this.getBasePackages(metadata);
        }

        Iterator var17 = ((Set)basePackages).iterator();

        while(var17.hasNext()) {
            String basePackage = (String)var17.next();
            Set<BeanDefinition> candidateComponents = scanner.findCandidateComponents(basePackage);
            Iterator var21 = candidateComponents.iterator();

            while(var21.hasNext()) {
                BeanDefinition candidateComponent = (BeanDefinition)var21.next();
                if (candidateComponent instanceof AnnotatedBeanDefinition) {
                    AnnotatedBeanDefinition beanDefinition = (AnnotatedBeanDefinition)candidateComponent;
                    AnnotationMetadata annotationMetadata = beanDefinition.getMetadata();
                    Assert.isTrue(annotationMetadata.isInterface(), "@FeignClient can only be specified on an interface");
                    Map<String, Object> attributes = annotationMetadata.getAnnotationAttributes(FeignClient.class.getCanonicalName());
                    String name = this.getClientName(attributes);
                    // 获取配置中的configuration,设置configuration属性
                    this.registerClientConfiguration(registry, name, attributes.get("configuration"));
					// 在Spring容器中注入FeignClientFactoryBean类,在启动时创建FeignClientFactoryBean对象
                    this.registerFeignClient(registry, annotationMetadata, attributes);
                }
            }
        }
    }
// 获取配置中的configuration,创建FeignClientSpecification对象,设置configuration属性
    private void registerClientConfiguration(BeanDefinitionRegistry registry, Object name, Object configuration) {
        BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(FeignClientSpecification.class);
        builder.addConstructorArgValue(name);
        builder.addConstructorArgValue(configuration);
        registry.registerBeanDefinition(name + "." + FeignClientSpecification.class.getSimpleName(), builder.getBeanDefinition());
    }
}

在registerFeignClient方法中,在向Spring容器中注入FeignClientFactoryBean时会设置相应属性。

属性解释如下

  1. url和path,path的作用是拼接到url后边,例如:url->http://localhost:8080, path->sys,则feign请求会去调用http://localhost:8080/sys
  2. name,主要是针对有注册中心,表示服务名,获取顺序为注解属性serviceId->name->value
  3. contextId,feign会为每一个contextId创建一个ApplicationContext,里面包含对应的Feign属性,获取顺序为注解属性contextId->serviceId->name->value
class FeignClientsRegistrar implements ImportBeanDefinitionRegistrar, ResourceLoaderAware, EnvironmentAware {
	 private void registerFeignClient(BeanDefinitionRegistry registry, AnnotationMetadata annotationMetadata, Map<String, Object> attributes) {
        String className = annotationMetadata.getClassName();
        BeanDefinitionBuilder definition = BeanDefinitionBuilder.genericBeanDefinition(FeignClientFactoryBean.class);
        this.validate(attributes);
        definition.addPropertyValue("url", this.getUrl(attributes));
        definition.addPropertyValue("path", this.getPath(attributes));
        String name = this.getName(attributes);
        definition.addPropertyValue("name", name);
        String contextId = this.getContextId(attributes);
        definition.addPropertyValue("contextId", contextId);
        definition.addPropertyValue("type", className);
        definition.addPropertyValue("decode404", attributes.get("decode404"));
        definition.addPropertyValue("fallback", attributes.get("fallback"));
        definition.addPropertyValue("fallbackFactory", attributes.get("fallbackFactory"));
        definition.setAutowireMode(2);
        String alias = contextId + "FeignClient";
        AbstractBeanDefinition beanDefinition = definition.getBeanDefinition();
        boolean primary = (Boolean)attributes.get("primary");
        beanDefinition.setPrimary(primary);
        String qualifier = this.getQualifier(attributes);
        if (StringUtils.hasText(qualifier)) {
            alias = qualifier;
        }

        BeanDefinitionHolder holder = new BeanDefinitionHolder(beanDefinition, className, new String[]{alias});
        BeanDefinitionReaderUtils.registerBeanDefinition(holder, registry);
    }
}

FeignClientFactoryBean 是Feign接口的代理类,继承自FactoryBean,所以当获取Bean时会调用getObject方法,

  1. 接着会调用getTarget方法,
  2. 从Spring上下中获取FeignContext对象
  3. 创建feign.Budilder ,并且配置feign.Budilder 属性

配置文件中自定义配置具体请看FeignClientProperties.FeignClientConfiguration类信息

  public static class FeignClientConfiguration {
  		private Level loggerLevel;// 是否打印日志
        private Integer connectTimeout;// 连接时间
        private Integer readTimeout;// 读取超时时间
        private Class<Retryer> retryer;// 重试次数
        private Class<ErrorDecoder> errorDecoder;// 响应失败解析
        private List<Class<RequestInterceptor>> requestInterceptors;// 请求拦截器
        private Boolean decode404;// 404解析器
        private Class<Decoder> decoder;// 响应成功解析器
        private Class<Encoder> encoder;
        private Class<Contract> contract;
  }

注:(FeignContext是在FeignAutoConfiguration类中自动加载的,同时该类包含了所有的FeignClientSpecification对象)

class FeignClientFactoryBean implements FactoryBean<Object>, InitializingBean, ApplicationContextAware {
 	public Object getObject() throws Exception {
        return this.getTarget();
	 }

    <T> T getTarget() {
    	// 从Spring上下中获取FeignContext对象
        FeignContext context = (FeignContext)this.applicationContext.getBean(FeignContext.class);
		// 获取Feign,配置feign属性,通过查找所有,没有则用默认的feign属性
        Builder builder = this.feign(context);
        if (!StringUtils.hasText(this.url)) {// url为空,则是利用注册中心,否则,通过直连http调用
            if (!this.name.startsWith("http")) {
                this.url = "http://" + this.name;
            } else {
                this.url = this.name;
            }

            this.url = this.url + this.cleanPath();
            return this.loadBalance(builder, context, new HardCodedTarget(this.type, this.name, this.url));
        } else {
            if (StringUtils.hasText(this.url) && !this.url.startsWith("http")) {
                this.url = "http://" + this.url;
            }

            String url = this.url + this.cleanPath();
            Client client = (Client)this.getOptional(context, Client.class);
            if (client != null) {
                if (client instanceof LoadBalancerFeignClient) {
                    client = ((LoadBalancerFeignClient)client).getDelegate();
                }

                builder.client(client);
            }

            Targeter targeter = (Targeter)this.get(context, Targeter.class);
            return targeter.target(this, builder, context, new HardCodedTarget(this.type, this.name, url));
        }
    }
     protected Builder feign(FeignContext context) {
        FeignLoggerFactory loggerFactory = (FeignLoggerFactory)this.get(context, FeignLoggerFactory.class);
        Logger logger = loggerFactory.create(this.type);
        Builder builder = ((Builder)this.get(context, Builder.class)).logger(logger).encoder((Encoder)this.get(context, Encoder.class)).decoder((Decoder)this.get(context, Decoder.class)).contract((Contract)this.get(context, Contract.class));
        this.configureFeign(context, builder);
        return builder;
    }
}

通过Targeter创建Feign代理类

    protected <T> T loadBalance(Builder builder, FeignContext context, HardCodedTarget<T> target) {
        Client client = (Client)this.getOptional(context, Client.class);
        if (client != null) {
            builder.client(client);
            Targeter targeter = (Targeter)this.get(context, Targeter.class);
            return targeter.target(this, builder, context, target);
        } else {
            throw new IllegalStateException("No Feign Client for loadBalancing defined. Did you forget to include spring-cloud-starter-netflix-ribbon?");
        }
    }

如果Builder 是feign.hystrix.HystrixFeign.Builder,则获取Hystrix降级接口,创建HystrixInvocationHandler代理类,否则创建非Hystrix代理类, 最终创建SynchronousMethodHandler代理类执行具体操作

class HystrixTargeter implements Targeter {
    HystrixTargeter() {
    }

    public <T> T target(FeignClientFactoryBean factory, Builder feign, FeignContext context, HardCodedTarget<T> target) {
        if (!(feign instanceof feign.hystrix.HystrixFeign.Builder)) {
            return feign.target(target);
        } else {
            feign.hystrix.HystrixFeign.Builder builder = (feign.hystrix.HystrixFeign.Builder)feign;
            SetterFactory setterFactory = (SetterFactory)this.getOptional(factory.getName(), context, SetterFactory.class);
            if (setterFactory != null) {
                builder.setterFactory(setterFactory);
            }

            Class<?> fallback = factory.getFallback();
            if (fallback != Void.TYPE) {
            // 如果又返回值,获取降级接口,创建回调代理类
                return this.targetWithFallback(factory.getName(), context, target, builder, fallback);
            } else {
                Class<?> fallbackFactory = factory.getFallbackFactory();
                return fallbackFactory != Void.TYPE ? this.targetWithFallbackFactory(factory.getName(), context, target, builder, fallbackFactory) : feign.target(target);
            }
        }
	 	private <T> T targetWithFallback(String feignClientName, FeignContext context, HardCodedTarget<T> target, feign.hystrix.HystrixFeign.Builder builder, Class<?> fallback) 	{
        T fallbackInstance = this.getFromContext("fallback", feignClientName, context, fallback, target.type());
        return builder.target(target, fallbackInstance);
   		 }
    }

SynchronousMethodHandler

  1. 获取请求参数,如连接读取超时时间
  2. 调用拦截器RequestInterceptor
  3. 请求
  4. 返回异常,抛出的异常信息,通过ErrorDecoder处理,否则请求正确,解析响应,默认走SpringDecoder,SpringDecorder在Spring启动的时候会注入参数解析器,用来解析参数
final class SynchronousMethodHandler implements MethodHandler {
public Object invoke(Object[] argv) throws Throwable {
        RequestTemplate template = this.buildTemplateFromArgs.create(argv);
        // 获取请求参数,如连接读取超时时间
        Options options = this.findOptions(argv);
        Retryer retryer = this.retryer.clone();

        while(true) {
            try {
                return this.executeAndDecode(template, options);
            } catch (RetryableException var9) {
                RetryableException e = var9;

                try {
                    retryer.continueOrPropagate(e);
                } catch (RetryableException var8) {
                    Throwable cause = var8.getCause();
                    if (this.propagationPolicy == ExceptionPropagationPolicy.UNWRAP && cause != null) {
                        throw cause;
                    }

                    throw var8;
                }

                if (this.logLevel != Level.NONE) {
                    this.logger.logRetry(this.metadata.configKey(), this.logLevel);
                }
            }
        }
    }
    
	Object executeAndDecode(RequestTemplate template, Options options) throws Throwable {
		// 调用拦截器RequestInterceptor
        Request request = this.targetRequest(template);
        if (this.logLevel != Level.NONE) {
            this.logger.logRequest(this.metadata.configKey(), this.logLevel, request);
        }

        long start = System.nanoTime();

        Response response;
        try {
        // 发送请求
            response = this.client.execute(request, options);
        } catch (IOException var16) {
            if (this.logLevel != Level.NONE) {
                this.logger.logIOException(this.metadata.configKey(), this.logLevel, var16, this.elapsedTime(start));
            }

            throw FeignException.errorExecuting(request, var16);
        }

        long elapsedTime = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - start);
        boolean shouldClose = true;

        Object var11;
        try {
            if (this.logLevel != Level.NONE) {
                response = this.logger.logAndRebufferResponse(this.metadata.configKey(), this.logLevel, response, elapsedTime);
            }

            if (Response.class == this.metadata.returnType()) {
                Response var19;
                if (response.body() == null) {
                    var19 = response;
                    return var19;
                }

                if (response.body().length() != null && (long)response.body().length() <= 8192L) {
                    byte[] bodyData = Util.toByteArray(response.body().asInputStream());
                    Response var21 = response.toBuilder().body(bodyData).build();
                    return var21;
                }

                shouldClose = false;
                var19 = response;
                return var19;
            }

            Object result;
            if (response.status() >= 200 && response.status() < 300) {
                if (Void.TYPE == this.metadata.returnType()) {
                    result = null;
                    return result;
                }

                result = this.decode(response);
                shouldClose = this.closeAfterDecode;
                var11 = result;
                return var11;
            }

            if (!this.decode404 || response.status() != 404 || Void.TYPE == this.metadata.returnType()) {
            // 返回异常,抛出的异常信息
                throw this.errorDecoder.decode(this.metadata.configKey(), response);
            }

//	请求正确,解析响应,默认走SpringDecoder
            result = this.decode(response);
            shouldClose = this.closeAfterDecode;
            var11 = result;
        } catch (IOException var17) {
            if (this.logLevel != Level.NONE) {
                this.logger.logIOException(this.metadata.configKey(), this.logLevel, var17, elapsedTime);
            }

            throw FeignException.errorReading(request, response, var17);
        } finally {
            if (shouldClose) {
                Util.ensureClosed(response.body());
            }

        }

        return var11;
    }

}

Feign 如何设置超时时间(connectionTimeout、readTimout)

对于这个问题网上有很多相关资料,大体上有两种方案,一种是通过设置 ribbon 的超时时间(因为 Feign 是基于 ribbon 来实现的,所以通过 ribbon 的超时时间设置也能达到目的),一种是直接设置 Feign 的超时时间,我将会在下边的篇幅里分别说一下如何通过application.yml 配置文件来设置超时时间。(注:这里是以 Feign 的默认客户端(Client.Default)来说的!!!!)

1、Ribbon

对于 ribbon 又分为全局配置和指定服务配置:

1.1 全局配置

对所有的服务该配置都生效

 ribbon:  
    ReadTimeout: 30000 #单位毫秒
    ConnectTimeout: 30000 #单位毫秒
1.2 指定服务配置

下边代码中的 annoroad-beta 是服务的名称,意思是该配置只针对名为 annoroad-beta 的服务有效,根据实际的需要替换成你自己的服务名

tmp:
  ribbon:
    ReadTimeout: 30000 #单位毫秒
    ConnectTimeout: 30000 #单位毫秒

2、Feign

与 Ribbon 一样,Feign 也分为全局配置和指定服务配置:

2.1 全局配置

下边代码中使用的 feign.client.config.default ,意思是所有服务都采用该配置

feign:
  client:
    config:
      default:
        connectTimeout: 10000 #单位毫秒
        readTimeout: 10000 #单位毫秒
2.2 指定服务配置

下边代码中使用的 feign.client.config.annoroad-beta,意思是该配置只针对名为 annoroad-beta 的服务有效,可以根据实际的需要替换成你自己的服务名

`

feign:
  client:
    config:
      tmp:
        connectTimeout: 10000 #单位毫秒
        readTimeout: 10000 #单位毫秒

总结

如果同时配置了Ribbon、Feign,那么 Feign 的配置将生效
Ribbon 的配置要想生效必须满足微服务相互调用的时候通过注册中心,如果你是在本地通过 @FeignClient 注解的 url 参数进行服务相互调用的测试,此时 ribbon 设置的超时时间将会失效,但是通过 Feign 设置的超时时间不会受到影响(仍然会生效)
综上所述建议使用 Feign 的来设置超时时间
具体请看LoadBalancerFeignClient类

public class FeignLoadBalancer extends AbstractLoadBalancerAwareClient<FeignLoadBalancer.RibbonRequest, FeignLoadBalancer.RibbonResponse> {
    public FeignLoadBalancer.RibbonResponse execute(FeignLoadBalancer.RibbonRequest request, IClientConfig configOverride) throws IOException {
        Options options;
        if (configOverride != null) {
            RibbonProperties override = RibbonProperties.from(configOverride);
            options = new Options(override.connectTimeout(this.connectTimeout), override.readTimeout(this.readTimeout));
        } else {
            options = new Options(this.connectTimeout, this.readTimeout);
        }

		// 调用request中的Client的execute类,其中options封装了Feign配置信息,在Client中会调用convertAndSend进行转换 
        Response response = request.client().execute(request.toRequest(), options);
        return new FeignLoadBalancer.RibbonResponse(request.getUri(), response);
    }
}
public interface Client {
 		 public Response execute(Request request, Options options) throws IOException {
            HttpURLConnection connection = this.convertAndSend(request, options);
            return this.convertResponse(connection, request);
        }
        
}

熔断

开启Hystrix

feign:
  hystrix:
    enabled: true
@FeignClient(value = "tmp-test1",fallback = HelloServicFallback.class)
public interface HelloFeign {

    @GetMapping("/feign")
    public String feign();

}

实现HelloFeign

@Component
public class HelloServicFallback implements HelloFeign{

    public String feign() {
        return "error";
    }
}

或打印错误日志需要实现FallbackFactory接口

@Component
@Slf4j
public class HelloServicFallbackFactory implements FallbackFactory<IUserClient> {

    @Override
    public HelloFeign create(Throwable throwable) {
        return new HelloFeign() {
            @Override
             public String feign() {
               log.error("错误", throwable.getMessage());
		        return "error";
		    }
        };
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值