文章目录
Feign简介
官网地址:https://github.com/OpenFeign/feign
Feign是一个声明式的Web服务客户端,让编写Web服务客户端变得非常容易,只需 创建一个接口并在接口上添加注解即可。
Feign默认集成了Ribbon,并和Eureka结合,默认实现了负载均衡的效果。
- Feign 采用的是基于接口的注解
- Feign 整合了ribbon,具有负载均衡的能力
- 整合了Hystrix,具有熔断的能力
OpenFeign简介
Spring Cloud提供的Feign增强版,使得Feign支持Spring MVC的注解,开发更容易了。
OpenFeign快速入门
创建一个model工程作为服务消费者,即eureka-feign-client
。
导入依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
在启动类中添加 @EnableFeignClients
注解开启Feign的功能。
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
import org.springframework.cloud.openfeign.EnableFeignClients;
import org.springframework.context.annotation.Bean;
import org.springframework.web.client.RestTemplate;
@SpringBootApplication
@EnableEurekaClient
// 开启Feign的功能
@EnableFeignClients
public class EurekaFeignClientApplication {
public static void main(String[] args) {
SpringApplication.run(EurekaFeignClientApplication.class, args);
}
}
配置文件appication.yml
server:
port: 8083
spring:
application:
name: eureka-feign-client
eureka:
client:
serviceUrl:
defaultZone: http://localhost:8100/eureka/
定义一个Feign接口,在接口上加@FeignClient
注解来声明一个 Feign Client,
其中value
为远程调用其他服务的服务名。
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
// 申明这是一个Feign客户端,并且指明服务id
@FeignClient(value = "eureka-client")
public interface FeignClientInter {
// 这里定义了类似于SpringMVC用法的方法,就可以进行RESTful方式的调用了
@GetMapping(value = "/hello")
String sayHelloFromClient();
}
通过上面定义的Feign客户端来消费服务。
import com.yq.feign.service.FeignClientInter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class HelloController {
@Autowired(required = false)
FeignClientInter feignClientInter;
@GetMapping("/feign")
public String demo() {
return feignClientInter.sayHelloFromClient();
}
}
启动测试
在浏览器上多次访问http://127.0.0.1:8083/feign
,浏览器交替显示,说明集成了Ribbon负载均衡。
OpenFeign超时控制
OpenFeign默认等待1秒钟。超过后报错。
超时配置:
# 设置feign客户端超时时间(OpenFeign默认支持ribbon)
ribbon:
# 指的是建立连接所用的时间,适用于网络状态正常的情况下,两端连接所用的时间
ReadTimeout: 5000
# 指的是建立连接后从服务器读取到可用资源所用的时间
ConnectTimeout: 5000
OpenFeign日志打印功能
日志级别:
配置日志Bean:
import feign.Logger;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class FeignConfig {
@Bean
public Logger.Level feignLoggerLevel() {
// 请求和响应的头信息,请求和响应的正文及元数据
return Logger.Level.FULL;
}
}
YML文件里需要开启日志的Feign客户端:
logging:
level:
# feign日志以什么级别监控哪个接口
com.demo.service.PaymentFeignService: debug
后台查看日志:
Feign的工作原理
Feign 是一个伪 Java Http 客户端 , Feign 不做任何的请求处理。 Feign 通过处理注解生成 Request 模板,从而简化了 Http API 的开发。
开发人员可以使用注解的方式定制 Request API 模板。在发送 HttpRequest 请求之前 , Feign 通过处理注解的方式替换掉 Request 模板中的参数,生成真正的 Request ,并交给 Java Http 客户端去处理 。利用这种方式,开发者只需要关注 Feign 注解模板的开发 ,而不用关注 Http 请求本身,简化了 Http 请求的过程 ,使得 Http 请求变得简单和容易理解。
-
Feign 通过包扫描注入 FeignClient 的 Bean ,该源码在
FeignClientsRegistrar
类中 。首先在程序启动时,会检查是否有@EnableFeignClients 注解,如果有该注解,则开启包扫描,扫描被@FeignClient 注解的接口 。
-
当程序的启动类上有@EnableFeignClients注解。在程序启动后,程序会通过包扫描将有@FeignClient 注解修饰的接口得到一个BeanDefinition ,并将BeanDefinition 注入 IoC 容器中。
org.springframework.cloud.openfeign.FeignClientsRegistrar#registerFeignClients
-
通过 JDK 的代理,当调用 FeignClient 接口里面的方法时,该
方法会被拦截,源码在ReflectiveFeign
类。
-
在
SynchronousMethodHandler
类进行拦截处理,会根据参数生成RequestTemplate 的Http请求模板对象,然后调用 executeAndDecode()方法,该方法通过 RequestTemplate 生成 Request请求对象,然后用 HttpClient 获取 Response ,即通过 HttpClient 进行 Http 请求来获取响应。
最终通过Client的实现类LoadBalancerFeignClient ,即负载均衡客户端,来实现负载均衡请求调用服务的。
在Feign中使用HttpClient和OkHttp
首先查看 FeignRibbonClient 的自动配置类 FeignRibbonClientAutoConfiguration
,@Import 自动注入三个请求框架,从名字上可以看出默认使用最后一个Client。
HttpClientFeignLoadBalancedConfiguration
OkHttpFeignLoadBalancedConfiguration
DefaultFeignLoadBalancedConfiguration
查看DefaultFeignLoadBalancedConfiguration源码,通过new Client.Default()
来创建Client的。
实现类是 Client.Default,,Client.Default 是由 HttpURLConnnection 来实现网络请求的。
继续分析@Import导入的类,FeignClient 还支持 HttpClient 和 OkhHttp 来进行网络请求,那么 Feign 中如何选择网络请求框架呢?
下面继续查看HttpClientFeignLoadBalancedConfiguration
和OkHttpFeignLoadBalancedConfiguration
源码。
从代码@ConditionalOnClass(ApacheHttpClient.class)
和@ConditionalOnClass(OkHttpClient.class)
注解可知道,只需要在 POM 文件加上HttpClient或者OkHttpClient 的 Classpath 即可 。
另外需要在配置文件 application.yml 中配置feign.httpclient.enabled
或者feign.okhttp.enabled
为 true,从 HttpClientFeignLoadBalancedConfiguration类上的@ConditionalOnProperty注解可知,这个配置可以不写,因为在默认的情况下就为true,如果使用OkHttp,必须配置feign.okhttp.enabled
,因为OkHttpFeignLoadBalancedConfiguration类上的@ConditionalOnProperty注解未设置为true。
POM 文件加上 feign-httpclient 的依赖, Feign 就会采用 HttpClient 作为网络请求框架。
<dependency>
<groupId>com.netflix.feign</groupId>
<artifactId>feign-okhttp</artifactId>
<version>RELEASE</version>
</dependency>
POM 文件加上 feign-okhttp 的依赖, Feign 就会采用 OkHttp 作为网络请求框架。
<dependency>
<groupId>com.netflix.feign</groupId>
<artifactId>feign-httpclient</artifactId>
<version>RELEASE</version>
</dependency>
如果容器中三个Client都存在,Feign会默认根据@Import注入类的顺序来选择Client。在LoadBalancerFeignClient
类中可以看出使用了哪个Client。
Feign调用链
OpenFeign与Ribbon区别
Ribbon侧重于做服务调用时的负载均衡,而OpenFeign侧重于面向接口进行服务调用。
在只引入Ribbon依赖的时候,可以使用restTemplate来进行服务调用,大概流程如下:
OpenFeign相比Ribbon在代码实现上是在客户端多了一层接口,之前用Ribbon的时候客户端只有Controller层,通过RestTemplate请求服务端的Controller层。
Openfeign需要在客户端创建一个service层,并创建一个service接口(要用到@FeignClient注解),其方法和服务端的Controller里的方法相对应,之后客户端的Controller调这个接口就行了。
OpenFeign的引入直接砍掉了RestTemplate,客户端Controller在调用服务端时不需要再关注请求的方式、地址以及是forObject还是forEntity,完全面向接口调用,层次结构更加明了,而且OpenFeign自身集成Ribbon,所以默认开启轮询的负载均衡。而且还可以和Hystrix相结合,写一个类实现service接口,其中实现的方法的方法体便是降级或熔断的fallback方法(需要在接口中指定该实现类)。这样结构更清晰,耦合也更低。
总结如下
1、通过 @EnableFeignCleints 注解启动 Feign Starter 组件。
2、Feign Starter 在项目启动过程中注册全局配置,扫描包下所有的 @FeignClient 接口类,并进行注册 IOC 容器。
3、@FeignClient 接口类被注入时,通过 FactoryBean#getObject 返回动态代理类。
4、接口被调用时被动态代理类逻辑拦截,将 @FeignClient 请求信息通过编码器生成 Request(没有带上Header头)。
5、交由 Ribbon 进行负载均衡(默认轮询的方式),挑选出一个健康的 Server 实例。
6、继而通过 Client 携带 Request 调用远端服务返回请求响应。
7、通过解码器生成 Response 返回客户端,将信息流解析成为接口返回数据。