之前博文的案例中, 我们是通过 RestTemplate 来调用服务, 而 Feign 框架则在此基础上做了一层封装, 比如, 可以通过注解等方式来绑定参数, 或者以声明的方式来指定请求返回类型是 JSON. 这种 "再次封装" 能给我们带来的便利有两点, 第一, 开发者无需像使用 RestTemplate 那样过多地关注 HTTP 调用细节, 第二, 在大多数场景里, 某种类型的调用请求会被在多个地方被多次使用, 通过 Feign 能方便地实现这类 "重用".
1 通过案例快速上手 Feign
在 FeignDemo-Server 项目里, 搭建基于 Eureka 的服务器, 该项目的端口号是 8888, 主机名是 localhost, 启动后, 能通过 http://localhost:8888/eureka/ 查看注册到 Eureka 服务器中的诸多服务提供者或调用者的信息.
在 FeignDemo-ServiceProvider 项目的控制器类里, 我们提供了一个 sayHello 方法, 本项目提供服务的端口号是 1111, 对外提供的 application name(服务名)是 sayHelloServiceProvider, 是向 FeignDemo-Server 服务器 (也是 Eureka 服务器) 的 http://localhost:8888/eureka/ 注册服务. 而提供 sayHello 的方法如下所示, 从中, 我们能看到对应的 RequestMapping 值.
- @RequestMapping(value = "/hello/{username}", method = RequestMethod.GET )
- public String sayHello(@PathVariable("username") String username){
- return "hello" + username;
- }
上述 Eureka 服务器和客户端的代码, 是复用架构师入门: 搭建基本的 Eureka 架构 (从项目里抽取) 这篇文章里的代码.
这里我们将在 FeignDemo-ServiceCaller 项目里, 演示通过 Feign 调用服务的方式.
第一步, 在 pom.xml 里, 引入 Eureka,Ribbon 和 Feign 的相关包, 关键代码如下. 其中, 是通过第 1 行到第 9 行的代码引入 Eureka 包, 通过第 10 行到第 13 行的代码引入 Ribbon 包, 通过第 14 行到第 17 行的代码引入 Feign 包.
- <dependency>
- <groupId>org.springframework.boot</groupId>
- <artifactId>spring-boot-starter-web</artifactId>
- <version>1.5.4.RELEASE</version>
- </dependency>
- <dependency>
- <groupId>org.springframework.cloud</groupId>
- <artifactId>spring-cloud-starter-eureka</artifactId>
- </dependency>
- <dependency>
- <groupId>org.springframework.cloud</groupId>
- <artifactId>spring-cloud-starter-ribbon</artifactId>
- </dependency>
- <dependency>
- <groupId>org.springframework.cloud</groupId>
- <artifactId>spring-cloud-starter-feign</artifactId>
- </dependency>
第二步, 在 application.YAML 里, 通过第 3 行的代码, 定义本项目的名字叫 callHelloByFeign, 通过第 5 行的代码, 指定本项目是工作在 8080 端口. 同时通过第 9 行的代码, 指定本项目是向 http://localhost:8888/eureka/ (也就是 FeignDemo-Server)这个 Eureka 服务器注册.
- spring:
- application:
- name: callHelloByFeign
- server:
- port: 8080
- eureka:
- client:
- serviceUrl:
- defaultZone: http://localhost:8888/eureka/
第三步, 在启动类里, 通过第 1 行的代码, 添加支持 Feign 的注释, 关键代码如下. 这样, 在启动这个 Eureka 客户端时, 就可以引入 Feign 支持.
- @EnableFeignClients
- @EnableDiscoveryClient
- @SpringBootApplication
- public class ServiceCallerApp
- {
- public static void main( String[] args )
- { SpringApplication.run(ServiceCallerApp.class, args); }
- }
第四步, 通过 Feign 封装客户端调用的细节, 外部模块是通过 Feign 来调用客户端的, 这部分的代码是在 Controller.java 里.
省略必要的 package 和 import 的代码
- // 通过注解指定待调用的服务名
- @FeignClient("sayHelloServiceProvider")
- // 在这个接口里, 通过 Feign 封装客户端的调用细节
- interface FeignClientTool
- {
- @RequestMapping(method = RequestMethod.GET, value = "/hello/{name}")
- String sayHelloInClient(@PathVariable("name") String name);
- }
- //Controller 是控制器类
- @RestController
- public class Controller {
- @Autowired
- private FeignClientTool tool;
- // 在 callHello 方法是, 是通过 Feign 来调用服务
- @RequestMapping(value = "/callHello", method = RequestMethod.GET)
- public String callHello(){
- return tool.sayHelloInClient("Peter");
- }
- }
在 Controller.java 这个文件, 其实定义了一个接口和一个类. 在第 5 行的 FeignClientTool 接口里, 我们封装了 Feign 的调用业务, 具体来说, 是通过第 3 行的 FeignClient 注解, 指定了该接口会调用 "sayHelloServiceProvider" 服务提供者的服务, 而通过第 8 行的, 则指定了调用该服务提供者中 sayHelloInClient 的方法.
而在第 12 行的 Controller 类里, 先是在第 14 行里, 通过 Autowired 注解, 引入了 FeignClientTool 类型的 tool 类, 随后在第 17 行的 callHello 方法里, 是通过 tool 类的 sayHelloInClient 方法, 调用了服务提供者的相关方法.
也就是说, 在 callHello 方法里, 我们并没有再通过 RestTemplate, 以输入地址和服务名的方式调用服务, 而是通过封装在 FeignClientTool(Feign 接口)里的方法调用服务.
完成上述代码后, 我们可以通过如下的步骤查看运行效果.
第一步, 启动 FeignDemo-Server 项目, 随后输入 http://localhost:8888/ , 能看到注册到 Eureka 服务器里的诸多服务.
第二步, 启动 FeignDemo-ServiceProvider 项目, 随后输入 http://localhost:1111/hello/Peter, http://localhost:1111/hello/Peter, 能调用其中的服务, 此时, 能在浏览里看到 "hello Peter" 的输出.
第三步, 启动 FeignDemo-ServiceCaller 项目, 随后输入 http://localhost:8080/callHello , 同样能在浏览里看到 "hello Peter" 的输出. 请注意, 这里的调用是通过 Feign 完成的.
2 通过比较其它调用方式, 了解 Feign 的封装性
在之前的代码里, 我们是通过如下形式, 通过 RestTemplate 对象来调用服务.
- RestTemplate template = getRestTemplate();
- String retVal = template.getForEntity("http://sayHello/hello/Eureka", String.class).getBody();
在第 2 行的调用中, 我们需要指定 url 以及返回类型等信息.
之前我们还见过基于 RestClient 对象的调用方式, 关键代码如下.
- RestClient client = (RestClient)ClientFactory.getNamedClient("RibbonDemo");
- HttpRequest request = HttpRequest.newBuilder().uri(new URI("/hello")).build();
- HttpResponse response = client.executeWithLoadBalancer(request);
其中是在第 1 行指定发起调用的 RestClient 类型的对象, 在第 2 行里指定待调用的目标地址, 随后在第 3 行发起调用.
这两种调用方式有着如下的共同点: 调用时, 需要详细地知道各种调用参数, 比如服务提供者的 url, 如果有需要通过 Ribbon 实现负载均衡等机制, 也需要在调用时一并指定.
但事实上, 这些调用方式的底层细节, 应该向服务使用者屏蔽, 比如在调用时, 无需关注目标 url 等信息. 这就好比某位老板要秘书去订飞机票, 作为服务使用者的老板只应当关心调用的结果, 比如买到的飞机票是几点开的, 该去哪个航站楼登机, 至于调用服务的底层细节, 比如该到哪个订票网站去买, 服务使用者无需知道.
说得更专业些, 这叫 "解耦合", 即降低服务调动者和服务提供者之间的耦合度, 这样的好处是, 一旦服务提供者改变了实现细节(没改变服务调用接口), 那么服务调用者部分的代码无需改动.
我们再来回顾下通过 Feign 调用服务的方式.
- private FeignClientTool tool; // 定义 Feign 类
- tool.sayHelloInClient("Peter"); // 直接调用
第 2 行是调用服务, 但其中, 我们看不到任何服务提供者的细节, 因为这些都在第 1 行引用的 FeignClientTool 类里封装掉了. 也就是说, 通过基于 Feign 的调用方式, 开发者能真正地做到 "面向业务", 而无需过多地关注发起调用的细节.
3 通过注解输出调用日志
在开发和调试阶段, 我们希望能看到日志, 从而能定位和排查问题. 这里, 我们将讲述在 Feign 里输出日志的方法, 以便让大家能在通过 Feign 调用服务时, 看到具体的服务信息.
这里我们将改写 FeignDemo-ServiceCaller 项目.
改动点 1: 在 application.YAML 文件里, 增加如下的代码, 以开启 Feign 客户端的 DEBUG 日志模式, 请注意, 这里需要指定完成的路径, 就像第 3 行那样.
- logging:
- level:
- com.controller.FeignClientTool: DEBUG
改动点 2: 在这个项目的启动类 ServiceCallerApp.java 里, 增加定义日志级别的代码, 在第 7 行的 feignLoggerLevel 方法里, 我们通过第 8 行的代码, 指定了 Feign 日志级别是 FULL.
- // 省略必要的 pacakge 和 import 代码
- @EnableFeignClients
- @EnableDiscoveryClient
- @SpringBootApplication
- public class ServiceCallerApp{
- @Bean // 定义日志级别是 FULL
- Level feignLoggerLevel() {
- return Level.FULL;
- }
- // 启动类
- public static void main( String[] args ) {
- SpringApplication.run(ServiceCallerApp.class, args);
- }
- }
完成后, 依次运行 Eureka 服务器, 服务提供者和服务调用者的启动类, 随后在浏览器里输入 http://localhost:8080/callHello , 即能在控制台里看到 DEBUG 级别的日志, 下面给出了部分输出.
- 2018-06-17 12:18:27.296 DEBUG 208 --- [rviceProvider-2] com.controller.FeignClientTool : [FeignClientTool#sayHelloInClient] ---> GET http://sayHelloServiceProvider/hello/Peter?name=Peter HTTP/1.1
- 2018-06-17 12:18:27.296 DEBUG 208 --- [rviceProvider-2] com.controller.FeignClientTool : [FeignClientTool#sayHelloInClient] ---> END HTTP (0-byte body)
从第 1 行的输出里, 我们能看到以 GET 的方式向 FeignClientTool 类的 sayHelloInClient 方法发起调用, 从第 2 行的输出里, 能看到调用结束.
在上文里, 我们用的是 FULL 级别的日志, 此外, 还有 NONE,BASIC 和 HEADERS 这三种, 在下表里, 我们将详细讲述各级别日志的输出情况.
日志输出级别 | 描述 |
NONE | 不输出任何日志 |
BASIC | 只输出请求的方法,请求的 URL 和相应的状态码,以及执行的时间 |
HEADERS | 除了会输出 BASIC 级别的日志外,还会记录请求和响应的头信息 |
FULL | 输出所有的和请求和响应相关的日志信息 |
一般情况下, 在调试阶段, 可以把日志级别设置成 FULL, 等上线后, 可以把级别调整为 BASIC, 因为在生产环境上, 过多的日志反而会降低排查和定位问题的效率.
4 压缩请求和返回, 以提升访问效率
在网络传输过程中, 如果我们能降低传输流量, 那么即可提升处理请求的效率. 尤其地, 在一些日常访问量比较高的网络应用中, 如果能降低处理请求 (Request) 和发送返回信息 (Response) 的时间, 那么就能提升本站的吞吐量.
在 Feign 里, 我们一般能通过如下的配置, 来压缩请求和响应.
第一, 可以通过在 application.YAML 里增加如下的配置, 从而压缩请求和返回信息.
- feign:
- compression:
- request:
- enabled: true
- feign:
- compression:
- response:
- enabled: true
其中, 前 4 行是压缩请求, 而后 4 行是压缩返回.
第二, 可以通过如下的代码, 设置哪类请求 (或返回) 将被压缩, 这里我们在第 4 行里, 指定了两类格式的请求将被压缩.
- feign:
- compression:
- request:
- mime-types: text/xml,application/xml
第三, 可以通过如下的代码, 指定待压缩请求的最小值, 这里是 2048, 也就是说, 超过这个值的 request 才会被压缩.
- feign:
- compression:
- request:
- min-request-size:2048
本文谢绝转载. 其它和 Spring Cloud 相关的博文如下:
Spring Cloud 微服务系列文, Hystrix 与 Eureka 的整合
架构师系列文: 通过 Spring Cloud 组件 Hystrix 合并请求
架构师入门: Spring Cloud 系列, Hystrix 与 Eureka 的整合
Hystrix 针对不可用服务的保护机制以及引入缓存
通过案例了解 Hystrix 的各种基本使用方式
Ribbon 整合 Eureka 组件, 以实现负载均衡
Spring Clould 负载均衡重要组件: Ribbon 中重要类的用法
架构师入门: 搭建双注册中心的高可用 Eureka 架构(基于项目实战)
架构师入门: 搭建基本的 Eureka 架构(从项目里抽取)
借助 Maven 入手 Spring Boot 第一个程序
来源: https://www.cnblogs.com/JavaArchitect/p/10448098.html