SpringCloud印象加深——feign的工作原理源码理解

Feign的工作原理

feign是一个伪客户端,即它不做任何的请求处理。Feign通过处理注解生成request,从而实现简化HTTP API开发的目的,即开发人员可以使用注解的方式定制request api模板,在发送http request请求之前,feign通过处理注解的方式替换掉request模板中的参数,这种实现方式显得更为直接、可理解。

1:如何启用

启动配置上检查是否有@EnableFeignClients注解,如果有该注解,则开启包扫描,扫描被@FeignClient注解修饰的接口。扫描出该注解后,通过beanDefinition注入到IOC容器中,方便后续被调用使用。

  • @EnableFeignClients注解中可以看到有导入FeignClientsRegistrar类,这个类就是用来扫面@FeignClient注解修饰的接口然后注册到IOC容器中的。

SpringCloud印象加深——feign的工作原理源码理解_第1张图片

  • 在FeignClientsRegistrar中,registerFeignClients()完成了注册feign的操作。
public void registerFeignClients(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
        ClassPathScanningCandidateComponentProvider scanner = this.getScanner();
        scanner.setResourceLoader(this.resourceLoader);
        Map attrs = metadata.getAnnotationAttributes(EnableFeignClients.class.getName());
        ......

//遍历该项目所需调用的服务
Iterator var17 = ((Set)basePackages).iterator();

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

            while(var21.hasNext()) {
                BeanDefinition candidateComponent = (BeanDefinition)var21.next();
                if (candidateComponent instanceof AnnotatedBeanDefinition) {
                    AnnotatedBeanDefinition beanDefinition = (AnnotatedBeanDefinition)candidateComponent;
                    //获取feign中的详细信息
                    AnnotationMetadata annotationMetadata = beanDefinition.getMetadata();
                    Map attributes = annotationMetadata.getAnnotationAttributes(FeignClient.class.getCanonicalName());
                    String name = this.getClientName(attributes);
                    //注册配置信息
                    this.registerClientConfiguration(registry, name, attributes.get("configuration"));
                    //注册feign客户端
                    this.registerFeignClient(registry, annotationMetadata, attributes);
                }
            }
        }
      }

注册feign客户端registerFeignClient,包括使用注解时配置的所有信息。当类有@FeignClient注解,将注解的信息取出,连同类名一起取出,赋给BeanDefinitionBuilder,然后根据BeanDefinitionBuilder得到beanDefinition,最后beanDefinition式注入到ioc容器中

private void registerFeignClient(BeanDefinitionRegistry registry, AnnotationMetadata annotationMetadata, Map 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);
        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 = name + "FeignClient";
        AbstractBeanDefinition beanDefinition = definition.getBeanDefinition();
        boolean primary = ((Boolean)attributes.get("primary")).booleanValue();
        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);
    }

2:如何发起请求

ReflectiveFeign内部使用了jdk的动态代理为目标接口生成了一个动态代理类,这里会生成一个InvocationHandler(jdk动态代理原理)统一的方法拦截器,同时为接口的每个方法生成一个SynchronousMethodHandler拦截器,并解析方法上的 元数据,生成一个http请求模板。

  • 注入bean之后,通过jdk的代理,当请求Feign Client的方法时会被拦截,代码在ReflectiveFeign类。其中targetToHandlersByName.apply(target)负责解析所有方法的注解元数据并创建每个方法对应的methodhandler用于代理对象调用。代码如下:

    public  T newInstance(Target target) {
        //解析方法注解
        Map nameToHandler = targetToHandlersByName.apply(target);
        Map methodToHandler = new LinkedHashMap();
        List defaultMethodHandlers = new LinkedList();
        //遍历所有方法,对应上解析后的methodhandler
        for (Method method : target.type().getMethods()) {
            if (method.getDeclaringClass() == Object.class) {
                continue;
            } else if (Util.isDefault(method)) {
                DefaultMethodHandler handler = new DefaultMethodHandler(method);
                defaultMethodHandlers.add(handler);
                methodToHandler.put(method, handler);
            } else {
                methodToHandler.put(method, nameToHandler.get(Feign.configKey(target.type(), method)));
            }
        }
        //创建动态代理invocationhandler,FeignInvocationHandler
        InvocationHandler handler = factory.create(target, methodToHandler);
        T proxy = (T) Proxy.newProxyInstance(target.type().getClassLoader(),
                new Class[]{target.type()}, handler);
        //java8 默认方法处理
        for (DefaultMethodHandler defaultMethodHandler : defaultMethodHandlers) {
            defaultMethodHandler.bindTo(proxy);
        }
        return proxy;
    }
public Map apply(Target key) {
            List metadata = this.contract.parseAndValidatateMetadata(key.type());
            Map result = new LinkedHashMap();

            MethodMetadata md;
            Object buildTemplate;
            for(Iterator var4 = metadata.iterator(); var4.hasNext(); result.put(md.configKey(), this.factory.create(key, md, (Factory)buildTemplate, this.options, this.decoder, this.errorDecoder))) {
                md = (MethodMetadata)var4.next();
                if (!md.formParams().isEmpty() && md.template().bodyTemplate() == null) {
                    buildTemplate = new ReflectiveFeign.BuildFormEncodedTemplateFromArgs(md, this.encoder);
                } else if (md.bodyIndex() != null) {
                    buildTemplate = new ReflectiveFeign.BuildEncodedTemplateFromArgs(md, this.encoder);
                } else {
                    buildTemplate = new ReflectiveFeign.BuildTemplateByResolvingArgs(md);
                }
            }

            return result;
        }

apply()中使用了this.factory.create(...)方法创建了methodhandler,这个method是动态代理中的对接口方法进行处理的核心类对象SynchronousMethodHandler。
下面的代码可以看到,其包含了feign builder所有的成员变量,创建了SynchronousMethodHandler对象

public MethodHandler create(Target target, MethodMetadata md, feign.RequestTemplate.Factory buildTemplateFromArgs, Options options, Decoder decoder, ErrorDecoder errorDecoder) {
            return new SynchronousMethodHandler(target, this.client, this.retryer, this.requestInterceptors, this.logger, this.logLevel, md, buildTemplateFromArgs, options, decoder, errorDecoder, this.decode404);
        }

SynchronousMethodHandler对象的核心方法是invoke(),方法名是不是很眼熟,动态代理invacationhandler的invoke最终会调用到这个invoke进行http请求,以下是invacationhandler调用methodhandler的过程

public Object invoke(Object[] argv) throws Throwable {
        RequestTemplate template = this.buildTemplateFromArgs.create(argv);
        Retryer retryer = this.retryer.clone();

        while(true) {
            try {
                return this.executeAndDecode(template);
            } catch (RetryableException var5) {
                retryer.continueOrPropagate(var5);
                if (this.logLevel != Level.NONE) {
                    this.logger.logRetry(this.metadata.configKey(), this.logLevel);
                }
            }
        }
    }

其中有个executeAndDecode()方法,该方法是通RequestTemplate生成Request请求对象,然后根据用client获取response。

Object executeAndDecode(RequestTemplate template) throws Throwable {
        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, this.options);
            response.toBuilder().request(request).build();
        } catch (IOException var15) {
            if (this.logLevel != Level.NONE) {
                this.logger.logIOException(this.metadata.configKey(), this.logLevel, var15, this.elapsedTime(start));
            }

            throw FeignException.errorExecuting(request, var15);
        }

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

        Response var9;
        try {
            if (this.logLevel != Level.NONE) {
                response = this.logger.logAndRebufferResponse(this.metadata.configKey(), this.logLevel, response, elapsedTime);
                response.toBuilder().request(request).build();
            }

            if (Response.class != this.metadata.returnType()) {
                Object var19;
                if (response.status() >= 200 && response.status() < 300) {
                    if (Void.TYPE == this.metadata.returnType()) {
                        var9 = null;
                        return var9;
                    }

                    var19 = this.decode(response);
                    return var19;
                }

                if (this.decode404 && response.status() == 404 && Void.TYPE != this.metadata.returnType()) {
                    var19 = this.decode(response);
                    return var19;
                }

                throw this.errorDecoder.decode(this.metadata.configKey(), response);
            }

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

                shouldClose = false;
                var9 = response;
                return var9;
            }

            var9 = response;
        } catch (IOException var16) {
            if (this.logLevel != Level.NONE) {
                this.logger.logIOException(this.metadata.configKey(), this.logLevel, var16, elapsedTime);
            }

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

        }

        return var9;
    }

3:Client组件

其中Client组件是一个非常重要的组件,Feign最终发送request请求以及接收response响应,都是由Client组件完成的,其中Client的实现类,只要有Client.Default,该类由HttpURLConnnection实现网络请求,另外还支持HttpClient、Okhttp.

首先来看以下在FeignRibbonClient的自动配置类,FeignRibbonClientAutoConfiguration ,主要在工程启动的时候注入一些bean,其代码如下:

@ConditionalOnClass({ ILoadBalancer.class, Feign.class })
@Configuration
@AutoConfigureBefore(FeignAutoConfiguration.class)
public class FeignRibbonClientAutoConfiguration {

@Bean
	@ConditionalOnMissingBean
	public Client feignClient(CachingSpringLoadBalancerFactory cachingFactory,
			SpringClientFactory clientFactory) {
		return new LoadBalancerFeignClient(new Client.Default(null, null),
				cachingFactory, clientFactory);
	}

}

在缺失配置feignClient的情况下,会自动注入new Client.Default(),跟踪Client.Default()源码,它使用的网络请求框架为HttpURLConnection,代码如下:

@Override
    public Response execute(Request request, Options options) throws IOException {
      HttpURLConnection connection = convertAndSend(request, options);
      return convertResponse(connection).toBuilder().request(request).build();
    }

怎么在feign中使用HttpClient,查看FeignRibbonClientAutoConfiguration的源码

@ConditionalOnClass({ILoadBalancer.class, Feign.class})
@Configuration
@AutoConfigureBefore({FeignAutoConfiguration.class})
@EnableConfigurationProperties({FeignHttpClientProperties.class})
@Import({HttpClientFeignLoadBalancedConfiguration.class, OkHttpFeignLoadBalancedConfiguration.class, DefaultFeignLoadBalancedConfiguration.class})
public class FeignRibbonClientAutoConfiguration {
    public FeignRibbonClientAutoConfiguration() {
    }

    @Bean
    @Primary
    @ConditionalOnMissingClass({"org.springframework.retry.support.RetryTemplate"})
    public CachingSpringLoadBalancerFactory cachingLBClientFactory(SpringClientFactory factory) {
        return new CachingSpringLoadBalancerFactory(factory);
    }

    @Bean
    @Primary
    @ConditionalOnClass(
        name = {"org.springframework.retry.support.RetryTemplate"}
    )
    public CachingSpringLoadBalancerFactory retryabeCachingLBClientFactory(SpringClientFactory factory, LoadBalancedRetryFactory retryFactory) {
        return new CachingSpringLoadBalancerFactory(factory, retryFactory);
    }

    @Bean
    @ConditionalOnMissingBean
    public Options feignRequestOptions() {
        return LoadBalancerFeignClient.DEFAULT_OPTIONS;
    }
}

可以发现@Import有导入各个客户端的配置文件,我们点开httpClient的配置文件HttpClientFeignLoadBalancedConfiguration

@Configuration
@ConditionalOnClass({ApacheHttpClient.class})
@ConditionalOnProperty(
    value = {"feign.httpclient.enabled"},
    matchIfMissing = true
)
class HttpClientFeignLoadBalancedConfiguration {
    ......省略
}

从代码@ConditionalOnClass(ApacheHttpClient.class)注解可知道,只需要在pom文件加上HttpClient的classpath就行了,另外需要在配置文件上加上feign.httpclient.enabled为true,从    @ConditionalOnProperty注解可知,这个可以不写,在默认的情况下就为true.

在pom文件加上:


    com.netflix.feign
    feign-httpclient
    RELEASE

同理,如果想要feign使用Okhttp,则只需要在pom文件上加上feign-okhttp的依赖:


    com.netflix.feign
    feign-okhttp
    RELEASE

总结

总到来说,Feign的源码实现的过程如下:

  • 首先通过@EnableFeignCleints注解开启FeignCleint
  • 根据Feign的规则实现接口,并加@FeignCleint注解
  • 程序启动后,会进行包扫描,扫描所有的@ FeignCleint的注解的类,并将这些信息注入到ioc容器中。
  • 当接口的方法被调用,通过jdk的代理,来生成具体的RequesTemplate
  • RequesTemplate在生成Request
  • Request交给Client去处理,其中Client可以是HttpUrlConnection、HttpClient也可以是Okhttp
  • 最后Client被封装到LoadBalanceClient类,这个类结合类Ribbon做到了负载均衡。

feign的调用时序图如下

å¨è¿éæå¥å¾çæè¿°

 

feign有其他的一些功能如错误处理、重试机制,官方还有hystrix扩展、Rxjava异步扩展、ribon扩展、httpclient的扩展、coder、encoder扩展等。这都归结于feign的良好扩展性。

你可能感兴趣的:(#,springcloud应用)