在實際開發中,對於服務依賴的調用可能不止一處,往往一個接口會被多處調用,所以我們通常會針對各個微服務自行封裝一些客戶端類來包裝這些依賴服務的調用,Spring Cloud Feign 在此基礎上做了進一步的封裝,由他來幫助我們定義和實現依賴服務接口的定義,我們只需要創建一個接口並用注解的方式來配置他,即可完成對服務提供方的接口綁定,簡化了在使用 Spring Cloud Ribbon 時自行封裝服務調用客戶端的開發量。
快速入門
-
首先創建一個 Spring Cloud 的基礎工程,並增加 spring-cloud-starter-eureka 依賴 和 spring-cloud-starter-feign 依賴,示例代碼如下:
<?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>org.lixue</groupId>
<artifactId>eureka-feign-consumer</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>
<name>eureka-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.SR3</spring-cloud.version>
</properties>
<dependencies>
<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>
-
創建應用主類 EurekaFeignConsumerApplication 並通過 @EnableFeignClients 注解開啟 Spring Cloud Feign 功能,使用 @EnableDiscoveryClient 注解開啟 Eureka的服務發現,代碼如下:
@EnableFeignClients
@EnableDiscoveryClient
@SpringBootApplication
public class EurekaFeignConsumerApplication {
public static void main(String[] args) {
SpringApplication.run(EurekaFeignConsumerApplication.class, args);
}
}
-
定義 HelloWorldService 接口,通過 @FeignClient 注解指定服務名來綁定服務,然后再使用Spring MVC 的注解來綁定具體該服務提供的 REST 接口,代碼如下:
@FeignClient ("ORG.LIXUE.HELLOWORLD")
public interface HelloWorldService {
@RequestMapping ("/hi")
String hi();
}
-
在需要調用服務的位置,使用 @Autowired 直接注入 HelloWorldService 實例,並使用該接口實例調用方法 hi 來完成 /hi 接口的調用,示例代碼如下:
@RestController
public class FeignConsumerController {
@Autowired
HelloWorldService helloWorldService;
@RequestMapping ("/hi")
public String hi() {
return helloWorldService.hi();
}
}
-
在 application.yml 中需要指定服務注冊中心,並定義自身的服務名和端口,示例如下:
server:
port: 9200
eureka:
client:
service-url:
defaultZone: http://eurekaserver2:9002/eureka,http://eurekaserver1:9001/eureka
spring:
application:
name: eureka-feign-consumer
- 啟動服務注冊中心以及二個ORG.LIXUE.HELLOWORLD服務,然后啟動 eureka-feign-consumer ,此時發送幾次 GET 請求到 http://localhost:9200/hi ,可以正確的返回 Hello World hi 9100 或者 Hello World hi 9101 ,可以看到Feign實現的消費者,依然是利用 Ribbon 維護了針對 ORG.LIXUE.HELLOWORLD服務列表信息,並且通過輪詢實現了客戶端負載均衡。
參數綁定
在入門示例中我們實現的是一個不帶參數的 REST 服務綁定,然而現實系統中的各種業務接口要比他復雜很多,我們會在HTTP的各個位置傳入各種不同類型的參數,並且在返回請求響應的時候也可能是一個復雜對象結構,因此我們需要使用 Feign 來對不同形式的參數進行綁定
-
@RequestParam 注解:常用來處理簡單類型的綁定,通過Request.getParameter() 獲取的String可直接轉換為簡單類型的情況( String--> 簡單類型的轉換操作由ConversionService配置的轉換器來完成);因為使用request.getParameter()方式獲取參數,所以可以處理get 方式中queryString的值,也可以處理post方式中 body data的值;用來處理Content-Type: 為 application/x-www-form-urlencoded編碼的內容,提交方式GET、POST;該注解有兩個屬性: value、required; value用來指定要傳入值的id名稱,required用來指示參數是否必須綁定
@RequestMapping (value = "/hi1", method = RequestMethod.GET)
public String hi(@RequestParam ("name") String name) {
return "Hello World hi " + port + " name " + name;
}
-
@CookieValue 注解:可以把Request header中關於cookie的值綁定到方法的參數上
@RequestMapping("/displayHeaderInfo.do")
public void displayHeaderInfo(@CookieValue("JSESSIONID") String cookie) {
//...
}
-
@RequestBody 注解:用於讀取Request請求的body部分數據,使用系統默認配置的HttpMessageConverter進行解析,然后把相應的數據綁定到要返回的對象上
@RequestMapping (value = "/hi2", method = RequestMethod.POST)
public String hi(@RequestBody User user) {
return "Hello World hi " + port + "\tUser=" + user;
}
注意:User 類必須有默認構造函數
-
@RequestHeader 注解:可以把Request請求header部分的值綁定到方法的參數上
@RequestMapping("/displayHeaderInfo.do")
public void displayHeaderInfo(@RequestHeader("Accept-Encoding") String encoding,
@RequestHeader("Keep-Alive") long keepAlive) {
//...
}
-
@PathVariable 注解:當使用@RequestMapping URI template 樣式映射時, 即 someUrl/{paramId}, 這時的paramId可通過 @Pathvariable 注解綁定它傳過來的值到方法的參數上,示例代碼如下:
@Controller
@RequestMapping("/owners/{ownerId}")
public class RelativePathUriTemplateController {
@RequestMapping("/pets/{petId}")
public void findPet(@PathVariable String ownerId, @PathVariable String petId, Model model) {
// implementation omitted
}
}
繼承特性
在Spring Cloud Feign 中,提供了繼承特性來幫助我們構建相應的服務客戶端不安定接口和服務控制器,進一步減少編碼量,示例如下:
-
創建一個基礎的 Maven 工程,命名為 service-contract,由於需要使用到 Spring MVC 的注解,因此在 pom.xml 中引入 spring-boot-starter-web 依賴,具體內容如下:
<?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>org.lixue</groupId>
<artifactId>service-contract</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>
<name>service-contract</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.SR3</spring-cloud.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>
</project>
-
在項目中創建服務需要使用的實體類和服務接口,代碼如下:
public class User {
private String name;
private int age;
// setter 和 getter 方法
@Override
public String toString() {
return "name=" + name + "\tage=" + age;
}
}
@RequestMapping("/hello")
public interface HelloWorldService {
@RequestMapping (value = "/hi", method = RequestMethod.GET)
String hi();
@RequestMapping (value = "/hi1", method = RequestMethod.GET)
String hi(@RequestParam ("name") String name);
@RequestMapping (value = "/hi2", method = RequestMethod.POST)
String hi(@RequestBody User user);
}
-
在服務的具體實現項目中增加 service-contract 的依賴,並且實現服務需要繼承上面的 HelloWorldService 接口,並重寫接口方法實現具體的服務實現
@RestController
public class HelloWorldController implements HelloWorldService {
@Value ("${server.port}")
int port;
@Override
public String hi() {
return "hi port " + port;
}
@Override
public String hi(String name) {
return "hi port " + port + " name " + name;
}
@Override
public String hi(@RequestBody User user) {
return "hi port " + port + " user " + user;
}
}
-
在具體的消費項目中增加 service-contract 依賴增加接口 HelloWorldServiceProxy ,繼承 service-contract 項目中的 HelloWorldService 並使用 @FeignClient 注解來聲明調用服務名稱
@FeignClient ("ORG.LIXUE.HELLOWORLD")
public interface HelloWorldServiceProxy extends HelloWorldService {
}
-
在需要調用服務的位置,使用 @Autowired 直接注入 HelloWorldServiceProxy 實例,並使用該接口實例調用方法 hi 來完成 /hi 接口的調用,示例代碼如下:
@RestController
public class FeignConsumerController {
@Autowired
HelloWorldService helloWorldService;
@RequestMapping ("/hi")
public String hi() {
User user = new User();
user.setName("liyong");
user.setAge(3434);
StringBuilder stringBuilder = new StringBuilder();
stringBuilder.append("hi=" + helloWorldServiceProxy.hi()).append("<br/>");
stringBuilder.append("hi1=" + helloWorldServiceProxy.hi("lixue")).append("<br/>");
stringBuilder.append("hi2=" + helloWorldServiceProxy.hi(user)).append("<br/>");
return stringBuilder.toString();
}
}
-
在 application.yml 中需要指定服務注冊中心,並定義自身的服務名和端口,示例如下:
server:
port: 9200
eureka:
client:
service-url:
defaultZone: http://eurekaserver2:9002/eureka,http://eurekaserver1:9001/eureka
spring:
application:
name: eureka-feign-consumer
-
啟動服務注冊中心以及二個ORG.LIXUE.HELLOWORLD服務,然后啟動 eureka-feign-consumer ,此時發送幾次 GET 請求到 http://localhost:9200/hi ,可以正確的返回 Hello World hi 9100 或者 Hello World hi 9101 ,可以看到Feign實現的消費者,依然是利用 Ribbon 維護了針對 ORG.LIXUE.HELLOWORLD服務列表信息,並且通過輪詢實現了客戶端負載均衡。