動態

詳情 返回 返回

OpenFeign深入學習筆記 - 動態 詳情

OpenFeign 是一個聲明式的 Web 服務客户端,它使得編寫 Web 服務客户端變得更加容易。OpenFeign 是在 Spring Cloud 生態系統中的一個組件,它整合了 Ribbon(客户端負載均衡器)和 Eureka(服務發現組件),從而簡化了微服務之間的調用。

在 SpringCloud 應用中,我們經常會 使用 OpenFeign,比如通過定義一個接口並使用註解的方式來創建一個 Web 服務客户端,而不需要編寫大量的模板代碼。OpenFeign 會自動生成接口的實現類,並使用 Ribbon 來調用相應的服務。

我們先來上手用一下,在 Spring Cloud 項目中使用 OpenFeign:

需求:我們的業務場景是這樣的:一個電子商務平台,其中包含一個商品服務(product-service)和一個訂單服務(order-service)。我們要使用 OpenFeign 來實現訂單服務調用商品服務的接口。

步驟1:創建商品服務(product-service)

  1. 添加依賴pom.xml):

    <dependencies>
        <!-- Spring Boot Web 依賴 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <!-- Spring Boot Actuator 依賴 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>
    </dependencies>
  2. 主應用類ProductApplication.java):

    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    
    @SpringBootApplication
    public class ProductApplication {
        public static void main(String[] args) {
            SpringApplication.run(ProductApplication.class, args);
        }
    }
  3. 商品控制器ProductController.java):

    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.PathVariable;
    import org.springframework.web.bind.annotation.RestController;
    
    @RestController
    public class ProductController {
    
        @GetMapping("/products/{id}")
        public String getProduct(@PathVariable("id") Long id) {
            // 模擬數據庫中獲取商品信息
            return "Product with ID: " + id;
        }
    }

步驟2:創建訂單服務(order-service)

  1. 添加依賴pom.xml):

    <dependencies>
        <!-- Spring Boot Web 依賴 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <!-- Spring Cloud OpenFeign 依賴 -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-openfeign</artifactId>
        </dependency>
    </dependencies>
  2. 主應用類OrderApplication.java):

    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    import org.springframework.cloud.openfeign.EnableFeignClients;
    
    @SpringBootApplication
    @EnableFeignClients
    public class OrderApplication {
        public static void main(String[] args) {
            SpringApplication.run(OrderApplication.class, args);
        }
    }
  3. Feign 客户端接口ProductClient.java):

    import org.springframework.cloud.openfeign.FeignClient;
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.PathVariable;
    
    @FeignClient(name = "product-service", url = "http://localhost:8081")
    public interface ProductClient {
        @GetMapping("/products/{id}")
        String getProduct(@PathVariable("id") Long id);
    }
  4. 訂單控制器OrderController.java):

    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.PathVariable;
    import org.springframework.web.bind.annotation.RestController;
    
    @RestController
    public class OrderController {
    
        private final ProductClient productClient;
    
        @Autowired
        public OrderController(ProductClient productClient) {
            this.productClient = productClient;
        }
    
        @GetMapping("/orders/{id}/product")
        public String getOrderProduct(@PathVariable("id") Long id) {
            // 調用商品服務獲取商品信息
            return productClient.getProduct(id);
        }
    }

步驟3:運行和測試

  1. 啓動商品服務ProductApplication):

    • 運行 ProductApplicationmain 方法。
  2. 啓動訂單服務OrderApplication):

    • 運行 OrderApplicationmain 方法。
  3. 測試調用

    • 使用瀏覽器或 Postman 訪問 http://localhost:8082/orders/1/product,我們就能看到商品服務返回的商品信息。

以上是OpenFeign的基本使用,作為優秀的程序員,我們必須要去深入瞭解OpenFeign核心組件背後的實現,知己知彼,方能百戰不殆。下面我們來一起看下 OpenFeign 的一些核心組件及其源碼分析:

OpenFeign 的核心組件有哪些?

OpenFeign 是 Spring Cloud 生態系統中的一個聲明式 Web 服務客户端,用於簡化微服務之間的 HTTP 調用。

1. Encoder:

在 OpenFeign 中,Encoder 組件負責將請求數據序列化成可以發送的格式。默認情況下,OpenFeign 只支持將請求數據序列化為字符串或字節數組。如果需要支持更復雜的對象序列化,可以通過實現自定義的 Encoder 來實現。

我們來分析一下 Encoder 組件的源碼實現:

步驟1:定義 Encoder 接口

首先,Feign定義了一個 Encoder 接口,該接口包含一個 encode 方法,用於將對象序列化為字節數組:

public interface Encoder {
    void encode(Object object, Type bodyType, RequestTemplate template) throws EncodeException;
}
  • object:要序列化的對象。
  • bodyType:對象的類型信息,通常用於確定如何序列化對象。
  • templateRequestTemplate 對象,用於設置請求的主體(body)。

步驟2:實現默認 Encoder

OpenFeign 提供了一個默認的 Encoder 實現,通常使用 Jackson 或其他 JSON 庫來序列化對象為 JSON 格式:

public class JacksonEncoder extends SpringEncoder implements Encoder {
    public JacksonEncoder(ObjectFactory objectFactory) {
        super(objectFactory);
    }

    @Override
    public void encode(Object object, Type bodyType, RequestTemplate template) throws EncodeException {
        // 將對象序列化為 JSON 格式,並設置到 RequestTemplate 中
        byte[] body = this.objectFactory.createInstance(bodyType).writeValueAsBytes(object);
        template.body(body);
        template.requestBody(Request.Body.create(body, ContentType.APPLICATION_JSON));
    }
}

在這個實現中,JacksonEncoder 使用 Jackson 庫將對象序列化為 JSON,並設置請求的內容類型為 APPLICATION_JSON

步驟3:自定義 Encoder 實現

如果我們需要支持其他類型的序列化,可以創建自定義的 Encoder 實現。例如,如果要支持 XML 序列化,可以創建一個使用 JAXB 或其他 XML 庫的 Encoder

public class XmlEncoder implements Encoder {
    @Override
    public void encode(Object object, Type bodyType, RequestTemplate template) throws EncodeException {
        // 使用 XML 庫將對象序列化為 XML 格式
        String xml = serializeToXml(object);
        template.body(xml, ContentType.APPLICATION_XML);
    }

    private String serializeToXml(Object object) {
        // 實現對象到 XML 的序列化邏輯
        // ...
        return xml;
    }
}

步驟4:配置 OpenFeign 客户端使用自定義 Encoder

在 Feign 客户端配置中,可以指定自定義的 Encoder 實現:

@Configuration
public class FeignConfig {
    @Bean
    public Encoder encoder() {
        return new XmlEncoder();
    }
}

然後在 @FeignClient 註解中指定配置類:

@FeignClient(name = "myClient", configuration = FeignConfig.class)
public interface MyClient {
    // ...
}

小結一下

Encoder 接口,為請求數據的序列化提供了一個擴展點。OpenFeign 提供了默認的 JacksonEncoder 實現,它使用 Jackson 庫來序列化對象為 JSON 格式。如果想實現自定義的 Encoder,來支持其他數據格式,如 XML、Protobuf 等也是非常方便靈活的。

2. Decoder:

OpenFeign 的Decoder 組件負責將HTTP響應體(通常是字節流)反序列化為Java對象。默認情況下,OpenFeign支持將響應體反序列化為字符串或字節數組。如果需要支持更復雜的對象反序列化,可以通過實現自定義的 Decoder 來實現。

下面來分析 Decoder 組件的源碼實現:

步驟1:定義 Decoder 接口

OpenFeign 定義了一個 Decoder 接口,該接口包含一個 decode 方法,用於將響應體反序列化為對象:

public interface Decoder {
    <T> T decode(Response response, Type type) throws IOException, DecodeException;
}
  • T:要反序列化成的對象類型。
  • response:Feign的 Response 對象,包含了HTTP響應的所有信息。
  • type:要反序列化成的對象的類型信息。

步驟2:實現默認 Decoder

OpenFeign 提供了一個默認的 Decoder 實現,通常使用 Jackson 或其他 JSON 庫來反序列化JSON響應體:

public class JacksonDecoder extends SpringDecoder implements Decoder {
    public JacksonDecoder(ObjectFactory objectFactory) {
        super(objectFactory);
    }

    @Override
    public <T> T decode(Response response, Type type) throws IOException, DecodeException {
        // 從響應中獲取字節流
        InputStream inputStream = response.body().asInputStream();
        // 使用 Jackson 庫將字節流反序列化為 Java 對象
        return this.objectFactory.createInstance(type).readValue(inputStream, type);
    }
}

在這個實現中,JacksonDecoder 使用 Jackson 庫將響應體的字節流反序列化為 Java 對象,並根據響應的狀態碼拋出相應的異常或返回對象。

步驟3:自定義 Decoder 實現

如果咱們需要支持其他類型的反序列化,可以創建自定義的 Decoder 實現。比如要支持 XML 反序列化,可以創建一個使用 JAXB 或其他 XML 庫的 Decoder

public class XmlDecoder implements Decoder {
    @Override
    public <T> T decode(Response response, Type type) throws IOException, DecodeException {
        // 從響應中獲取字節流
        InputStream inputStream = response.body().asInputStream();
        // 使用 XML 庫將字節流反序列化為 Java 對象
        T object = deserializeFromXml(inputStream, type);
        return object;
    }

    private <T> T deserializeFromXml(InputStream inputStream, Type type) {
        // 實現 XML 到 Java 對象的反序列化邏輯
        // ...
        return null;
    }
}

步驟4:配置 OpenFeign 客户端使用自定義 Decoder

在 Feign 客户端配置中,可以指定自定義的 Decoder 實現:

@Configuration
public class FeignConfig {
    @Bean
    public Decoder decoder() {
        return new XmlDecoder();
    }
}

然後在 @FeignClient 註解中指定配置類:

@FeignClient(name = "myClient", configuration = FeignConfig.class)
public interface MyClient {
    // ...
}

小結一下

OpenFeign 提供了默認的 JacksonDecoder 實現,它使用 Jackson 庫來反序列化 JSON 格式的響應體。咱們還可以通過實現自定義的 Decoder,可以支持其他數據格式,如 XML等。開發中,咱們可以根據需要選擇或實現適合自己業務場景的反序列化方式。

3. Contract:

OpenFeign的Contract 組件負責將接口的方法和註解轉換為HTTP請求。它定義瞭如何將Java接口映射到HTTP請求上,包括請求的URL、HTTP方法、請求頭、查詢參數和請求體等。Contract 組件是Feign中非常核心的部分,因為它決定了Feign客户端如何理解和構建HTTP請求。

步驟1:定義 Contract 接口

Feign定義了一個 Contract 接口,該接口包含兩個主要的方法:

public interface Contract {
    List<MethodMetadata> parseAndValidatteMethods(FeignTarget<?> target);
    RequestTemplate create(Request request, Target<?> target, Method method, Object... argv);
}
  • parseAndValidatteMethods:解析並驗證目標接口的方法,生成 MethodMetadata 列表,每個 MethodMetadata 包含一個方法的所有元數據。
  • create:根據 RequestTarget 創建 RequestTemplate,用於構建實際的HTTP請求。

步驟2:實現默認 Contract

Feign提供了一個默認的 Contract 實現,通常使用Java的反射API來解析接口的方法和註解:

public class FeignContract implements Contract {
    @Override
    public List<MethodMetadata> parseAndValidateMethods(FeignTarget<?> target) {
        // 解析目標接口的方法,生成 MethodMetadata 列表
        // ...
    }

    @Override
    public RequestTemplate create(Request request, Target<?> target, Method method, Object... argv) {
        // 根據 MethodMetadata 和參數創建 RequestTemplate
        // ...
    }
}

在實現中咱們可以看到,FeignContract 會檢查接口方法上的註解(如 @GetMapping@PostMapping 等),並根據這些註解構建HTTP請求。

步驟3:自定義 Contract 實現

同樣的道理,如果需要支持自定義的註解或擴展Feign的功能,可以通過實現自定義的 Contract 來實現:

public class MyCustomContract implements Contract {
    @Override
    public List<MethodMetadata> parseAndValidateMethods(FeignTarget<?> target) {
        // 自定義解析邏輯
        // ...
    }

    @Override
    public RequestTemplate create(Request request, Target<?> target, Method method, Object... argv) {
        // 自定義創建 RequestTemplate 的邏輯
        // ...
    }
}

步驟4:配置 OpenFeign 客户端使用自定義 Contract

在Feign客户端配置中,可以指定自定義的 Contract 實現:

@Configuration
public class FeignConfig {
    @Bean
    public Contract contract() {
        return new MyCustomContract();
    }
}

然後在 @FeignClient 註解中指定配置類:

@FeignClient(name = "myClient", configuration = FeignConfig.class)
public interface MyClient {
    // ...
}

小結一下

OpenFeign 提供了默認的 FeignContract 實現,它使用Java的反射API來解析接口的方法和註解,並生成 MethodMetadata。這種方式允許Feign自動處理標準的JAX-RS註解。咱們可以通過實現自定義的 Contract,可以支持自定義註解或改變Feign的請求構建邏輯。

4. Client:

Client 組件是負責發送HTTP請求並接收HTTP響應的核心組件。OpenFeign 默認使用Java標準庫中的HttpURLConnection來發送請求,但也支持使用其他HTTP客户端庫,如Apache HttpClient或OkHttp。

步驟1:定義 Client 接口

OpenFeign中定義了一個Client接口,該接口包含一個execute方法,用於執行HTTP請求:

public interface Client {
    Response execute(Request request, Request.Options options) throws IOException;
}
  • Request:代表要執行的HTTP請求。
  • Request.Options:包含請求的配置選項,如連接超時和讀取超時。
  • Response:代表HTTP響應。

步驟2:實現默認 Client

OpenFeign 提供了一個默認的Client實現,使用Java的HttpURLConnection

public class HttpURLConnectionClient implements Client {
    @Override
    public Response execute(Request request, Request.Options options) throws IOException {
        // 創建和配置 HttpURLConnection
        URL url = new URL(request.url());
        HttpURLConnection connection = (HttpURLConnection) url.openConnection();
        // 設置請求方法、頭信息、超時等
        // ...
        // 發送請求並獲取響應
        // ...
        return response;
    }
}

在這個實現中,HttpURLConnectionClient使用HttpURLConnection來構建和發送HTTP請求,並處理響應。

步驟3:自定義 Client 實現

如果我們需要使用其他HTTP客户端庫,可以創建自定義的Client實現。例如,使用Apache HttpClient:

public class HttpClientClient implements Client {
    private final CloseableHttpClient httpClient;

    public HttpClientClient(CloseableHttpClient httpClient) {
        this.httpClient = httpClient;
    }

    @Override
    public Response execute(Request request, Request.Options options) throws IOException {
        // 使用 Apache HttpClient 構建和發送HTTP請求
        // ...
        return response;
    }
}

步驟4:配置 Feign 客户端使用自定義 Client

在Feign配置中,可以指定自定義的Client實現:

@Configuration
public class FeignConfig {
    @Bean
    public Client httpClientClient() {
        CloseableHttpClient httpClient = HttpClients.createDefault();
        return new HttpClientClient(httpClient);
    }
}

然後在@FeignClient註解中指定配置類:

@FeignClient(name = "myClient", configuration = FeignConfig.class)
public interface MyClient {
    // ...
}

小結一下

OpenFeign 提供了默認的HttpURLConnectionClient實現,它使用Java標準庫中的HttpURLConnection來發送請求。這種方式的好處是簡單且無需額外依賴。也可以通過實現自定義的Client,如Apache HttpClient或OkHttp。這讓OpenFeign能夠適應不同的性能和功能需求。

5. RequestInterceptor:

RequestInterceptor 是一個非常重要的組件,它允許咱們在請求發送之前對其進行攔截,從而可以添加一些通用的處理邏輯,比如設置認證頭、日誌記錄、修改請求參數等。我們來分析一下 RequestInterceptor 組件的源碼實現:

步驟1:定義 RequestInterceptor 接口

Feign定義了一個 RequestInterceptor 接口,該接口包含一個 apply 方法,用於在請求發送前對 RequestTemplate 進行操作:

public interface RequestInterceptor {
    void apply(RequestTemplate template);
}
  • RequestTemplate:代表即將發送的HTTP請求,可以修改URL、頭信息、請求體等。

步驟2:實現默認 RequestInterceptor

OpenFeign 提供了一些默認的 RequestInterceptor 實現,例如用於設置默認頭信息的 RequestInterceptor

public class DefaultRequestInterceptor implements RequestInterceptor {
    private final Configuration configuration;

    public DefaultRequestInterceptor(Configuration configuration) {
        this.configuration = configuration;
    }

    @Override
    public void apply(RequestTemplate template) {
        // 設置默認的頭信息,比如Content-Type
        template.header("Content-Type", configuration.getContentType().toString());
        // 可以添加更多的默認處理邏輯
    }
}

在這個實現中,DefaultRequestInterceptor 會在每個請求中設置一些默認的頭信息。

步驟3:自定義 RequestInterceptor 實現

咱們可以根據需要實現自定義的 RequestInterceptor。例如,添加一個用於設置認證頭的攔截器:

public class AuthRequestInterceptor implements RequestInterceptor {
    private final String authToken;

    public AuthRequestInterceptor(String authToken) {
        this.authToken = authToken;
    }

    @Override
    public void apply(RequestTemplate template) {
        // 在每個請求中添加認證頭
        template.header("Authorization", "Bearer " + authToken);
    }
}

步驟4:配置 OpenFeign 客户端使用自定義 RequestInterceptor

在 OpenFeign 配置中,可以指定自定義的 RequestInterceptor 實現:

@Configuration
public class FeignConfig {
    @Bean
    public RequestInterceptor authRequestInterceptor() {
        // 假設從配置文件或環境變量中獲取認證令牌
        String authToken = "your-auth-token";
        return new AuthRequestInterceptor(authToken);
    }
}

然後在 @FeignClient 註解中指定配置類:

@FeignClient(name = "myClient", configuration = FeignConfig.class)
public interface MyClient {
    // ...
}

小結一下

RequestInterceptor讓咱們在不修改每個單獨請求的情況下,統一處理請求。這使得Feign客户端更加靈活和強大,能夠適應各種複雜的業務需求。

6. Retryer:

Retryer 組件負責定義重試策略,它決定了在遇到特定類型的錯誤時是否重試請求,以及重試的次數和間隔。Retryer 是Feign中的一個重要組件,特別是在網絡不穩定或服務不穩定的環境中,大派用場,它可以顯著提高系統的健壯性哦。

步驟1:定義 Retryer 接口

Feign定義了一個 Retryer 接口,該接口包含幾個關鍵的方法,用於控制重試的行為:

public interface Retryer {
    void continueOrPropagate(RetryableException e);
    Retryer clone();
    long getDelay(RetryableException e, int attempt);
    boolean shouldRetry(RetryableException e, int attempt, int retry);
}
  • continueOrPropagate:決定是繼續重試還是拋出異常。
  • clone:創建 Retryer 的副本,通常用於每個請求的獨立重試策略。
  • getDelay:返回在下一次重試之前的延遲時間。
  • shouldRetry:決定是否應該重試請求。

步驟2:實現默認 Retryer

Feign提供了一個默認的 Retryer 實現,通常是一個簡單的重試策略,例如:

public class DefaultRetryer implements Retryer {
    private final long period;
    private final long maxPeriod;
    private final int maxAttempts;

    public DefaultRetryer(long period, long maxPeriod, int maxAttempts) {
        this.period = period;
        this.maxPeriod = maxPeriod;
        this.maxAttempts = maxAttempts;
    }

    @Override
    public void continueOrPropagate(RetryableException e) {
        // 根據異常類型和重試次數決定是否重試
        if (shouldRetry(e, e.getAttempt(), maxAttempts)) {
            // 繼續重試
        } else {
            // 拋出異常
            throw e;
        }
    }

    @Override
    public Retryer clone() {
        return new DefaultRetryer(period, maxPeriod, maxAttempts);
    }

    @Override
    public long getDelay(RetryableException e, int attempt) {
        // 計算重試延遲
        return Math.min(period * (long) Math.pow(2, attempt), maxPeriod);
    }

    @Override
    public boolean shouldRetry(RetryableException e, int attempt, int retry) {
        // 根據異常類型和重試次數決定是否重試
        return attempt < retry;
    }
}

在這個實現中,DefaultRetryer 使用指數退避策略來計算重試延遲,並允許指定最大重試次數。

步驟3:自定義 Retryer 實現

當咱們需要更復雜的重試策略時,可以創建自定義的 Retryer 實現。例如,可以基於特定的異常類型或響應碼來決定重試策略:

public class CustomRetryer implements Retryer {
    // ... 自定義重試邏輯 ...

    @Override
    public void continueOrPropagate(RetryableException e) {
        // 自定義重試邏輯
    }

    @Override
    public Retryer clone() {
        return new CustomRetryer();
    }

    @Override
    public long getDelay(RetryableException e, int attempt) {
        // 自定義延遲邏輯
        return ...;
    }

    @Override
    public boolean shouldRetry(RetryableException e, int attempt, int retry) {
        // 自定義重試條件
        return ...;
    }
}

步驟4:配置 Feign 客户端使用自定義 Retryer

在Feign配置中,可以指定自定義的 Retryer 實現:

@Configuration
public class FeignConfig {
    @Bean
    public Retryer retryer() {
        return new CustomRetryer();
    }
}

然後在 @FeignClient 註解中指定配置類:

@FeignClient(name = "myClient", configuration = FeignConfig.class)
public interface MyClient {
    // ...
}

小結一下

OpenFeign 允許我們根據需要選擇或實現適合自己業務場景的重試策略,從而提高系統的健壯性和可靠性。問題來了,啥是指數退避策略?

解釋一下哈,指數退避策略(Exponential Backoff)是一種在網絡通信和分佈式系統中常用的重試策略,特別是在處理臨時故障或網絡延遲時。這種策略旨在通過增加連續重試之間的等待時間來減少系統的負載,並提高重試成功的機會。

指數退避策略的工作原理是這樣的:當發生錯誤或故障時,系統首先會等待一個初始的短暫延遲,然後重試。如果第一次重試失敗,等待時間會指數增長。這意味着每次重試的等待時間都是前一次的兩倍(或另一個指數因子)。

通常會設置一個最大嘗試次數,以防止無限重試。為了避免等待時間過長,會設定一個最大延遲時間,超過這個時間後,即使重試次數沒有達到最大次數,也不會再增加等待時間。

為了減少多個客户端同時重試時的同步效應,有時會在指數退避中加入隨機化因子,使得每次的等待時間在一定範圍內變化。

7. Configuration:

Configuration 組件是一個關鍵的設置類,它允許用户自定義Feign客户端的行為。Configuration 類通常包含了一系列的設置,比如連接超時、讀取超時、重試策略、編碼器、解碼器、契約(Contract)、日誌級別等。這些設置可以應用於所有的Feign客户端,或者特定的Feign客户端。

步驟1:定義 Configuration 接口

Feign定義了一個 Configuration 接口,該接口允許用户配置Feign客户端的各種參數:

public interface Configuration {
    // 返回配置的編碼器
    Encoder encoder();

    // 返回配置的解碼器
    Decoder decoder();

    // 返回配置的契約
    Contract contract();

    // 返回配置的請求攔截器
    RequestInterceptor requestInterceptor();

    // 返回配置的重試策略
    Retryer retryer();

    // 返回配置的日誌級別
    Logger.Level loggerLevel();
    
    // ... 可能還有其他配置方法 ...
}

步驟2:實現默認 Configuration

Feign提供了一個默認的 Configuration 實現,這個實現包含了Feign的默認行為:

public class DefaultConfiguration implements Configuration {
    // ... 定義默認的編碼器、解碼器、契約等 ...

    @Override
    public Encoder encoder() {
        return new JacksonEncoder(...);
    }

    @Override
    public Decoder decoder() {
        return new JacksonDecoder(...);
    }

    @Override
    public Contract contract() {
        return new SpringMvcContract(...);
    }

    // ... 實現其他配置方法 ...
}

在這個實現中,DefaultConfiguration 定義了Feign的默認編碼器、解碼器、契約等組件。

步驟3:自定義 Configuration 實現

用户可以創建自定義的 Configuration 實現,以覆蓋默認的行為:

public class CustomConfiguration extends DefaultConfiguration {
    // ... 自定義特定的配置 ...

    @Override
    public Encoder encoder() {
        // 返回自定義的編碼器
        return new CustomEncoder(...);
    }

    @Override
    public Decoder decoder() {
        // 返回自定義的解碼器
        return new CustomDecoder(...);
    }

    // ... 可以覆蓋其他配置方法 ...
}

步驟4:配置 Feign 客户端使用自定義 Configuration

在Feign配置中,可以指定自定義的 Configuration 實現:

@Configuration
public class FeignConfig {
    @Bean
    public Configuration feignConfiguration() {
        return new CustomConfiguration(...);
    }
}

然後在 @FeignClient 註解中指定配置類:

@FeignClient(name = "myClient", configuration = FeignConfig.class)
public interface MyClient {
    // ...
}

8. Target:

Target 組件代表了Feign客户端將要調用的遠程服務的目標。它通常包含了服務的名稱和可能的特定配置,例如請求的URL。Target 組件在Feign的動態代理機制中扮演着重要角色,因為它定義瞭如何將方法調用轉換為實際的HTTP請求。

步驟1:定義 Target 接口

Feign定義了一個 Target 接口,該接口包含一些關鍵的方法和屬性:

public interface Target<T> {
    String name();

    String url();

    Class<T> type();
}
  • name():返回服務的名稱,通常用於服務發現。
  • url():返回服務的URL,可以是完整的URL或者是一個模板。
  • type():返回Feign客户端接口的類型。

步驟2:實現 Target 接口

Feign提供了 Target 接口的實現,通常是一個名為 HardCodedTarget 的類:

public class HardCodedTarget<T> implements Target<T> {
    private final String name;
    private final String url;
    private final Class<T> type;

    public HardCodedTarget(Class<T> type, String name, String url) {
        this.type = type;
        this.name = name;
        this.url = url;
    }

    @Override
    public String name() {
        return name;
    }

    @Override
    public String url() {
        return url;
    }

    @Override
    public Class<T> type() {
        return type;
    }
}

在這個實現中,HardCodedTarget 通過構造函數接收服務的名稱、URL和接口類型,並提供相應的getter方法。

步驟3:使用 Target 組件

在Feign客户端接口中,可以通過 @FeignClient 註解的 value 屬性指定服務名稱,Feign在內部會使用 Target 來構建代理:

@FeignClient(value = "myService", url = "http://localhost:8080")
public interface MyClient extends MyServiceApi {
    // 定義服務方法
}

Feign會根據註解信息創建一個 HardCodedTarget 實例,並使用它來構建動態代理。

步驟4:動態代理和 Target

Feign使用Java的動態代理機制(是不是哪哪都是動態代理,所以説動態代理很重要)來創建客户端代理。在Feign的 ReflectiveFeign 類中,會使用 InvocationHandlerFactory 來創建 InvocationHandler

public class ReflectiveFeign extends Feign {
    private final InvocationHandlerFactory invocationHandlerFactory;

    public ReflectiveFeign(InvocationHandlerFactory invocationHandlerFactory) {
        this.invocationHandlerFactory = invocationHandlerFactory;
    }

    @Override
    public <T> T newInstance(Target<T> target) {
        // 使用 InvocationHandlerFactory 創建 InvocationHandler
        InvocationHandler invocationHandler = invocationHandlerFactory.create(target);
        // 創建動態代理
        return (T) Proxy.newProxyInstance(target.type().getClassLoader(),
                new Class<?>[]{target.type()}, invocationHandler);
    }
}

在這個過程中,InvocationHandler 會使用 Target 來構建實際的HTTP請求。

9. InvocationHandlerFactory:

InvocationHandlerFactory 組件是動態代理的核心,它負責創建 InvocationHandler,這是Java動態代理機制的關鍵部分。InvocationHandler 定義了代理對象在被調用時的行為。在Feign的上下文中,InvocationHandler 負責將方法調用轉換為HTTP請求。

步驟1:定義 InvocationHandlerFactory 接口

Feign定義了一個 InvocationHandlerFactory 接口,該接口包含一個方法,用於創建 InvocationHandler

public interface InvocationHandlerFactory {
    InvocationHandler create(Target target);
}
  • Target:代表Feign客户端的目標,包含了服務的名稱、URL和接口類型。

步驟2:實現 InvocationHandlerFactory

Feign提供了一個默認的 InvocationHandlerFactory 實現,通常是一個名為 ReflectiveInvocationHandlerFactory 的類:

public class ReflectiveInvocationHandlerFactory implements InvocationHandlerFactory {
    @Override
    public InvocationHandler create(Target target) {
        return new ReflectiveInvocationHandler(target);
    }
}

在這個實現中,ReflectiveInvocationHandlerFactory 創建了一個 ReflectiveInvocationHandler 實例,這個 InvocationHandler 會處理反射調用。

步驟3:實現 InvocationHandler

ReflectiveInvocationHandlerInvocationHandler 接口的一個實現,它負責將方法調用轉換為HTTP請求:

public class ReflectiveInvocationHandler implements InvocationHandler {
    private final Target<?> target;
    private final FeignClientFactory feignClientFactory;

    public ReflectiveInvocationHandler(Target<?> target, FeignClientFactory feignClientFactory) {
        this.target = target;
        this.feignClientFactory = feignClientFactory;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        // 根據方法和參數構建RequestTemplate
        RequestTemplate template = buildTemplateFromArgs(target, method, args);
        // 發送請求並獲取響應
        Response response = feignClientFactory.create(target).execute(template, options());
        // 根據響應構建返回值
        return decode(response, method.getReturnType());
    }

    private RequestTemplate buildTemplateFromArgs(Target<?> target, Method method, Object[] args) {
        // 構建請求模板邏輯
        // ...
    }

    private Response.Options options() {
        // 獲取請求選項,如超時設置
        // ...
    }

    private Object decode(Response response, Type type) {
        // 將響應解碼為方法返回類型的邏輯
        // ...
    }
}

代碼中我們發現,invoke 方法是核心,它根據目標對象、方法和參數構建一個 RequestTemplate,然後使用 FeignClient 發送請求並獲取響應。

步驟4:配置 Feign 客户端使用自定義 InvocationHandlerFactory

自定義 InvocationHandlerFactory,可以在Feign配置中指定:

@Configuration
public class FeignConfig {
    @Bean
    public InvocationHandlerFactory invocationHandlerFactory() {
        return new CustomInvocationHandlerFactory();
    }
}

然後在 @FeignClient 註解中指定配置類:

@FeignClient(name = "myClient", configuration = FeignConfig.class)
public interface MyClient {
    // ...
}

小結一下

為啥説程序員需要充分理解設計模式的應用,如果在面試時問你任何關於設計模式的問題,請不要只講概念、不要只講概念、不要只講概念,還要結合業務場景,或者結合框架源碼的理解來講,講一講解決了什麼問題,這是關鍵所在。

最後

OpenFeign 是 Spring Cloud 生態系統中的一個強大工具,它使得微服務之間的通信變得更加簡單和高效。通過使用 OpenFeign,開發者可以專注於業務邏輯的實現,而不需要關心底層的 HTTP 通信細節。歡迎關注威哥愛編程,原創不易,求個贊啊兄弟。

user avatar zhuyunbo 頭像 mulavar 頭像 awbeci 頭像 zengh 頭像 xishui_5ac9a340a5484 頭像 chenchaoyang666 頭像 faker_5acebe0256c30 頭像 yangge_5c6804373b5a0 頭像
點贊 8 用戶, 點贊了這篇動態!
點贊

Add a new 評論

Some HTML is okay.