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文件如下:

 

 1 <?xml version="1.0" encoding="UTF-8"?>
 2 <project xmlns="http://maven.apache.org/POM/4.0.0" 
 3         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
 4         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
 5     <modelVersion>4.0.0</modelVersion>
 6     <groupId>com.chry</groupId>
 7     <artifactId>springcloud.helloworld.feign.client</artifactId>
 8     <version>0.0.1-SNAPSHOT</version>
 9     <packaging>jar</packaging>
10     <name>springcloud.helloworld.feign.client</name>
11     <description>Demo Feigh client application</description>
12 
13     <parent>
14         <groupId>org.springframework.boot</groupId>
15         <artifactId>spring-boot-starter-parent</artifactId>
16         <version>1.5.3.RELEASE</version>
17         <relativePath/> <!-- lookup parent from repository -->
18     </parent>
19 
20     <properties>
21         <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
22         <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
23         <java.version>1.8</java.version>
24     </properties>
25 
26     <dependencies>
27         <dependency>
28             <groupId>org.springframework.cloud</groupId>
29             <artifactId>spring-cloud-starter-eureka</artifactId>
30         </dependency>
31         <dependency>
32             <groupId>org.springframework.cloud</groupId>
33             <artifactId>spring-cloud-starter-feign</artifactId>
34         </dependency>
35         <dependency>
36             <groupId>org.springframework.boot</groupId>
37             <artifactId>spring-boot-starter-web</artifactId>
38         </dependency>
39 
40         <dependency>
41             <groupId>org.springframework.boot</groupId>
42             <artifactId>spring-boot-starter-test</artifactId>
43             <scope>test</scope>
44         </dependency>
45     </dependencies>
46 
47     <dependencyManagement>
48         <dependencies>
49             <dependency>
50                 <groupId>org.springframework.cloud</groupId>
51                 <artifactId>spring-cloud-dependencies</artifactId>
52                 <version>Dalston.RC1</version>
53                 <type>pom</type>
54                 <scope>import</scope>
55             </dependency>
56         </dependencies>
57     </dependencyManagement>
58 
59     <build>
60         <plugins>
61             <plugin>
62                 <groupId>org.springframework.boot</groupId>
63                 <artifactId>spring-boot-maven-plugin</artifactId>
64             </plugin>
65         </plugins>
66     </build>
67 
68     <repositories>
69         <repository>
70             <id>spring-milestones</id>
71             <name>Spring Milestones</name>
72             <url>https://repo.spring.io/milestone</url>
73             <snapshots>
74                 <enabled>false</enabled>
75             </snapshots>
76         </repository>
77     </repositories>
78 </project>

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負載均衡。

                

6. 在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

 7. 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接口拋出業務異常以供調用者處理。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章