Feign簡介

banq發表於2019-01-29

在本教程中,我們將介紹Feign。我們還將談談Spring Boot和Feign。
在本教程中,我們將瞭解如何使用Feign編寫宣告性REST客戶端。它透過抽象出我們通常編寫的樣板程式碼,使編寫Web客戶端變得容易。我們需要做的就是宣告一個介面並註釋它及其方法。實際的實現將由框架在執行時完成,以及呼叫之間的各種訊息轉換。我們首先需要設定一個使用已知端點執行的示例REST API,以便我們可以使用feign客戶端呼叫它們。

1.服務設定
我們的示例服務是一個簡單的spring-boot應用程式,包含一個具有兩個端點的REST控制器:

@RestController
public class ProductController {

 private static List < Product > productList = new ArrayList < > ();
 static {
  productList.add(new Product(1, "product-1", 12.0));
  productList.add(new Product(2, "product-2", 34.0));
  productList.add(new Product(3, "product-3", 9.0));
 }

 @GetMapping("/products")
 public ResponseEntity << ? > getProsucts() {

  return ResponseEntity.ok(productList);

 }

 @GetMapping("/product/{id}")
 public ResponseEntity << ? > getProsucts(@PathVariable int id) {

  Product product = findProduct(id);
  if (product == null) {
   return ResponseEntity.badRequest()
    .body("Invalid product Id");
  }

  return ResponseEntity.ok(product);

 }

 private Product findProduct(int id) {
  return productList.stream()
   .filter(user -> user.getId()
    .equals(id))
   .findFirst()
   .orElse(null);
 }

}

兩個端點是' /products'和' product/{id}',它們返回一個產品列表和一個基於分別傳遞的id的產品。如果未找到產品,則返回HTTP.BAD_REQUEST響應。以下是application.properties:

server.port=8081
spring.application.name=product-service


Product服務將在埠8081上執行

2.客戶端安裝
建立Spring啟動應用程式的最佳方法是Spring Initializr。選擇Spring Boot版本,並新增“Web”,“Feign”依賴項。將它生成為Maven專案,你就完成了。請注意pom.xml中的以下依賴項:

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>


Feign最初由Netflix贊助,但後來開源。在spring-boot 1.x版本中,feign依賴項來自Netflix但是從spring-boot 2.x開始使用openfeign。透過在主類中新增' @EnableFeignClients' 使專案能夠使用Feign客戶端:

@SpringBootApplication
@EnableFeignClients
public class FeignClientExampleApplication {

 public static void main(String[] args) {
  SpringApplication.run(FeignClientExampleApplication.class, args);
 }
}


讓我們來定義我們的產品服務Feign客戶端。首先,我們必須建立一個介面ProductServiceClient,並透過@FeignClient指定名稱和產品服務的URL執行來對其進行註釋。至於方法,我們只需要宣告它並註釋就像Spring MVC樣式一樣。我們還需要告訴它可能需要哪些輸入以及轉換所需的響應型別。


@FeignClient(name = "product-service", url = "http://localhost:8081")
public interface ProductServiceClient {

 @RequestMapping(value = "/products", method = RequestMethod.GET)
 public List < Product > getAllProducts();

 @RequestMapping(value = "/product/{id}", method = RequestMethod.GET)
 public Product getProduct(@PathVariable("id") int productId);

}

Spring將使用openfeign在執行時實現此介面的實現。我們必須在專案中定義Product類,因為我們正在將響應轉換為它:

public class Product {

 private Integer id;

 private String name;

 private double price;

 public Product(Integer id, String name, double price) {
  this.id = id;
  this.name = name;
  this.price = price;
 }

 public Integer getId() {
  return id;
 }

 public String getName() {
  return name;
 }

 public double getPrice() {
  return price;
 }
}

現在讓我們在AppController中使用這個ProductServiceClient。為此,我們需要@Autowired將ProductServiceClient放入我們的控制器中。

@RestController
public class AppController {

 @Autowired
 ProductServiceClient productServiceClient;

 @GetMapping("/fetchProducts")
 public ResponseEntity << ? > fetchProducts() {

  return ResponseEntity.ok(productServiceClient.getAllProducts());
 }

 @GetMapping("/fetchProduct/{id}")
 public ResponseEntity << ? > fetchProduct(@PathVariable int id) {

  return ResponseEntity.ok(productServiceClient.getProduct(id));
 }

}


這就是我們所要做的。讓我們使用Postman進行測試:
http://localhost:8080/fetchProduct/1

在簡單程式碼的背後,所有樣板程式碼都由spring和openfeign庫處理。這樣可以減少程式碼,減少出錯的機會。

3.處理錯誤的錯誤
預設情況下,Feign僅針對任何錯誤情況(其中響應不是2XX或者存在轉換錯誤等)丟擲FeignException。
但是,如果找不到產品ID,您可能希望捕獲這些錯誤並在最終處理響應,就像產品服務引發的BAD_REQUEST錯誤一樣。我們首先定義我們的自定義ProductNotFound異常:

public class ProductNotFoundException extends RuntimeException {

 private static final long serialVersionUID = 1 L;

 public ProductNotFoundException(String msg) {
  super(msg);
 }
}

現在讓我們為這個應用定義我們的異常處理程式:

@RestControllerAdvice
public class AppExceptionHandler {

 @ResponseBody
 @ExceptionHandler(value = ProductNotFoundException.class)
 public ResponseEntity << ? > handleException(ProductNotFoundException exception) {
  return ResponseEntity.status(HttpStatus.NOT_FOUND)
   .body(exception.getMessage());
 }

}

現在要捕獲FeignException並提供您自己的實現,您需要實現feign.codec.ErrorDecoder並將其在Spring應用程式上下文中註冊為bean 。

@Component
public class AppFeignErrorDecoder implements ErrorDecoder {

 private final ErrorDecoder defaultErrorDecoder = new Default();

 @Override
 public Exception decode(String methodKey, Response response) {
  if (response.status() >= 400 && response.status() <= 499) {
   throw new ProductNotFoundException("Product Not Found");
  }

  return defaultErrorDecoder.decode(methodKey, response);
 }

}


如您所見,我們捕獲了所有4xx錯誤並丟擲了我們自己的自定義異常。

4. .使用Feign和Eureka和Ribbon
通常在微服務架構中,所有服務都註冊到像Eureka這樣的註冊服務,並且可能存在執行相同服務的多個例項。因此,您可能不希望在Feign客戶端中對URL進行硬編碼,也希望連線到響應更快的服務例項。

我們來設定一個Eureka伺服器。同樣,我們將使用Spring Initializr來建立它。
然後您需要做的就是新增@EnableEurekaServer到主類:

@SpringBootApplication
@EnableEurekaServer
public class EurekaServerApplication {

 public static void main(String[] args) {
  SpringApplication.run(EurekaServerApplication.class, args);
 }
}

以下是application.properties:server.port=8761。因此,我們的Eureka伺服器將在8761上執行,這是Spring的推薦埠。

現在註冊我們的2項服務。您只需在我們專案中新增以下依賴項pom.xml:

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>

新增@EnableDiscoveryClient到應用程式的主類,如:

@SpringBootApplication
@EnableDiscoveryClient
@EnableFeignClients
public class FeignClientExampleApplication {

 public static void main(String[] args) {
  SpringApplication.run(FeignClientExampleApplication.class, args);
 }
}


(banq注:Spring2 已經不需要@EnableDiscoveryClient)

啟動這兩個應用程式,您將看到他們在Eureka伺服器中註冊了。

現在讓我們從ProductServiceClient中刪除硬編碼的URL:

@FeignClient(name = "product-service" /*, url = "http://localhost:8081"*/ )
public interface ProductServiceClient {
 // same as previous
}


現在,幕後,ProductServiceClient將使用name屬性從Eureka伺服器獲取URL (“product-service”)。Ribbon是一個客戶端負載均衡器,它附帶了我們所包含的依賴項。這將自動獲取最佳伺服器以供我們進行其餘呼叫。