Spring Cloud實戰系列(三) - 聲明式客戶端調用Feign

相關

  1. Spring Cloud實戰系列(一) - 服務註冊與發現Eureka php

  2. Spring Cloud實戰系列(二) - 客戶端調用Rest + Ribbon 前端

  3. Spring Cloud實戰系列(三) - 聲明式客戶端Feign java

  4. Spring Cloud實戰系列(四) - 熔斷器Hystrix git

  5. Spring Cloud實戰系列(五) - 服務網關Zuul github

  6. Spring Cloud實戰系列(六) - 分佈式配置中心Spring Cloud Configweb

  7. Spring Cloud實戰系列(七) - 服務鏈路追蹤Spring Cloud Sleuthspring

  8. Spring Cloud實戰系列(八) - 微服務監控Spring Boot Adminapache

  9. Spring Cloud實戰系列(九) - 服務認證受權Spring Cloud OAuth 2.0 編程

  10. Spring Cloud實戰系列(十) - 單點登陸JWT與Spring Security OAuth json

前言

上一篇文章,講述瞭如何經過 RestTemplate 配合 Ribbon 去消費服務。Feign 是一個 聲明式HTTP 僞客戶端,提供 面向接口HTTP 客戶端調用 編程。本文進一步講如何經過 Feign 去消費服務。

  • Feign 只須要建立一個 接口 並提供 註解 便可調用。

  • Feign 具備 可插拔 的註解特性,可以使用 Feign 註解JAX-RS 註解

  • Feign 支持 可插拔編碼器解碼器

  • Feign 默認集成了 Ribbon,能夠和 Eureka 結合使用,默認實現了 負載均衡 的效果。

正文

1. 建立服務契約模塊

建立一個 service-contract 的項目 Module,建立完成後的 pom.xml 以下:

<?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>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>1.5.3.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>io.ostenant.github.springcloud</groupId>
    <artifactId>service-contract</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>service-contract</name>
    <description>Demo project for Spring Boot</description>

    <properties>
        <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.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>io.ostenant.github.springcloud</groupId>
            <artifactId>service-contract</artifactId>
            <version>0.0.1-SNAPSHOT</version>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>
</project>
複製代碼

service-contract 中定義 業務接口 和相關的 DTO 對象以下:

User.java

public class User implements Serializable {
    private String name;
    private int age;

    public User() {
    }

    public User(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public void getName() {
        return this.name;
    }

    public String setName() {
        this.name = name;
    }

    public int getAge() {
        return this.age;
    }

    public void setAge(int age) {
        this.age = age;
    }
}
複製代碼

UserContract.java

UserContract 定義了 User 的全部行爲,是一個使用 @FeignClient 註解標記的 聲明式服務接口。其中,@FeignClientvalue 指定的是 服務提供者服務名稱

@FeignClient("service-provider")
public interface UserContract {
    @PostMapping("/user")
    void add(@RequestBody User user);

    @GetMapping("/user/{name}")  
    User findByName(@PathVariable String name);

    @GetMapping("/users")
    List<User> findAll();
}
複製代碼

對於 服務提供者 而言,須要實現 UserContract 接口的方法;對於 服務消費者 而言,能夠直接注入 UserContract 做爲 客戶端樁 使用。

2. 建立服務提供者

建立一個 service-provider 的項目 Module,建立完成後引入 服務契約模塊 的依賴,pom.xml 以下:

<?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>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>1.5.3.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>io.ostenant.github.springcloud</groupId>
    <artifactId>service-provider</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>service-provider</name>
    <description>Demo project for Spring Boot</description>

    <properties>
        <java.version>1.8</java.version>
        <spring-cloud.version>Dalston.SR1</spring-cloud.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-eureka</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>io.ostenant.github.springcloud</groupId>
            <artifactId>service-contract</artifactId>
            <version>0.0.1-SNAPSHOT</version>
        </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>
複製代碼

經過 註解 @EnableEurekaClient 代表本身是一個 Eureka Client

@SpringBootApplication
@EnableEurekaClient
@RestController
public class ServiceProviderApplication {
    public static void main(String[] args) {
        SpringApplication.run(ServiceProviderApplication.class, args);
    }
}
複製代碼

建立一個類 UserService,實現 UserContract 接口的具體業務,對外提供 User 相關的 HTTP 的服務。

@RestController
public class UserService implements UserContract {
    private static final Set<User> userSet = new HashSet<>();

    static {
        userSet.add(new User("Alex", 28));
        userSet.add(new User("Lambert", 32));
        userSet.add(new User("Diouf", 30));
    }

    @Override
    public void add(@RequestBody User user) {
        userSet.add(user);
    }

    @Override
    public User findByName(@PathVariable String name) {
        return userSet.stream().filter(user -> {
            return user.getName().equals(name);
        }).findFirst();
    }

    @Override
    public List<User> findAll() {
        return new ArrayList<>(userSet);
    }
}
複製代碼

配置文件 中註明的 服務註冊中心 的地址,application.yml 配置文件以下:

spring:
 active:
 profiles: sp1 # sp2

---

spring:
 profiles: sp1
eureka:
 client:
 serviceUrl:
 defaultZone: http://localhost:8761/eureka/
server:
 port: 8770
spring:
 application:
 name: service-provider

---

spring:
 profiles: sp2
eureka:
 client:
 serviceUrl:
 defaultZone: http://localhost:8761/eureka/
server:
 port: 8771
spring:
 application:
 name: service-provider
複製代碼

分別以 spring.profiles.active=sp1spring.profiles.active=sp2 做爲 Spring Boot啓動命令參數,在 端口號 87708771 啓動 2服務提供者 實例。

3. 建立服務消費者

新建一個項目 Module,取名爲 service-consumer,在它的 pom 文件中引入 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>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>1.5.3.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>io.ostenant.github.springcloud</groupId>
    <artifactId>service-consumer</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>service-consumer</name>
    <description>Demo project for Spring Boot</description>

    <properties>
        <java.version>1.8</java.version>
        <spring-cloud.version>Dalston.SR1</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-web</artifactId>
        </dependency>
        <dependency>
            <groupId>io.ostenant.github.springcloud</groupId>
            <artifactId>service-contract</artifactId>
            <version>0.0.1-SNAPSHOT</version>
        </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>
複製代碼

在項目的配置文件 application.yml 文件,指定 應用名稱service-consumer端口號8772服務註冊地址http://localhost:8761/eureka/ ,代碼以下:

eureka:
 client:
 serviceUrl:
 defaultZone: http://localhost:8761/eureka/
server:
 port: 8772
spring:
 application:
 name: service-feign
複製代碼

在應用的啓動類 ServiceConsumerApplication 上加上 @EnableFeignClients 註解開啓 Feign 的功能。

@SpringBootApplication
@EnableDiscoveryClient
@EnableFeignClients
public class ServiceConsumerApplication {

    public static void main(String[] args) {
        SpringApplication.run(ServiceConsumerApplication.class, args);
    }
}
複製代碼

定義一個 UserController 控制器,用於調用 服務提供者 提供的服務並 響應 前端。

@RestController
public class UserController {
    @Autowired
    private UserContract userContract;

    @PostMapping("/user")
    public void add(@RequestBody User user) {
        userContract.add(user);
    }

    @GetMapping("/user/{name}")
    public User findByName(@PathVariable String name) {
        return userContract.findByName(name);
    }

    @GetMapping("/users")
    public List<User> findAll() {
        return userContract.findAll();
    }
}
複製代碼

控制層 UserController 引入 Feign 接口,經過 @FeignClient服務名稱),來指定調用的是哪一個 服務

啓動 服務消費者 應用,訪問 http://localhost:8772/users 測試 服務消費者 的訪問連通性,響應內容爲:

[
    {
      "name": "Alex",
      "age": 28
    },
    {
      "name": "Lambert",
      "age": 32
    },
    {
      "name": "Diouf",
      "age": 30
    }
]
複製代碼

4. Feign的源碼實現過程

總的來講,Feign源碼實現 過程以下:

  1. 首先經過 @EnableFeignClients 註解開啓 FeignClient 的功能。只有這個 註解 存在,纔會在程序啓動時 啓動 @FeignClient 註解包掃描

  2. 服務提供者 實現基於 Feign契約接口,並在 契約接口 上面加上 @FeignClient 註解。

  3. 服務消費者 啓動後,會進行 包掃描 操做,掃描全部的 @FeignClient註解 的類,並將這些信息注入 Spring 上下文中。

  4. 接口 的方法被調用時,經過 JDK代理 來生成具體的 RequestTemplate 模板對象

  5. 根據 RequestTemplate 再生成 HTTP 請求的 Request 對象。

  6. Request 對象交給 Client 去處理,其中 Client 內嵌的 網絡請求框架 能夠是 HTTPURLConnectionHttpClientOkHttp

  7. 最後 Client 被封裝到 LoadBalanceClient 類,這個類結合 Ribbon 完成 負載均衡 功能。

參考

  • 方誌朋《深刻理解Spring Cloud與微服務構建》

歡迎關注技術公衆號: 零壹技術棧

零壹技術棧

本賬號將持續分享後端技術乾貨,包括虛擬機基礎,多線程編程,高性能框架,異步、緩存和消息中間件,分佈式和微服務,架構學習和進階等學習資料和文章。

相關文章
相關標籤/搜索