Spring Cloud Feign 是基於 Netflix Feign 實現的,整合了 Spring Cloud Ribbon 和 Spring Cloud Hystrix,除了提供這二者的強大功能以外,還提供了一種聲明式的 Web 服務客戶端定義方式。java
<?xml version="1.0" encoding="UTF-8"?> <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.example</groupId> <artifactId>feign-consumer</artifactId> <version>0.0.1-SNAPSHOT</version> <packaging>jar</packaging> <name>feign-consumer</name> <description>Demo project for Spring Boot</description> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>1.5.6.RELEASE</version> <relativePath/> <!-- lookup parent from repository --> </parent> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> <java.version>1.8</java.version> <spring-cloud.version>Dalston.SR2</spring-cloud.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-eureka</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-feign</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> </dependencies> <dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-dependencies</artifactId> <version>${spring-cloud.version}</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> </project>
package com.example.demo; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.client.discovery.EnableDiscoveryClient; import org.springframework.cloud.netflix.feign.EnableFeignClients; @SpringBootApplication @EnableDiscoveryClient @EnableFeignClients public class FeignConsumerApplication { public static void main(String[] args) { SpringApplication.run(FeignConsumerApplication.class, args); } }
@FeignClient(value = "hello-service") public interface HelloService { @RequestMapping(value = "/index") String hello(); }
注意:此處服務名不區分大小寫,hello-service 和 HELLO-SERVICE 均可以使用。web
package com.example.demo.controller; import com.example.demo.service.HelloService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RestController; /** * @author lxx * @version V1.0.0 * @date 2017-8-16 */ @RestController public class ConsumerController { @Autowired HelloService helloService; @RequestMapping(value = "feign-consumer", method = RequestMethod.GET) public String helloConsumer(){ return helloService.hello(); } }
spring.application.name=feign-consumer server.port=9991 eureka.client.service-url.defaultZone=http://localhost:8082/eureka/
和Ribbon實現時同樣,啓動 eureka-server 和 兩個hello-service,而後啓動 feign-consumer,發送請求到 http://localhost:9991/feign-consumer,正確返回。與 Ribbon 不一樣的是,經過 Feign 咱們只需定義服務綁定接口,以聲明式的方法,優雅而簡單地實現了服務調用。spring
上面介紹了一個不帶參數的 REST 服務綁定。然而實際系統中的各類業務接口要複雜的多,咱們會在 HTTP 的各個位置傳入不一樣類型的參數,而且在返回請求響應的時候也多是一個複雜的對象結構。apache
在開始介紹 Spring Cloud Feign 的參數綁定以前,先擴展一下服務提供方 hello-service 。增長下面這些接口定義,其中包含帶有 Request 參數的請求、帶有 Header 信息的請求、帶有 RequestBody 的請求以及請求響應體中是一個對象的請求。api
package com.example.demo.web; import org.apache.log4j.Logger; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.cloud.client.ServiceInstance; import org.springframework.cloud.client.discovery.DiscoveryClient; import org.springframework.web.bind.annotation.*; import java.util.Random; /** * @author lxx * @version V1.0.0 * @date 2017-8-9 */ @RestController public class HelloController { private final Logger logger = Logger.getLogger(getClass()); @Autowired private DiscoveryClient client; @RequestMapping(value = "/index") public String index(){ ServiceInstance instance = client.getLocalServiceInstance(); // 讓處理線程等待幾秒鐘 int sleepTime = new Random().nextInt(3000); logger.info("sleepTime:"+sleepTime); try { Thread.sleep(sleepTime); } catch (InterruptedException e) { e.printStackTrace(); } logger.info("/hello:host:"+instance.getHost()+" port:"+instance.getPort() +" service_id:"+instance.getServiceId()); return "hello world!"; } @RequestMapping(value = "/hello1", method = RequestMethod.GET) public String hello1(@RequestParam String name){ return "HELLO " + name; } @RequestMapping(value = "/hello2", method = RequestMethod.GET) public User hello2(@RequestHeader String name, @RequestHeader Integer age){ return new User(name, age); } @RequestMapping(value = "/hello3", method = RequestMethod.POST) public String hello3(@RequestBody User user){ return "HELLO," + user.getName()+","+user.getAge(); } }
User 對象的定義以下,須要注意,這裏必需要有User 的默認構造函數。否則,Spring Cloud Feign 根據 JSON 字符串轉換 User 對象會拋出異常。app
package com.example.demo.web; /** * @author lxx * @version V1.0.0 * @date 2017-8-16 */ public class User { private String name; private Integer age; public User() { } public User(String name, Integer age) { this.name = name; this.age = age; } 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; } @Override public String toString() { return "User{" + "name='" + name + '\'' + ", age=" + age + '}'; } }
完成對 hello-service 的改造以後,在 feign-consumer 應用中實現這些新增的請求的綁定。dom
package com.example.demo.service; import com.example.demo.entity.User; import org.springframework.cloud.netflix.feign.FeignClient; import org.springframework.web.bind.annotation.*; /** * @author lxx * @version V1.0.0 * @date 2017-8-16 */ @FeignClient(value = "hello-service") public interface HelloService { @RequestMapping(value = "/index") String hello(); @RequestMapping(value = "/hello1", method = RequestMethod.GET) String hello1(@RequestParam(value = "name") String name); @RequestMapping(value = "/hello2", method = RequestMethod.GET) User hello2(@RequestParam(value = "name") String name, @RequestHeader(value = "age") Integer age); @RequestMapping(value = "/hello3", method = RequestMethod.POST) String hello3(@RequestBody User user); }
注意:在參數綁定時,@RequestParam、@RequestHeader等能夠指定參數名稱的註解,他們的 value 不能少。在 Spring MVC 中,這些註解會根據參數名來做爲默認值,可是在 Feign 中綁定參數必須經過 value 屬性來指明具體的參數名,否則會拋出異常 IllegalStateException ,value 屬性不能爲空。maven
package com.example.demo.controller; import com.example.demo.entity.User; import com.example.demo.service.HelloService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RestController; /** * @author lxx * @version V1.0.0 * @date 2017-8-16 */ @RestController public class ConsumerController { @Autowired HelloService helloService; @RequestMapping(value = "feign-consumer", method = RequestMethod.GET) public String helloConsumer(){ return helloService.hello(); } @RequestMapping(value = "/feign-consumer2", method = RequestMethod.GET) public String helloConsumer2(){ StringBuilder sb = new StringBuilder(); sb.append(helloService.hello1("didi")).append("\n"); sb.append(helloService.hello2("didi", 18)).append("\n"); sb.append(helloService.hello3(new User("didi", 20))).append("\n"); return sb.toString(); } }
改造以後,啓動服務註冊中心、兩個hello-service服務以及改造以後的 feign-consumer。經過發送請求到 http://localhost:9991/feign-consumer2 ,觸發 HelloService 對新增接口的調用,獲取以下輸出:ide
經過上述的實踐能夠發現,當使用 Spring MVC 的註解綁定服務接口時,幾乎能夠從服務提供方的 Controller 中依靠複製操做,構建出相應的服務客戶端綁定接口。既然存在那麼多複製操做,天然須要考慮這部份內容是否能夠獲得進一步的抽象。在 Spring Cloud Feign 中,針對該問題提供了繼承特性來幫助咱們解決這些複製操做,以進一步減小編碼量。下面,詳細看看如何經過 Spring Cloud Feign 的繼承特性來實現 REST 接口定義的複用。函數
<?xml version="1.0" encoding="UTF-8"?> <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.example</groupId> <artifactId>hello-service-api</artifactId> <version>0.0.1-SNAPSHOT</version> <packaging>jar</packaging> <name>hello-service-api</name> <description>Demo project for Spring Boot</description> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>1.5.6.RELEASE</version> <relativePath/> <!-- lookup parent from repository --> </parent> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> <java.version>1.8</java.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> </project>
package com.example.demo.service; import com.example.demo.entity.User; import org.springframework.web.bind.annotation.*; /** * @author lxx * @version V1.0.0 * @date 2017-8-16 */ @RequestMapping(value = "/refactor") public interface HelloService { @RequestMapping(value = "/hello4", method = RequestMethod.GET) String hello4(@RequestParam(value = "name") String name); @RequestMapping(value = "/hello5", method = RequestMethod.GET) User hello5(@RequestHeader(value = "name") String name, @RequestHeader(value = "age") Integer age); @RequestMapping(value = "/hello6", method = RequestMethod.POST) String hello6(@RequestBody User user); }
由於後續還會經過以前的hello-service 和 feign-consumer 來重構,爲了不接口混淆,在這裏定義 HelloService 時,除了頭部定義了 /refactor 前綴以外,同時將提供服務的三個接口改名爲 hello四、hello五、hello6.
以上操做完成後,執行命令 mvn install 將該模塊構建到本地倉庫。
<dependency> <groupId>com.example</groupId> <artifactId>hello-service-api</artifactId> <version>0.0.1-SNAPSHOT</version> </dependency>
package com.example.demo.web; import entity.*; import entity.User; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestHeader; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; import service.HelloService; /** * @author lxx * @version V1.0.0 * @date 2017-8-16 */ @RestController public class RefactorHelloController implements HelloService { @Override public String hello4(@RequestParam(value = "name") String name) { return "HELLO " + name; } @Override public User hello5(@RequestHeader(value = "name") String name, @RequestHeader(value = "age") Integer age) { return new User(name, age); } @Override public String hello6(@RequestBody User user) { return "HELLO," + user.getName()+","+user.getAge(); } }