之前博文的案例中,我们是通过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值。

1          @RequestMapping(value = "/hello/{username}", method = RequestMethod.GET   )

2          public String sayHello(@PathVariable("username") String username){

3          return "hello " + username;

4      }

上述Eureka服务器和客户端的代码,是复用架构师入门:搭建基本的Eureka架构(从项目里抽取)这篇文章里的代码。

这里我们将在FeignDemo-ServiceCaller项目里,演示通过Feign调用服务的方式。

第一步,在pom.xml里,引入Eureka、Ribbon和Feign的相关包,关键代码如下。其中,是通过第1行到第9行的代码引入Eureka包,通过第10行到第13行的代码引入Ribbon包,通过第14行到第17行的代码引入Feign包。

1    <dependency>
2 <groupId>org.springframework.boot</groupId>
3 <artifactId>spring-boot-starter-web</artifactId>
4 <version>1.5.4.RELEASE</version>
5 </dependency>
6 <dependency>
7 <groupId>org.springframework.cloud</groupId>
8 <artifactId>spring-cloud-starter-eureka</artifactId>
9 </dependency>
10 <dependency>
11 <groupId>org.springframework.cloud</groupId>
12 <artifactId>spring-cloud-starter-ribbon</artifactId>
13 </dependency>
14 <dependency>
15 <groupId>org.springframework.cloud</groupId>
16 <artifactId>spring-cloud-starter-feign</artifactId>
17 </dependency>

第二步,在application.yml里,通过第3行的代码,定义本项目的名字叫callHelloByFeign,通过第5行的代码,指定本项目是工作在8080端口。同时通过第9行的代码,指定本项目是向http://localhost:8888/eureka/ (也就是FeignDemo-Server)这个Eureka服务器注册。

1    spring:
2 application:
3 name: callHelloByFeign
4 server:
5 port: 8080
6 eureka:
7 client:
8 serviceUrl:
9 defaultZone: http://localhost:8888/eureka/

第三步,在启动类里,通过第1行的代码,添加支持Feign的注释,关键代码如下。这样,在启动这个Eureka客户端时,就可以引入Feign支持。

1    @EnableFeignClients
2 @EnableDiscoveryClient
3 @SpringBootApplication
4 public class ServiceCallerApp
5 {
6 public static void main( String[] args )
7 { SpringApplication.run(ServiceCallerApp.class, args); }
8 }

第四步,通过Feign封装客户端调用的细节,外部模块是通过Feign来调用客户端的,这部分的代码是在Controller.java里。

1    省略必要的package和import的代码
2 //通过注解指定待调用的服务名
3 @FeignClient("sayHelloServiceProvider")
4 //在这个接口里,通过Feign封装客户端的调用细节
5 interface FeignClientTool
6 {
7 @RequestMapping(method = RequestMethod.GET, value = "/hello/{name}")
8 String sayHelloInClient(@PathVariable("name") String name);
9 }
10 //Controller是控制器类
11 @RestController
12 public class Controller {
13 @Autowired
14 private FeignClientTool tool;
15 //在callHello方法是,是通过Feign来调用服务
16 @RequestMapping(value = "/callHello", method = RequestMethod.GET)
17 public String callHello(){
18 return tool.sayHelloInClient("Peter");
19 }
20 }

在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,能调用其中的服务,此时,能在浏览里看到“hello Peter”的输出。

第三步,启动FeignDemo-ServiceCaller项目,随后输入http://localhost:8080/callHello,同样能在浏览里看到“hello Peter”的输出。请注意,这里的调用是通过Feign完成的。

2 通过比较其它调用方式,了解Feign的封装性

在之前的代码里,我们是通过如下形式,通过RestTemplate对象来调用服务。

1      RestTemplate template = getRestTemplate();

2             String retVal = template.getForEntity("http://sayHello/hello/Eureka", String.class).getBody();

在第2行的调用中,我们需要指定url以及返回类型等信息。

之前我们还见过基于RestClient对象的调用方式,关键代码如下。

1      RestClient client = (RestClient)ClientFactory.getNamedClient("RibbonDemo");

2       HttpRequest request = HttpRequest.newBuilder().uri(new URI("/hello")).build();

3      HttpResponse response = client.executeWithLoadBalancer(request);

其中是在第1行指定发起调用的RestClient类型的对象,在第2行里指定待调用的目标地址,随后在第3行发起调用。

这两种调用方式有着如下的共同点:调用时,需要详细地知道各种调用参数,比如服务提供者的url,如果有需要通过Ribbon实现负载均衡等机制,也需要在调用时一并指定。

但事实上,这些调用方式的底层细节,应该向服务使用者屏蔽,比如在调用时,无需关注目标url等信息。这就好比某位老板要秘书去订飞机票,作为服务使用者的老板只应当关心调用的结果,比如买到的飞机票是几点开的,该去哪个航站楼登机,至于调用服务的底层细节,比如该到哪个订票网站去买,服务使用者无需知道。

说得更专业些,这叫“解耦合”,即降低服务调动者和服务提供者之间的耦合度,这样的好处是,一旦服务提供者改变了实现细节(没改变服务调用接口),那么服务调用者部分的代码无需改动。

我们再来回顾下通过Feign调用服务的方式。

1      private FeignClientTool tool; //定义Feign类

2      tool.sayHelloInClient("Peter"); //直接调用

第2行是调用服务,但其中,我们看不到任何服务提供者的细节,因为这些都在第1行引用的FeignClientTool类里封装掉了。也就是说,通过基于Feign的调用方式,开发者能真正地做到“面向业务”,而无需过多地关注发起调用的细节。

3 通过注解输出调用日志

在开发和调试阶段,我们希望能看到日志,从而能定位和排查问题。这里,我们将讲述在Feign里输出日志的方法,以便让大家能在通过Feign调用服务时,看到具体的服务信息。

这里我们将改写FeignDemo-ServiceCaller项目。

改动点1:在application.yml文件里,增加如下的代码,以开启Feign客户端的DEBUG日志模式,请注意,这里需要指定完成的路径,就像第3行那样。

1      logging:

2        level:

3          com.controller.FeignClientTool: DEBUG

改动点2:在这个项目的启动类ServiceCallerApp.java里,增加定义日志级别的代码,在第7行的feignLoggerLevel方法里,我们通过第8行的代码,指定了Feign日志级别是FULL。

1    //省略必要的pacakge和import代码
2 @EnableFeignClients
3 @EnableDiscoveryClient
4 @SpringBootApplication
5 public class ServiceCallerApp{
6 @Bean //定义日志级别是FULL
7 Level feignLoggerLevel() {
8 return Level.FULL;
9 }
10 //启动类
11 public static void main( String[] args ) {
12 SpringApplication.run(ServiceCallerApp.class, args);
13 }
14 }

完成后,依次运行Eureka服务器、服务提供者和服务调用者的启动类,随后在浏览器里输入http://localhost:8080/callHello,即能在控制台里看到DEBUG级别的日志,下面给出了部分输出。

1      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

2      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.yml里增加如下的配置,从而压缩请求和返回信息。

1      feign:

2        compression:

3          request:

4            enabled: true

5      feign:

6         compression:

7           response:

8            enabled: true

其中,前4行是压缩请求,而后4行是压缩返回。

第二,可以通过如下的代码,设置哪类请求(或返回)将被压缩,这里我们在第4行里,指定了两类格式的请求将被压缩。

1      feign:

2        compression:

3          request:

4            mime-types: text/xml,application/xml

第三,可以通过如下的代码,指定待压缩请求的最小值,这里是2048,也就是说,超过这个值的request才会被压缩。

1      feign:

2        compression:

3          request:

4            min-request-size:2048

    本文谢绝转载。其它和Spring Cloud相关的博文如下:    

 

Spring Cloud微服务系列文,服务调用框架Feign的更多相关文章

  1. 2018年分享的Spring Cloud 2.x系列文章

    还有几个小时2018年就要过去了,盘点一下小编从做做公众号以来发送了273篇文章,其中包含原创文章90篇,虽然原创的有点少,但是2019年小编将一如既往给大家分享跟多的干货,分享工作中的经验,让大家在 ...

  2. 【多线程】java多线程Completablefuture 详解【在spring cloud微服务之间调用,防止接口超时的应用】【未完成】

    参考地址:https://www.jianshu.com/p/6f3ee90ab7d3 示例: public static void main(String[] args) throws Interr ...

  3. Spring Cloud 微服务六:调用链跟踪Spring cloud sleuth +zipkin

    前言:随着微服务系统的增加,服务之间的调用关系变得会非常复杂,这给运维以及排查问题带来了很大的麻烦,这时服务调用监控就显得非常重要了.spring cloud sleuth实现了对分布式服务的监控解决 ...

  4. Dubbo和Spring Cloud微服务架构比较

    Dubbo 出生于阿里系,是阿里巴巴服务化治理的核心框架,并被广泛应用于中国各互联网公司:只需要通过 Spring 配置的方式即可完成服务化,对于应用无入侵,设计的目的还是服务于自身的业务为主. 微服 ...

  5. 在阿里云容器服务上开发基于Docker的Spring Cloud微服务应用

    本文为阿里云容器服务Spring Cloud应用开发系列文章的第一篇. 一.在阿里云容器服务上开发Spring Cloud微服务应用(本文) 二.部署Spring Cloud应用示例 三.服务发现 四 ...

  6. Dubbo 和 Spring Cloud微服务架构 比较及相关差异

    你真的了解微服务架构吗?听听八年阿里架构师怎样讲述Dubbo和Spring Cloud微服务架构. 微服务架构是互联网很热门的话题,是互联网技术发展的必然结果.它提倡将单一应用程序划分成一组小的服务, ...

  7. 【spring colud】spring cloud微服务项目搭建【spring boot2.0】

    spring cloud微服务项目搭建 =================================== 示例版本: 1.spring boot 2.0版本 2.开发工具 IntellJ IDE ...

  8. 7个点说清楚spring cloud微服务架构

    前言 spring cloud作为当下主流的微服务框架,让我们实现微服务架构简单快捷,spring cloud中各个组件在微服务架构中扮演的角色如下图所示,黑线表示注释说明,蓝线由A指向B,表示B从A ...

  9. spring cloud微服务实践七

    在spring cloud 2.x以后,由于zuul一直停滞在1.x版本,所以spring官方就自己开发了一个项目 Spring Cloud Gateway.作为spring cloud微服务的网关组 ...

随机推荐

  1. python 写日志

    简单配置 日志级别 级别 何时使用 DEBUG 详细信息,典型地调试问题时会感兴趣. INFO 证明事情按预期工作. WARNING 表明发生了一些意外,或者不久的将来会发生问题(如'磁盘满了').软 ...

  2. 根据高德API知道坐标获取详细地址信息

    /** * 根据坐标获取具体地址 * @param coor 坐标字符串 * @return */ public static String getAdd(String coor){ String u ...

  3. java集合之ArrayList,TreeSet和HashMap分析

    java集合是一个重点和难点,如果我们刻意记住所有的用法与区别则是不太现实的,之前一直在使用相关的集合类,但是没有仔细研究区别,现在来把平时使用比较频繁的一些集合做一下分析和总结,目的就是以后在需要使 ...

  4. java Queue中 add/offer,element/peek,remove/poll区别

    转自https://blog.csdn.net/u012050154/article/details/60572567 java Queue中 add/offer,element/peek,remov ...

  5. Dubbo中消费者初始化的过程解析

    首先还是Spring碰到dubbo的标签之后,会使用parseCustomElement解析dubbo标签,使用的解析器是dubbo的DubboBeanDefinitionParser,解析完成之后返 ...

  6. MFC中List box的用法

    首先在头文件中创建对象,CSGrid m_proViewList;//创建表头#pragma region 建立grid表的标题 m_proViewList.AppendColumn(_T(" ...

  7. bzoj 2588 树上主席树

    主席树上树,对于每个节点,继承其父亲的,最后跑f[x]+f[y]-f[lca]-f[fa[lca]] 去重竟然要减一,我竟然不知道?? #include<cstdio> #include& ...

  8. BZOJ_2073_[POI2004]PRZ_状压DP

    BZOJ_2073_[POI2004]PRZ_状压DP 题意: 一只队伍在爬山时碰到了雪崩,他们在逃跑时遇到了一座桥,他们要尽快的过桥. 桥已经很旧了, 所以它不能承受太重的东西. 任何时候队伍在桥上 ...

  9. .NET 创建 classlib时,netcoreapp2.0与netstandard2.0的区别

    最近单位在开发一个新项目,在技术选型的时候,我们决定后台代码全部使用 dot net core来进行开发. 当项目引用公司之前的一个类库的时候,总是出现缺少XX组件的错误,所以我们检查了所有的类库,将 ...

  10. 字典---python

    Python 字典(Dictionary) 字典是另一种可变容器模型,且可存储任意类型对象. 字典的每个键值 key=>value 对用冒号 : 分割,每个键值对之间用逗号 , 分割,整个字典包 ...