Spring Cloud 入門教程(六): 用宣告式REST客戶端Feign呼叫遠端HTTP服務
首先簡單解釋一下什麼是宣告式實現?
要做一件事, 需要知道三個要素,where, what, how。即在哪裡( where)用什麼辦法(how)做什麼(what)。什麼時候做(when)我們納入how的範疇。
1)程式設計式實現: 每一個要素(where,what,how)都需要用具體程式碼實現來表示。傳統的方式一般都是程式設計式實現,業務開發者需要關心每一處邏輯
2)宣告式實現: 只需要宣告在哪裡(where )做什麼(what),而無需關心如何實現(how)。Spring的AOP就是一種宣告式實現,比如網站檢查是否登入,開發頁面邏輯的時候,只需要通過AOP配置宣告載入頁面(where)需要做檢查使用者是否登入(what),而無需關心如何檢查使用者是否登入(how)。如何檢查這個邏輯由AOP機制去實現, 而AOP的登入檢查實現機制與正在開發頁面的邏輯本身是無關的。
在Spring Cloud Netflix棧中,各個微服務都是以HTTP介面的形式暴露自身服務的,因此在呼叫遠端服務時就必須使用HTTP客戶端。Feign就是Spring Cloud提供的一種宣告式REST客戶端。可以通過Feign訪問呼叫遠端微服務提供的REST介面。現在我們就用Feign來呼叫SERVICE-HELLOWORLD暴露的REST介面,以獲取到“Hello World”資訊。在使用Feign時,Spring Cloud集成了Ribbon和Eureka來提供HTTP客戶端的負載均衡。
下面我們就採用Feign的方式來呼叫Hello World服務叢集。 1. 建立Maven工程,加入spring-cloud-starter-feign依賴
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-feign</artifactId>
</dependency>
完整的pom檔案如下:
<?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.chry</groupId> <artifactId>springcloud.helloworld.feign.client</artifactId> <version>0.0.1-SNAPSHOT</version> <packaging>jar</packaging> <name>springcloud.helloworld.feign.client</name> <description>Demo Feigh client application</description> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>1.5.3.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.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-web</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>Dalston.RC1</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> <repositories> <repository> <id>spring-milestones</id> <name>Spring Milestones</name> <url>https://repo.spring.io/milestone</url> <snapshots> <enabled>false</enabled> </snapshots> </repository> </repositories>
2. 建立啟動類,需呀加上@EnableFeignClients註解以使用Feign, 使用@EnableDiscoveryClient開啟服務自動發現
1 package springcloud.helloworld.feign.service;
2
3 import org.springframework.boot.SpringApplication;
4 import org.springframework.boot.autoconfigure.SpringBootApplication;
5 import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
6 import org.springframework.cloud.netflix.feign.EnableFeignClients;
7
8 @SpringBootApplication
9 @EnableDiscoveryClient
10 @EnableFeignClients
11 public class ServiceFeignApplication {
12 public static void main(String[] args) {
13 SpringApplication.run(ServiceFeignApplication.class, args);
14 }
15 }
3. 新增配置檔案application.yml, 使用埠8902, 名字定義為service-feign, 並註冊到eureka服務中心
1 eureka:
2 client:
3 serviceUrl:
4 defaultZone: http://localhost:8761/eureka/
5 server:
6 port: 8902
7 spring:
8 application:
9 name: service-feign
4. 定義Feign:一個用@FeignClient註解的介面類, @FeignClient用於通知Feign元件對該介面進行代理(不需要編寫介面實現),使用者可直接通過@Autowired注入; 該介面通過value定義了需要呼叫的SERVICE-HELLOWORLD服務(通過服務中心自動發現機制會定位具體URL); @RequestMapping定義了Feign需要訪問的SERVICE-HELLOWORLD服務的URL(本例中為根“/”)
1 package springcloud.helloworld.feign.service;
2
3 import org.springframework.cloud.netflix.feign.FeignClient;
4 import org.springframework.web.bind.annotation.RequestMapping;
5 import org.springframework.web.bind.annotation.RequestMethod;
6
7 @FeignClient(value = "SERVICE-HELLOWORLD")
8 public interface HelloWorldService {
9 @RequestMapping(value = "/",method = RequestMethod.GET)
10 String sayHello();
11 }
Spring Cloud應用在啟動時,Feign會掃描標有@FeignClient註解的介面,生成代理,並註冊到Spring容器中。生成代理時Feign會為每個介面方法建立一個RequetTemplate物件,該物件封裝了HTTP請求需要的全部資訊,請求引數名、請求方法等資訊都是在這個過程中確定的,Feign的模板化就體現在這裡 5. 定義一個WebController。 注入之前通過@FeignClient定義生成的bean,
sayHello()對映到http://localhost:8902/hello, 在這裡,我修改了Hello World服務的對映,將根“/”, 修改成了“/hello”。
1 package springcloud.helloworld.feign.service;
2
3 import org.springframework.beans.factory.annotation.Autowired;
4 import org.springframework.web.bind.annotation.RequestMapping;
5 import org.springframework.web.bind.annotation.RequestMethod;
6 import org.springframework.web.bind.annotation.RestController;
7
8 @RestController
9 public class WebController {
10 @Autowired HelloWorldService helloWorldFeignService;
11 @RequestMapping(value = "/hello",method = RequestMethod.GET)
12 public String sayHello(){
13 return helloWorldFeignService.sayHello();
14 }
15 }
6. 啟動Feign應用, 訪問http://localhost:8902/hello, 多次重新整理,可以看到和前一章Ribbon裡面的應用一樣, 兩個Hello World服務的輸出交替出現。說明通過Feign訪問服務, Spring Cloud已經預設使用了Ribbon負載均衡。
7. 在Feign中使用Apache HTTP Client Feign在預設情況下使用的是JDK原生的URLConnection傳送HTTP請求,沒有連線池,但是對每個地址gwai會保持一個長連線,即利用HTTP的persistence connection 。我們可以用Apache的HTTP Client替換Feign原始的http client, 從而獲取連線池、超時時間等與效能息息相關的控制能力。Spring Cloud從Brixtion.SR5版本開始支援這種替換,首先在專案中宣告Apache HTTP Client和feign-httpclient依賴:
1 <!-- 使用Apache HttpClient替換Feign原生httpclient -->
2 <dependency>
3 <groupId>org.apache.httpcomponents</groupId>
4 <artifactId>httpclient</artifactId>
5 </dependency>
6 <dependency>
7 <groupId>com.netflix.feign</groupId>
8 <artifactId>feign-httpclient</artifactId>
9 <version>${feign-httpclient}</version>
10 </dependency>
然後在application.properties中新增:
feign.httpclient.enabled=true
8. Feign的Encoder、Decoder和ErrorDecoder Feign將方法簽名中方法引數物件序列化為請求引數放到HTTP請求中的過程,是由編碼器(Encoder)完成的。同理,將HTTP響應資料反序列化為Java物件是由解碼器(Decoder)完成的。預設情況下,Feign會將標有@RequestParam註解的引數轉換成字串新增到URL中,將沒有註解的引數通過Jackson轉換成json放到請求體中。注意,如果在@RequetMapping中的method將請求方式指定為POST,那麼所有未標註解的引數將會被忽略,例如:
@RequestMapping(value = "/group/{groupId}", method = RequestMethod.GET)
void update(@PathVariable("groupId") Integer groupId, @RequestParam("groupName") String groupName, DataObject obj);
此時因為宣告的是GET請求沒有請求體,所以obj引數就會被忽略。
在Spring Cloud環境下,Feign的Encoder只會用來編碼沒有添加註解的引數。如果你自定義了Encoder, 那麼只有在編碼obj引數時才會呼叫你的Encoder。對於Decoder, 預設會委託給SpringMVC中的MappingJackson2HttpMessageConverter類進行解碼。只有當狀態碼不在200 ~ 300之間時ErrorDecoder才會被呼叫。ErrorDecoder的作用是可以根據HTTP響應資訊返回一個異常,該異常可以在呼叫Feign介面的地方被捕獲到。我們目前就通過ErrorDecoder來使Feign介面丟擲業務異常以供呼叫者處理。