天天看點

spring cloud 系列第4篇 —— feign 聲明式服務調用 (F版本)

一、Feign 簡介

在上一個用例中,我們使用 Ribbon + RestTemplate 實作服務之間的遠端調用,實際上每一個調用都是模闆化的内容,是以 Spring Cloud Feign 在此基礎上進行了進一步的封裝。我們隻需要定義一個接口并使用 Feign 注解的方式來進行配置,同時采用 springMvc 注解進行參數綁定就可以完成服務的調用。Feign 同時還内置實作了負載均衡、服務容錯等功能。

二、項目結構

  • common:公共的接口和實體類;
  • consumer:服務的消費者,采用 Feign 調用産品服務;
  • producer:服務的提供者;
  • eureka:注冊中心。
spring cloud 系列第4篇 —— feign 聲明式服務調用 (F版本)

三、服務提供者的實作

spring cloud 系列第4篇 —— feign 聲明式服務調用 (F版本)

3.1 定義服務

産品服務由

ProductService

提供,并通過

ProducerController

将服務暴露給外部調用:

ProductService.java:

/**
 * @author : heibaiying
 * @description : 産品提供接口實作類
 */
@Service
public class ProductService implements IProductService, ApplicationListener<WebServerInitializedEvent> {

    private static List<Product> productList = new ArrayList<>();

    public Product queryProductById(int id) {
        return productList.stream().filter(p->p.getId()==id).collect(Collectors.toList()).get(0);
    }


    public List<Product> queryAllProducts() {
        return productList;
    }

    @Override
    public void saveProduct(Product product) {
        productList.add(product);
    }

    @Override
    public void onApplicationEvent(WebServerInitializedEvent event) {
        int port = event.getWebServer().getPort();
        for (long i = 0; i < 20; i++) {
            productList.add(new Product(i, port + "産品" + i, i / 2 == 0, new Date(), 66.66f * i));
        }
    }
}
           

ProducerController.java:

@RestController
public class ProducerController implements ProductFeign {

    @Autowired
    private IProductService productService;

    @GetMapping("products")
    public List<Product> productList() {
        return productService.queryAllProducts();
    }

    @GetMapping("product/{id}")
    public Product productDetail(@PathVariable int id) {
        return productService.queryProductById(id);
    }

    @PostMapping("product")
    public void save(@RequestBody Product product) {
        productService.saveProduct(product);
    }
}
           

3.2 服務注冊

指定注冊中心位址,并在啟動類上開啟自動注冊 @EnableDiscoveryClient:

server:
  port: 8020
# 指定服務命名
spring:
  application:
    name: producer
# 指定注冊中心位址
eureka:
  client:
    serviceUrl:
      defaultZone: http://localhost:8010/eureka/
           
@SpringBootApplication
@EnableDiscoveryClient
public class ProducerApplication {

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

}

           

四、服務消費者的實作

spring cloud 系列第4篇 —— feign 聲明式服務調用 (F版本)

4.1 基本依賴

<!-- feign 依賴-->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
           

4.2 @EnableFeignClients

指定注冊中心位址,并在啟動類上添加注解 @EnableDiscoveryClient 和 @EnableFeignClients,@EnableFeignClients 會去掃描工程中所有用 @FeignClient 聲明的 Feign 用戶端:

server:
  port: 8080
# 指定服務命名
spring:
  application:
    name: consumer
# 指定注冊中心位址
eureka:
  client:
    serviceUrl:
      defaultZone: http://localhost:8010/eureka/
           
@SpringBootApplication
@EnableDiscoveryClient
@EnableFeignClients
public class ConsumerApplication {

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

}
           

4.3 建立服務調用接口

/**
 * @author : heibaiying
 * @description : 聲明式服務調用
 */
public interface ProductFeign {

    @GetMapping("products")
    List<Product> productList();

    /**
     * 這是需要強調的是使用 feign 時候@PathVariable 一定要用 value 指明參數,
     * 不然會抛出.IllegalStateException: PathVariable annotation was empty on param 異常
     */
    @GetMapping("product/{id}")
    Product productDetail(@PathVariable(value = "id") int id);


    @PostMapping("product")
    void save(@RequestBody Product product);
}

           

按照官方對于服務最佳化的推薦,這裡我們的服務調用接口放在公共子產品中,因為在實際的開發中,同一個服務調用接口可能被多個子產品所使用。

spring cloud 系列第4篇 —— feign 聲明式服務調用 (F版本)

4.4 Feign 用戶端

繼承公共接口,建立 CProductFeign, 用 @FeignClient 聲明為 Feign 用戶端:

/**
 * @author : heibaiying
 * @description : 聲明式接口調用
 */
@FeignClient(value = "producer",configuration = FeignConfig.class)
public interface CProductFeign extends ProductFeign {

}
           

4.5 調用遠端服務

注入并使用 Feign 接口調用遠端服務:

@Controller
@RequestMapping("sell")
public class SellController {

    @Autowired
    private CProductFeign cproductFeign;

    @GetMapping("products")
    public String productList(Model model) {
        List<Product> products = cproductFeign.productList();
        model.addAttribute("products", products);
        return "products";
    }

    @GetMapping("product/{id}")
    public String productDetail(@PathVariable int id, Model model) {
        Product product = cproductFeign.productDetail(id);
        model.addAttribute("product", product);
        return "product";
    }


    @PostMapping("product")
    public String save(@RequestParam String productName) {
        long id = Math.round(Math.random() * 100);
        Product product = new Product(id, productName, false, new Date(), 88);
        cproductFeign.save(product);
        return "redirect:products";
    }
}
           

五、啟動測試

5.1 啟動服務

啟動一個Eureka服務、三個生産者服務(注意區分端口)、和一個消費者服務。Feign 的依賴中導入了 spring-cloud-starter-netflix-ribbon 依賴,并且在内部實作了基于 Ribbon 的用戶端負載均衡,是以我們這裡啟動三個生産者服務來觀察負載均衡的情況:

spring cloud 系列第4篇 —— feign 聲明式服務調用 (F版本)

服務注冊中心:

spring cloud 系列第4篇 —— feign 聲明式服務調用 (F版本)

5.2 驗證負載均衡

通路 http://localhost:8080/sell/products 檢視負載均衡的調用結果:

spring cloud 系列第4篇 —— feign 聲明式服務調用 (F版本)
spring cloud 系列第4篇 —— feign 聲明式服務調用 (F版本)
spring cloud 系列第4篇 —— feign 聲明式服務調用 (F版本)

六、Feign 的服務容錯

6.1 開啟容錯配置

Feign 的依賴中預設導入了 Hystrix (熔斷器)的相關依賴,我們不需要額外導入,隻需要開啟相關配置即可:

spring cloud 系列第4篇 —— feign 聲明式服務調用 (F版本)

在 application.yml 中開啟 Hystrix :

feign:
  hystrix:
    # 如果為 true,則 OpenFign 用戶端将使用 Hystrix 斷路器進行封裝 預設為 false
    enabled: true
           

6.2 定義降級處理

建立

CProductFeignImpl

,繼承 Feign接口(CProductFeign),定義熔斷時候的降級處理機制:

/**
 * @author : heibaiying
 * @description : 定義發生熔斷時候的降級處理。除了繼承自 CProductFeign,還需要用 @Component 聲明為 spring 的元件
 */
@Component
public class CProductFeignImpl implements CProductFeign {

    // 發生熔斷時候,傳回空集合,前端頁面會做容錯顯示
    @Override
    public List<Product> productList() {
        return new ArrayList<>();
    }

    @Override
    public Product productDetail(int id) {
        return null;
    }

    @Override
    public void save(Product product) {

    }
}
           

頁面的簡單容錯處理:

<!doctype html>
<html lang="en">
<head>
    <title>産品清單</title>
</head>
<body>
<h3>産品清單:點選檢視詳情</h3>
<form action="/sell/product" method="post">
    <input type="text" name="productName">
    <input type="submit" value="新增産品">
</form>
<ul>
    <#if (products?size>0) >
        <#list products as product>
            <li>
                <a href="/sell/product/${product.id}">${product.name}</a>
            </li>
        </#list>
    <#else>
        <h4 style="color: red">目前排隊人數過多,請之後再購買!</h4>
    </#if>
</ul>
</body>
</html>
           

6.3 配置降級處理

在 @FeignClient 注解中,用 fallback 參數指定熔斷時候的降級處理:

/**
 * @author : heibaiying
 * @description : 聲明式接口調用
 */
@FeignClient(value = "producer",configuration = FeignConfig.class,fallback = CProductFeignImpl.class)
public interface CProductFeign extends ProductFeign {

}
           

6.4 測試熔斷

Hystrix 預設調用逾時時間為 2s ,這裡我們使用線程休眠的方式來模拟逾時熔斷。

public List<Product> queryAllProducts() {
    /*用于測試 hystrix 逾時熔斷
    try {
        int i = new Random().nextInt(2500);
        Thread.sleep(i);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }*/
    return productList;
}
           

測試結果:

spring cloud 系列第4篇 —— feign 聲明式服務調用 (F版本)
源碼GitHub位址:https://github.com/heibaiying/spring-samples-for-all
spring cloud 系列第4篇 —— feign 聲明式服務調用 (F版本)

繼續閱讀