Feign+FeignClient你有读过这些源码吗?

575 阅读4分钟

写一个简单的Feign

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

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

FeignClient注解源码

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface FeignClient {

@AliasFor("name")
String value() default "";
	
@AliasFor("value")
String name() default "";
	
@AliasFor("value")
String name() default "";
String url() default "";
boolean decode404() default false;

Class<?>[] configuration() default {};
Class<?> fallback() default void.class;

Class<?> fallbackFactory() default void.class;
}

String path() default "";

boolean primary() default true;

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

@Documented表示这个注解会包含在javadoc里。

FeignClient配置

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

@Configuration
public class FeignClientsConfiguration {

...//省略代码

@Bean
	@ConditionalOnMissingBean
	public Decoder feignDecoder() {
		return new ResponseEntityDecoder(new SpringDecoder(this.messageConverters));
	}

	@Bean
	@ConditionalOnMissingBean
	public Encoder feignEncoder() {
		return new SpringEncoder(this.messageConverters);
	}

	@Bean
	@ConditionalOnMissingBean
	public Contract feignContract(ConversionService feignConversionService) {
		return new SpringMvcContract(this.parameterProcessors, feignConversionService);
	}

...//省略代码
}

Feign的工作原理

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

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();
			}
			registerClientConfiguration(registry, name,
					defaultAttrs.get("defaultConfiguration"));
		}
	}

首先可以看到会先检测是否有@EnableFeignClients的这个注解,如果是有这个注解的话,那么就会开启包来进行扫描。扫描被@FeignClient注解接口。源码如上

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

Client组件

我们知道Client它是一个非常重要的一个组件,Feign最终发送的request请求并且接受response的响应,它都是由Client组件来完成的。先看看FeignRibbonClient的自动配置类,主要是在启动的时候注入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(),使用的是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的

@ConditionalOnClass({ ILoadBalancer.class, Feign.class })
@Configuration
@AutoConfigureBefore(FeignAutoConfiguration.class)
public class FeignRibbonClientAutoConfiguration {
...//省略代码

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

		@Autowired(required = false)
		private HttpClient httpClient;

		@Bean
		@ConditionalOnMissingBean(Client.class)
		public Client feignClient(CachingSpringLoadBalancerFactory cachingFactory,
				SpringClientFactory clientFactory) {
			ApacheHttpClient delegate;
			if (this.httpClient != null) {
				delegate = new ApacheHttpClient(this.httpClient);
			}
			else {
				delegate = new ApacheHttpClient();
			}
			return new LoadBalancerFeignClient(delegate, cachingFactory, clientFactory);
		}
	}

...//省略代码
}

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

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

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

	@Override
	public Response execute(Request request, Request.Options options) throws IOException {
		try {
			URI asUri = URI.create(request.url());
			String clientName = asUri.getHost();
			URI uriWithoutHost = cleanUrl(request.url(), clientName);
			FeignLoadBalancer.RibbonRequest ribbonRequest = new FeignLoadBalancer.RibbonRequest(
					this.delegate, request, uriWithoutHost);

			IClientConfig requestConfig = getClientConfig(options, clientName);
			return lbClient(clientName).executeWithLoadBalancer(ribbonRequest,
					requestConfig).toResponse();
		}
		catch (ClientException e) {
			IOException io = findIOException(e);
			if (io != null) {
				throw io;
			}
			throw new RuntimeException(e);
		}
	}

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

  public T executeWithLoadBalancer(final S request, final IClientConfig requestConfig) throws ClientException {
        RequestSpecificRetryHandler handler = getRequestSpecificRetryHandler(request, requestConfig);
        LoadBalancerCommand<T> command = LoadBalancerCommand.<T>builder()
                .withLoadBalancerContext(this)
                .withRetryHandler(handler)
                .withLoadBalancerURI(request.getUri())
                .build();

        try {
            return command.submit(
                new ServerOperation<T>() {
                    @Override
                    public Observable<T> call(Server server) {
                        URI finalUri = reconstructURIWithServer(server, request.getUri());
                        S requestForServer = (S) request.replaceUri(finalUri);
                        try {
                            return Observable.just(AbstractLoadBalancerAwareClient.this.execute(requestForServer, requestConfig));
                        } 
                        catch (Exception e) {
                            return Observable.error(e);
                        }
                    }
                })
                .toBlocking()
                .single();
        } catch (Exception e) {
            Throwable t = e.getCause();
            if (t instanceof ClientException) {
                throw (ClientException) t;
            } else {
                throw new ClientException(e);
            }
        }
        
    }	

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

总结

通过@EnableFeignCleints注解开启FeignCleint

根据Feign的规则实现接口,并加上@FeignCleint注解

程序启动后,会进行扫描,所有注解的@FeignCleint的类,并将这些信息注入到ioc容器中。

RequesTemplate在生成Request

Request交给Client去处理,其中Client可以是HttpUrlConnection、HttpClient也可以是Okhttp

最后Client被封装到LoadBalanceClient类,这个类结合类Ribbon做到了负载均衡。