Java Fegin

一个简单的Feign

现在来实现一个简单的Feign,首先是要使用注解@FeignClient

  1. @FeignClient(value = "xdclass-hi",configuration = FeignConfig.class)
  2. public interface SchedualServiceHi {
  3. @GetMapping(value = "/hi")
  4. String sayHiFromClientOne(@RequestParam(value = "name") String name);
  5. }

FeignClient注解源码

  1. @Target(ElementType.TYPE)
  2. @Retention(RetentionPolicy.RUNTIME)
  3. @Documented
  4. public @interface FeignClient {
  5. @AliasFor("name")
  6. String value() default "";
  7. @AliasFor("value")
  8. String name() default "";
  9. @AliasFor("value")
  10. String name() default "";
  11. String url() default "";
  12. boolean decode404() default false;
  13. Class<?>[] configuration() default {};
  14. Class<?> fallback() default void.class;
  15. Class<?> fallbackFactory() default void.class;
  16. }
  17. String path() default "";
  18. boolean primary() default true;

FeignClient注解被@Target(ElementType.TYPE)修饰,表示FeignClient的注解作用在所对应的目标接口上。@Retention这个注解会在class字节码文件里,在运行的时候通过反射就可以获取到。
@Documented表示这个注解会包含在javadoc里。

FeignClient配置

默认的配置类是叫FeignClientsConfiguration,这一个类它是在netflix-core的jar包下,可以发现它是一个配置类,有很多相关的配置,如:bean,feignRetryerFeignLoggerFactory等等。

  1. @Configuration
  2. public class FeignClientsConfiguration {
  3. ...//省略代码
  4. @Bean
  5. @ConditionalOnMissingBean
  6. public Decoder feignDecoder() {
  7. return new ResponseEntityDecoder(new SpringDecoder(this.messageConverters));
  8. }
  9. @Bean
  10. @ConditionalOnMissingBean
  11. public Encoder feignEncoder() {
  12. return new SpringEncoder(this.messageConverters);
  13. }
  14. @Bean
  15. @ConditionalOnMissingBean
  16. public Contract feignContract(ConversionService feignConversionService) {
  17. return new SpringMvcContract(this.parameterProcessors, feignConversionService);
  18. }
  19. ...//省略代码
  20. }

Feign的工作原理

feign它是一个伪的客户端,就是它不会对任何的请求进行处理。Feign是通过处理注解这种来生成request的,从而来简化掉HTTP API开发的目的,就是开发可以用注解的这种方式来制定request api模板。在进行发送请求之前,feign就开始通过处理注解的这种方式来替换request的参数,这种方式就更加直接、易理解。

  1. private void registerDefaultConfiguration(AnnotationMetadata metadata,
  2. BeanDefinitionRegistry registry) {
  3. Map<String, Object> defaultAttrs = metadata
  4. .getAnnotationAttributes(EnableFeignClients.class.getName(), true);
  5. if (defaultAttrs != null && defaultAttrs.containsKey("defaultConfiguration")) {
  6. String name;
  7. if (metadata.hasEnclosingClass()) {
  8. name = "default." + metadata.getEnclosingClassName();
  9. }
  10. else {
  11. name = "default." + metadata.getClassName();
  12. }
  13. registerClientConfiguration(registry, name,
  14. defaultAttrs.get("defaultConfiguration"));
  15. }
  16. }

首先可以看到会先检测是否有@EnableFeignClients的这个注解,如果是有这个注解的话,那么就会开启包来进行扫描。扫描被@FeignClient注解接口。源码如上
接着程序启动之后就通过包来进行扫描,如果这个类是有@FeignClient注解的话,那么就会把注解的信息取出来,连同类名也一起取出了,并且给BeanDefinitionBuilder,然后得到了beanDefinition,最后把beanDefinition注入到ioc容器里。

Client组件

Client它是一个非常重要的一个组件,Feign最终发送的request请求并且接受response的响应,它都是由Client组件来完成的。先看看FeignRibbonClient的自动配置类,主要是在启动的时候注入bean。

  1. @ConditionalOnClass({ ILoadBalancer.class, Feign.class })
  2. @Configuration
  3. @AutoConfigureBefore(FeignAutoConfiguration.class)
  4. public class FeignRibbonClientAutoConfiguration {
  5. @Bean
  6. @ConditionalOnMissingBean
  7. public Client feignClient(CachingSpringLoadBalancerFactory cachingFactory,
  8. SpringClientFactory clientFactory) {
  9. return new LoadBalancerFeignClient(new Client.Default(null, null),
  10. cachingFactory, clientFactory);
  11. }
  12. }

如果少了配置feignClient的情况,那么将会自动的注入new Client.Default(),使用的是HttpURLConnection

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

来看看feign是怎么使用HttpClient的

  1. @ConditionalOnClass({ ILoadBalancer.class, Feign.class })
  2. @Configuration
  3. @AutoConfigureBefore(FeignAutoConfiguration.class)
  4. public class FeignRibbonClientAutoConfiguration {
  5. ...//省略代码
  6. @Configuration
  7. @ConditionalOnClass(ApacheHttpClient.class)
  8. @ConditionalOnProperty(value = "feign.httpclient.enabled", matchIfMissing = true)
  9. protected static class HttpClientFeignLoadBalancedConfiguration {
  10. @Autowired(required = false)
  11. private HttpClient httpClient;
  12. @Bean
  13. @ConditionalOnMissingBean(Client.class)
  14. public Client feignClient(CachingSpringLoadBalancerFactory cachingFactory,
  15. SpringClientFactory clientFactory) {
  16. ApacheHttpClient delegate;
  17. if (this.httpClient != null) {
  18. delegate = new ApacheHttpClient(this.httpClient);
  19. }
  20. else {
  21. delegate = new ApacheHttpClient();
  22. }
  23. return new LoadBalancerFeignClient(delegate, cachingFactory, clientFactory);
  24. }
  25. }
  26. ...//省略代码
  27. }

从代码可以知道,只需要在pom文件加上HttpClient的calsspath就可以了,也需要加上feign.httpclient.enabled是true。

Feign它的负载均衡是怎么实现的?

通过上述的FeignRibbonClientAutoConfiguration类配置Client的类型(httpurlconnection,okhttp和httpclient)时候,可知最终向容器注入的是LoadBalancerFeignClient,即负载均衡客户端。

  1. @Override
  2. public Response execute(Request request, Request.Options options) throws IOException {
  3. try {
  4. URI asUri = URI.create(request.url());
  5. String clientName = asUri.getHost();
  6. URI uriWithoutHost = cleanUrl(request.url(), clientName);
  7. FeignLoadBalancer.RibbonRequest ribbonRequest = new FeignLoadBalancer.RibbonRequest(
  8. this.delegate, request, uriWithoutHost);
  9. IClientConfig requestConfig = getClientConfig(options, clientName);
  10. return lbClient(clientName).executeWithLoadBalancer(ribbonRequest,
  11. requestConfig).toResponse();
  12. }
  13. catch (ClientException e) {
  14. IOException io = findIOException(e);
  15. if (io != null) {
  16. throw io;
  17. }
  18. throw new RuntimeException(e);
  19. }
  20. }

里面有个executeWithLoadBalancer的方法,这个就是通过负载均衡的方式来请求

  1. public T executeWithLoadBalancer(final S request, final IClientConfig requestConfig) throws ClientException {
  2. RequestSpecificRetryHandler handler = getRequestSpecificRetryHandler(request, requestConfig);
  3. LoadBalancerCommand<T> command = LoadBalancerCommand.<T>builder()
  4. .withLoadBalancerContext(this)
  5. .withRetryHandler(handler)
  6. .withLoadBalancerURI(request.getUri())
  7. .build();
  8. try {
  9. return command.submit(
  10. new ServerOperation<T>() {
  11. @Override
  12. public Observable<T> call(Server server) {
  13. URI finalUri = reconstructURIWithServer(server, request.getUri());
  14. S requestForServer = (S) request.replaceUri(finalUri);
  15. try {
  16. return Observable.just(AbstractLoadBalancerAwareClient.this.execute(requestForServer, requestConfig));
  17. }
  18. catch (Exception e) {
  19. return Observable.error(e);
  20. }
  21. }
  22. })
  23. .toBlocking()
  24. .single();
  25. } catch (Exception e) {
  26. Throwable t = e.getCause();
  27. if (t instanceof ClientException) {
  28. throw (ClientException) t;
  29. } else {
  30. throw new ClientException(e);
  31. }
  32. }
  33. }

最终负载均衡交给loadBalancerContext来处理,即之前讲述的Ribbon。

总结

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