一、Spring Cloud Feign概念引入
通過前面的隨筆,我們了解如何通過Spring Cloud ribbon進行負責均衡,如何通過Spring Cloud Hystrix進行服務斷路保護,
兩者作為基礎工具類框架應用在各種基礎設施類微服務和業務類微服務中,並且成對存在,那么有沒有更高層的封裝,將兩者的使用
進一步簡化呢? 有! 他就是Spring Cloud Feign。它基於Netflix Feign實現,整合了Spring Cloud Ribbon和Spring Cloud Hystrix,
除了提供兩者強大的功能外,還提供了一種聲明式的Web服務客戶端定義方式。
二、入門實例
我們還是繼續使用前面隨筆中的hello-service服務,這里通過Spring Cloud Feign提供的聲明式服務綁定功能來實現對服務接口的調用。
我們需要新建一個feign-consumer來代替之前的hello-consumer
先給出代碼結構:

代碼實現:
- 新建maven工程(feign-consumer)
- 修改pom文件,引入eureka和feign依賴
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.sam</groupId> <artifactId>feign-consumer</artifactId> <version>0.0.1-SNAPSHOT</version> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>1.5.1.RELEASE</version> </parent> <properties> <javaVersion>1.8</javaVersion> </properties> <dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-dependencies</artifactId> <version>Camden.SR6</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement> <dependencies> <!-- 引入eureka 客戶端依賴 --> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-eureka</artifactId> </dependency> <!-- 引入feign 依賴 --> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-feign</artifactId> </dependency> </dependencies> </project>
- 新建啟動類
/** * 通過@EnableFeignClients來開啟spring cloud feign的支持功能 * */ @EnableFeignClients @EnableDiscoveryClient @SpringBootApplication public class FeiApp { public static void main(String[] args) { SpringApplication.run(FeiApp.class, args); } }
- 新建service接口
/** * 通過@FeignClient注解指定服務名來綁定服務,這里的服務名字不區分大小寫 * 然后再通過@RequestMapping來綁定服務下的rest接口 * */ @FeignClient(name="hello-service") public interface FeignConsumerService{ @RequestMapping("/hello") public void hello(); }
- 新建controller
@RestController public class FeiConsumerController { @Autowired FeignConsumerService consumerService; @RequestMapping("feign-consumer") public String feignConsumer() { consumerService.hello(); return "feign consumer call finished!!!"; } }
- 新建application.properties
server.port=9001 spring.application.name=feign-consumer eureka.client.serviceUrl.defaultZone=http://localhost:1111/eureka
- 測試,
- 啟動服務注冊中心eureka、啟動兩個hello-service(端口號分別為9090和9091),啟動feign-consumer
- 訪問http://localhost:9001/feign-consumer
- 啟動服務注冊中心eureka、啟動兩個hello-service(端口號分別為9090和9091),啟動feign-consumer

並且多次訪問的話,會輪詢調用兩個hello-service服務。
三、參數綁定
在上面的例子中,我們實現的只是一個不帶參數的rest服務綁定,然而現實的業務中不會這么簡單,往往會有各種參數,
這個時候我們做如下事情:
- 如果服務提供方有對象參數(如User對象),那么feign-consumer工程中需要建一個路徑和類名完全一樣的類。
- 然后將服務提供方controller里面的所有方法聲明進行copy(包括前面的@RequestMapping),粘貼到feign-consumer的service接口里面。
四、繼承特性
根據上面參數綁定的做法,我們需要進行很多copy操作,這樣比較麻煩,可以通過繼承的方式進行簡化。
這種實現步驟如下:
1.我們需要新建一個基礎工程hello-service-api,
代碼結構:

代碼實現:
- )新建maven項目hello-service-api
- )修改pom文件
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.sam</groupId> <artifactId>hello-service-api</artifactId> <version>0.0.1-SNAPSHOT</version> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>1.5.1.RELEASE</version> </parent> <properties> <javaVersion>1.8</javaVersion> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> </dependencies> </project>
你會發現其實就是一個普通的spring boot項目。
- )考慮到需要掩飾參數中有對象的情況,我們加個User類
package com.sam.entity; public class User { private String name; private Integer age; public User(String name, Integer age) { super(); this.name = name; this.age = age; } public User() { super(); } public String getName() { return name; } public void setName(String name) { this.name = name; } public Integer getAge() { return age; } public void setAge(Integer age) { this.age = age; } }
User必須要有一個無參數的構造器。
- )新建service接口
/** * 為了同前面那個hello 接口區分開了,我們加了refactor前綴 * */ @RequestMapping("/refactor") public interface HelloService { @RequestMapping("/hello2") public String hello2(); @RequestMapping("/hello3") public User printUser(@RequestBody User user); }
2.重構hello-sevice服務
- )修改pom文件
<!-- 引入 hello-service-api的依賴,以繼承其提供的接口 --> <dependency> <groupId>com.sam</groupId> <artifactId>hello-service-api</artifactId> <version>0.0.1-SNAPSHOT</version> </dependency>
- )HelloController implements HelloService,並實現interface中的接口
@RestController public class HelloController implements HelloService{ Logger logger = LoggerFactory.getLogger(HelloController.class); @Autowired DiscoveryClient discoveryClient; @RequestMapping("/hello") public String hello() throws Exception { ServiceInstance instance = discoveryClient.getLocalServiceInstance(); //打印服務的服務id logger.info("*********" + instance.getServiceId()); return "hello,this is hello-service"; } @Override public String hello2() { return "hello,this is hello2-service"; } @Override public User printUser(@RequestBody User user) { return user; } }
controller實現接口的方法時,不需要@RequestMapping注解,只需要類注解@RestController即可。
3.重構feign-consumer服務
- )修改POM文件
<!-- 引入 hello-service-api的依賴,以繼承其提供的接口 --> <dependency> <groupId>com.sam</groupId> <artifactId>hello-service-api</artifactId> <version>0.0.1-SNAPSHOT</version> </dependency>
- )讓FeignConsumerService extends hello-service-api中的HelloService
/** * 通過@FeignClient注解指定服務名來綁定服務,這里的服務名字不區分大小寫 * 然后再通過@RequestMapping來綁定服務下的rest接口 * */ @FeignClient(name="hello-service") public interface FeignConsumerService extends HelloService{ @RequestMapping("/hello") public void hello(); }
只需要繼承即可。
- )修改controller,追加方法
@RestController public class FeiConsumerController { @Autowired FeignConsumerService consumerService; @RequestMapping("feign-consumer") public String feignConsumer() { consumerService.hello(); return "feign consumer call finished!!!"; } @RequestMapping("feign-consumer-user") public User feignConsumer2(User user) { consumerService.hello2(); return consumerService.printUser(user); } }
4.測試

五、其他
由於Spring Cloud Feign是通過ribbon和hystrix實現具體功能的,因此可以直接通過配置這兩個來實現功能
1.ribbon配置方式:
通過ribbon.<key>=<value>的方式進行全局配置,比如
ribbon.ConnectTimeout=500
ribbon.ReadTimeout=5000
通過<client>.ribbon.<key>=<value>的方式進行指定服務配置,比如
#這里的<client>為@FeignClient(value="hello-service")指定的服務名
hello-service.ribbon.ConnectTimeout=500
hello-service.ribbon.ReadTimeout=500
2.hystrix配置方式:
通過hystrix.command.default.xxx進行全局配置
如:hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds=5000
通過hystrix.command.<commandKey>.xxx進行指定配置,這里的<commandKey>可以為方法名
如:hystrix.command.hello.execution.isolation.thread.timeoutInMilliseconds=5000
3.請求壓縮配置,支持對請求和響應進行GZIP壓縮,以減少通信過程中的性能損耗。
feign.compression.request.enabled=true;
feigan.compression.response.enabled=true;
4.日志配置
Spring Cloud Feign在構建被@FeignClient注解修飾的服務客戶端是,會為每一個客戶端都創建一個feign.Logger實例,我們可以利用該日志對象進行Log分析。
