Feign 是受到 Retrofit,JAXRS-2.0 和 WebSocket 的影响,它是一个 jav 的到 http 客户端绑定的开源项目。 Feign 的主要目标是将 Java Http 客户端变得简单。Feign 的源码地址:https://github.com/OpenFeign/feign
在我之前的博文有写到如何用 Feign 去消费服务,文章地址:http://blog.csdn.net/forezp/article/details/69808079 。
现在来简单的实现一个 Feign 客户端,首先通过 @FeignClient,客户端,其中 value 为调用其他服务的名称,FeignConfig.class 为 FeignClient 的配置文件,代码如下:
- @FeignClient(value="service-hi",configuration = FeignConfig.class)public interfaceSchedualServiceHi {
- @GetMapping(value="/hi")
- String sayHiFromClientOne(@RequestParam(value="name") String name);
- }
其自定义配置文件如下,当然也可以不写配置文件,用默认的即可:
- @Configuration
- public class FeignConfig{
- @Bean
- publicRetryerfeignRetryer() {return newRetryer.Default(100, SECONDS.toMillis(1),5);
- }
- }
查看 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 "";booleandecode404()default false;
- Class[] configuration()default{};
- Class fallback()default void.class;
- Class fallbackFactory()default void.class;
- }
- String path()default "";booleanprimary()default true;
FeignClient 注解被 @Target(ElementType.TYPE) 修饰,表示 FeignClient 注解的作用目标在接口上; @Retention(RetentionPolicy.RUNTIME),注解会在 class 字节码文件中存在,在运行时可以通过反射获取到;@Documented 表示该注解将被包含在 javadoc 中。
feign 用于声明具有该接口的 REST 客户端的接口的注释应该是创建(例如用于自动连接到另一个组件。 如果功能区可用,那将是 用于负载平衡后端请求,并且可以配置负载平衡器 使用与伪装客户端相同名称(即值)@RibbonClient 。
其中 value() 和 name() 一样,是被调用的 service 的名称。 url(), 直接填写硬编码的 url,decode404() 即 404 是否被解码,还是抛异常;configuration(),标明 FeignClient 的配置类,默认的配置类为 FeignClientsConfiguration 类,可以覆盖 Decoder、Encoder 和 Contract 等信息,进行自定义配置。fallback(), 填写熔断器的信息类。
默认的配置类为 FeignClientsConfiguration,这个类在 spring-cloud-netflix-core 的 jar 包下,打开这个类,可以发现它是一个配置类,注入了很多的相关配置的 bean,包括 feignRetryer、FeignLoggerFactory、FormattingConversionService 等, 其中还包括了 Decoder、Encoder、Contract,如果这三个 bean 在没有注入的情况下,会自动注入默认的配置。
代码如下:
- @Configuration
- public class FeignClientsConfiguration{...//省略代码
- @Bean
- @ConditionalOnMissingBean
- publicDecoderfeignDecoder() {return newResponseEntityDecoder(newSpringDecoder(this.messageConverters));
- }@Bean
- @ConditionalOnMissingBean
- publicEncoderfeignEncoder() {return newSpringEncoder(this.messageConverters);
- }@Bean
- @ConditionalOnMissingBean
- publicContractfeignContract(ConversionService feignConversionService) {return newSpringMvcContract(this.parameterProcessors, feignConversionService);
- }
- ...//省略代码}
重写配置:
你可以重写 FeignClientsConfiguration 中的 bean,从而达到自定义配置的目的,比如 FeignClientsConfiguration 的默认重试次数为 Retryer.NEVER_RETRY,即不重试,那么希望做到重写,写个配置文件,注入 feignRetryer 的 bean, 代码如下:
- @Configuration
- public class FeignConfig{
- @Bean
- publicRetryerfeignRetryer() {return newRetryer.Default(100, SECONDS.toMillis(1),5);
- }
- }
在上述代码更改了该 FeignClient 的重试次数,重试间隔为 100ms,最大重试时间为 1s, 重试次数为 5 次。
feign 是一个伪客户端,即它不做任何的请求处理。Feign 通过处理注解生成 request,从而实现简化 HTTP API 开发的目的,即开发人员可以使用注解的方式定制 request api 模板,在发送 http request 请求之前,feign 通过处理注解的方式替换掉 request 模板中的参数,这种实现方式显得更为直接、可理解。
通过包扫描注入 FeignClient 的 bean,该源码在 FeignClientsRegistrar 类: 首先在启动配置上检查是否有 @EnableFeignClients 注解,如果有该注解,则开启包扫描,扫描被 @FeignClient 注解接口。代码如下:
- private void registerDefaultConfiguration(AnnotationMetadata metadata,
- BeanDefinitionRegistry registry) {
- Map 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"));
- }
- }
程序启动后通过包扫描,当类有 @FeignClient 注解,将注解的信息取出,连同类名一起取出,赋给 BeanDefinitionBuilder,然后根据 BeanDefinitionBuilder 得到 beanDefinition,最后 beanDefinition 式注入到 ioc 容器中,源码如下:
- public void registerFeignClients(AnnotationMetadata metadata,
- BeanDefinitionRegistry registry) {
- ClassPathScanningCandidateComponentProvider scanner = getScanner();scanner.setResourceLoader(this.resourceLoader);
- Set basePackages;Map attrs = metadata
- .getAnnotationAttributes(EnableFeignClients.class.getName());AnnotationTypeFilter annotationTypeFilter = new AnnotationTypeFilter(
- FeignClient.class);final Class[] clients = attrs == null ? null
- : (Class[]) attrs.get("clients");if (clients == null || clients.length==0) {
- scanner.addIncludeFilter(annotationTypeFilter);basePackages = getBasePackages(metadata);}
- else {
- finalSet clientClasses = new HashSet<>();basePackages = new HashSet<>();for (Class clazz : clients) {
- basePackages.add(ClassUtils.getPackageName(clazz));clientClasses.add(clazz.getCanonicalName());}
- AbstractClassTestingTypeFilter filter = new AbstractClassTestingTypeFilter() {
- @Override
- protected boolean match(ClassMetadata metadata) {
- String cleaned = metadata.getClassName().replaceAll("\\$",".");return clientClasses.contains(cleaned);}
- };scanner.addIncludeFilter(
- new AllTypeFilter(Arrays.asList(filter, annotationTypeFilter)));}
- for (String basePackage : basePackages) {SetcandidateComponents = scanner.findCandidateComponents(basePackage);for (BeanDefinition candidateComponent : candidateComponents) {
- if (candidateComponent instanceof AnnotatedBeanDefinition) {
- // verify annotated class is an interface
- AnnotatedBeanDefinition beanDefinition = (AnnotatedBeanDefinition) candidateComponent;AnnotationMetadata annotationMetadata = beanDefinition.getMetadata();Assert.isTrue(annotationMetadata.isInterface(),"@FeignClient can only be specified on an interface");Map attributes = annotationMetadata
- .getAnnotationAttributes(
- FeignClient.class.getCanonicalName());String name = getClientName(attributes);registerClientConfiguration(registry, name,
- attributes.get("configuration"));registerFeignClient(registry, annotationMetadata, attributes);}
- }
- }
- }
- private void registerFeignClient(BeanDefinitionRegistry registry,
- AnnotationMetadata annotationMetadata, Map attributes) {
- String className = annotationMetadata.getClassName();BeanDefinitionBuilder definition = BeanDefinitionBuilder.genericBeanDefinition(FeignClientFactoryBean.class);validate(attributes);definition.addPropertyValue("url", getUrl(attributes));definition.addPropertyValue("path", getPath(attributes));String name = 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(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);String alias = name +"FeignClient";AbstractBeanDefinition beanDefinition = definition.getBeanDefinition();boolean primary = (Boolean)attributes.get("primary"); // has a default, won't be nullbeanDefinition.setPrimary(primary);String qualifier = getQualifier(attributes);if (StringUtils.hasText(qualifier)) {
- alias = qualifier;}
- BeanDefinitionHolder holder = new BeanDefinitionHolder(beanDefinition, className,
- new String[] { alias });BeanDefinitionReaderUtils.registerBeanDefinition(holder, registry);}
注入 bean 之后,通过 jdk 的代理,当请求 Feign Client 的方法时会被拦截,代码在 ReflectiveFeign 类,代码如下:
- public T newInstance(Target target) {
- Map nameToHandler = targetToHandlersByName.apply(target);
- Map methodToHandler = new LinkedHashMap();
- List defaultMethodHandlers = new LinkedList();
- 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 handler = factory.create(target, methodToHandler);
- T proxy = (T) Proxy.newProxyInstance(target.type().getClassLoader(), new Class[] {
- target.type()
- },
- handler);
- for (DefaultMethodHandler defaultMethodHandler: defaultMethodHandlers) {
- defaultMethodHandler.bindTo(proxy);
- }
- return proxy;
- }
在 SynchronousMethodHandler 类进行拦截处理,当被 FeignClient 的方法被拦截会根据参数生成 RequestTemplate 对象,该对象就是 http 请求的模板,代码如下:
- @Override
- publicObjectinvoke(Object[] argv)throwsThrowable {
- RequestTemplate template = buildTemplateFromArgs.create(argv);
- Retryer retryer =this.retryer.clone();while(true) {try{returnexecuteAndDecode(template);
- }catch(RetryableException e) {
- retryer.continueOrPropagate(e);if(logLevel != Logger.Level.NONE) {
- logger.logRetry(metadata.configKey(), logLevel);
- }continue;
- }
- }
- }
其中有个 executeAndDecode() 方法,该方法是通 RequestTemplate 生成 Request 请求对象,然后根据用 client 获取 response。
- Object executeAndDecode(RequestTemplate template) throws Throwable {
- Request request = targetRequest(template);...//省略代码
- response = client.execute(request, options);...//省略代码
- }
其中 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
- publicClientfeignClient(CachingSpringLoadBalancerFactory cachingFactory,
- SpringClientFactory clientFactory) {return newLoadBalancerFeignClient(newClient.Default(null,null),
- cachingFactory, clientFactory);
- }
- }
在缺失配置 feignClient 的情况下,会自动注入 new Client.Default(), 跟踪 Client.Default() 源码,它使用的网络请求框架为 HttpURLConnection,代码如下:
- @Overridepublic 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)public classFeignRibbonClientAutoConfiguration {
- ...//省略代码
- @Configuration
- @ConditionalOnClass(ApacheHttpClient.class)@ConditionalOnProperty(value ="feign.httpclient.enabled", matchIfMissing =true)protected static classHttpClientFeignLoadBalancedConfiguration {@Autowired(required =false)privateHttpClient httpClient;@Bean
- @ConditionalOnMissingBean(Client.class)publicClient feignClient(CachingSpringLoadBalancerFactory cachingFactory,
- SpringClientFactory clientFactory) {
- ApacheHttpClientdelegate;if(this.httpClient !=null) {delegate=newApacheHttpClient(this.httpClient);
- }else{delegate=newApacheHttpClient();
- }return newLoadBalancerFeignClient(delegate, cachingFactory, clientFactory);
- }
- }
- ...//省略代码}
从代码 @ConditionalOnClass(ApacheHttpClient.class) 注解可知道,只需要在 pom 文件加上 HttpClient 的 classpath 就行了,另外需要在配置文件上加上 feign.httpclient.enabled 为 true,从 @ConditionalOnProperty 注解可知,这个可以不写,在默认的情况下就为 true.
在 pom 文件加上:
- <dependency>
- <groupId>
- com.netflix.feign
- </groupId>
- <artifactId>
- feign-httpclient
- </artifactId>
- <version>
- RELEASE
- </version>
- </dependency>
同理,如果想要 feign 使用 Okhttp,则只需要在 pom 文件上加上 feign-okhttp 的依赖:
- <dependency>
- <groupId>
- com.netflix.feign
- </groupId>
- <artifactId>
- feign-okhttp
- </artifactId>
- <version>
- RELEASE
- </version>
- </dependency>
通过上述的 FeignRibbonClientAutoConfiguration 类配置 Client 的类型 (httpurlconnection,okhttp 和 httpclient) 时候,可知最终向容器注入的是 LoadBalancerFeignClient,即负载均衡客户端。现在来看下 LoadBalancerFeignClient 的代码:
- @Override
- publicResponseexecute(Request request, Request.Options options)throwsIOException {try{
- URI asUri = URI.create(request.url());
- String clientName = asUri.getHost();
- URI uriWithoutHost = cleanUrl(request.url(), clientName);
- FeignLoadBalancer.RibbonRequest ribbonRequest =newFeignLoadBalancer.RibbonRequest(this.delegate, request, uriWithoutHost);
- IClientConfig requestConfig = getClientConfig(options, clientName);returnlbClient(clientName).executeWithLoadBalancer(ribbonRequest,
- requestConfig).toResponse();
- }catch(ClientException e) {
- IOException io = findIOException(e);if(io !=null) {throwio;
- }throw newRuntimeException(e);
- }
- }
其中有个 executeWithLoadBalancer() 方法,即通过负载均衡的方式请求。
- publicTexecuteWithLoadBalancer(finalS request,finalIClientConfig requestConfig)throwsClientException {
- RequestSpecificRetryHandler handler = getRequestSpecificRetryHandler(request, requestConfig);
- LoadBalancerCommand command = LoadBalancerCommand.builder()
- .withLoadBalancerContext(this)
- .withRetryHandler(handler)
- .withLoadBalancerURI(request.getUri())
- .build();try{returncommand.submit(newServerOperation() {
- @Override
- publicObservable call(Server server) {
- URI finalUri = reconstructURIWithServer(server, request.getUri());
- S requestForServer = (S) request.replaceUri(finalUri);try{returnObservable.just(AbstractLoadBalancerAwareClient.this.execute(requestForServer, requestConfig));
- }catch(Exception e) {returnObservable.error(e);
- }
- }
- })
- .toBlocking()
- .single();
- }catch(Exception e) {
- Throwable t = e.getCause();if(tinstanceofClientException) {throw(ClientException) t;
- }else{throw newClientException(e);
- }
- }
- }
其中服务在 submit() 方法上,点击 submit 进入具体的方法, 这个方法是 LoadBalancerCommand 的方法:
- Observable o =
- (server==nullselectServer() : Observable.just(server))
- .concatMap(newFunc1>() {
- @Override
- // Called for each server being selected
- publicObservable call(Server server) {
- context.setServer(server);
- }}
上述代码中有个 selectServe(),该方法是选择服务的进行负载均衡的方法,代码如下:
- privateObservable<Server> selectServer() {
- return Observable.create(newOnSubscribe<Server>() {
- @Overridepublicvoidcall(SubscriberServer>next) {
- try {Server server= loadBalancerContext.getServerFromLoadBalancer(loadBalancerURI, loadBalancerKey);next.onNext(server);next.onCompleted();
- } catch (Exception e) {next.onError(e);
- }
- }
- });
- }
最终负载均衡交给 loadBalancerContext 来处理,即之前讲述的 Ribbon,在这里不再重复。
总到来说,Feign 的源码实现的过程如下:
https://github.com/OpenFeign/feign
https://blog.de-swaef.eu/the-netflix-stack-using-spring-boot-part-3-feign/
来源: http://blog.csdn.net/forezp/article/details/73480304