OpenFeign在Spring Cloud中的應(yīng)用:聲明式Web服務(wù)客戶(hù)端詳解

2024-12-27 11:14 更新

OpenFeign 是一個(gè)聲明式的 Web 服務(wù)客戶(hù)端,它使得編寫(xiě) Web 服務(wù)客戶(hù)端變得更加容易。OpenFeign 是在 Spring Cloud 生態(tài)系統(tǒng)中的一個(gè)組件,它整合了 Ribbon(客戶(hù)端負(fù)載均衡器)和 Eureka(服務(wù)發(fā)現(xiàn)組件),從而簡(jiǎn)化了微服務(wù)之間的調(diào)用。

在 SpringCloud 應(yīng)用中,我們經(jīng)常會(huì) 使用 OpenFeign,比如通過(guò)定義一個(gè)接口并使用注解的方式來(lái)創(chuàng)建一個(gè) Web 服務(wù)客戶(hù)端,而不需要編寫(xiě)大量的模板代碼。OpenFeign 會(huì)自動(dòng)生成接口的實(shí)現(xiàn)類(lèi),并使用 Ribbon 來(lái)調(diào)用相應(yīng)的服務(wù)。

我們先來(lái)上手用一下,在 Spring Cloud 項(xiàng)目中使用 OpenFeign:

需求:我們的業(yè)務(wù)場(chǎng)景是這樣的:一個(gè)電子商務(wù)平臺(tái),其中包含一個(gè)商品服務(wù)(product-service)和一個(gè)訂單服務(wù)(order-service)。我們要使用 OpenFeign 來(lái)實(shí)現(xiàn)訂單服務(wù)調(diào)用商品服務(wù)的接口。

步驟1:創(chuàng)建商品服務(wù)(product-service)

  1. 添加依賴(lài)pom.xml):

   <dependencies>
       <!-- Spring Boot Web 依賴(lài) -->
       <dependency>
           <groupId>org.springframework.boot</groupId>
           <artifactId>spring-boot-starter-web</artifactId>
       </dependency>
       <!-- Spring Boot Actuator 依賴(lài) -->
       <dependency>
           <groupId>org.springframework.boot</groupId>
           <artifactId>spring-boot-starter-actuator</artifactId>
       </dependency>
   </dependencies>

  1. 主應(yīng)用類(lèi)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);
       }
   }

  1. 商品控制器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) {
           // 模擬數(shù)據(jù)庫(kù)中獲取商品信息
           return "Product with ID: " + id;
       }
   }

步驟2:創(chuàng)建訂單服務(wù)(order-service)

  1. 添加依賴(lài)pom.xml):

   <dependencies>
       <!-- Spring Boot Web 依賴(lài) -->
       <dependency>
           <groupId>org.springframework.boot</groupId>
           <artifactId>spring-boot-starter-web</artifactId>
       </dependency>
       <!-- Spring Cloud OpenFeign 依賴(lài) -->
       <dependency>
           <groupId>org.springframework.cloud</groupId>
           <artifactId>spring-cloud-starter-openfeign</artifactId>
       </dependency>
   </dependencies>

  1. 主應(yīng)用類(lèi)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);
       }
   }

  1. Feign 客戶(hù)端接口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);
   }

  1. 訂單控制器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) {
           // 調(diào)用商品服務(wù)獲取商品信息
           return productClient.getProduct(id);
       }
   }

步驟3:運(yùn)行和測(cè)試

  1. 啟動(dòng)商品服務(wù)ProductApplication):
    • 運(yùn)行 ProductApplicationmain 方法。

  1. 啟動(dòng)訂單服務(wù)OrderApplication):
    • 運(yùn)行 OrderApplicationmain 方法。

  1. 測(cè)試調(diào)用
    • 使用瀏覽器或 Postman 訪問(wèn) http://localhost:8082/orders/1/product,我們就能看到商品服務(wù)返回的商品信息。

以上是OpenFeign的基本使用,作為優(yōu)秀的程序員,我們必須要去深入了解OpenFeign核心組件背后的實(shí)現(xiàn),知己知彼,方能百戰(zhàn)不殆。下面我們來(lái)一起看下 OpenFeign 的一些核心組件及其源碼分析:

OpenFeign 的核心組件有哪些?

OpenFeign 是 Spring Cloud 生態(tài)系統(tǒng)中的一個(gè)聲明式 Web 服務(wù)客戶(hù)端,用于簡(jiǎn)化微服務(wù)之間的 HTTP 調(diào)用。

1. Encoder:

在 OpenFeign 中,Encoder 組件負(fù)責(zé)將請(qǐng)求數(shù)據(jù)序列化成可以發(fā)送的格式。默認(rèn)情況下,OpenFeign 只支持將請(qǐng)求數(shù)據(jù)序列化為字符串或字節(jié)數(shù)組。如果需要支持更復(fù)雜的對(duì)象序列化,可以通過(guò)實(shí)現(xiàn)自定義的 Encoder 來(lái)實(shí)現(xiàn)。

我們來(lái)分析一下 Encoder 組件的源碼實(shí)現(xiàn):

步驟1:定義 Encoder 接口

首先,F(xiàn)eign定義了一個(gè) Encoder 接口,該接口包含一個(gè) encode 方法,用于將對(duì)象序列化為字節(jié)數(shù)組:

public interface Encoder {
    void encode(Object object, Type bodyType, RequestTemplate template) throws EncodeException;
}

  • object:要序列化的對(duì)象。
  • bodyType:對(duì)象的類(lèi)型信息,通常用于確定如何序列化對(duì)象。
  • templateRequestTemplate 對(duì)象,用于設(shè)置請(qǐng)求的主體(body)。

步驟2:實(shí)現(xiàn)默認(rèn) Encoder

OpenFeign 提供了一個(gè)默認(rèn)的 Encoder 實(shí)現(xiàn),通常使用 Jackson 或其他 JSON 庫(kù)來(lái)序列化對(duì)象為 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 {
        // 將對(duì)象序列化為 JSON 格式,并設(shè)置到 RequestTemplate 中
        byte[] body = this.objectFactory.createInstance(bodyType).writeValueAsBytes(object);
        template.body(body);
        template.requestBody(Request.Body.create(body, ContentType.APPLICATION_JSON));
    }
}

在這個(gè)實(shí)現(xiàn)中,JacksonEncoder 使用 Jackson 庫(kù)將對(duì)象序列化為 JSON,并設(shè)置請(qǐng)求的內(nèi)容類(lèi)型為 APPLICATION_JSON

步驟3:自定義 Encoder 實(shí)現(xiàn)

如果我們需要支持其他類(lèi)型的序列化,可以創(chuàng)建自定義的 Encoder 實(shí)現(xiàn)。例如,如果要支持 XML 序列化,可以創(chuàng)建一個(gè)使用 JAXB 或其他 XML 庫(kù)的 Encoder

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


    private String serializeToXml(Object object) {
        // 實(shí)現(xiàn)對(duì)象到 XML 的序列化邏輯
        // ...
        return xml;
    }
}

步驟4:配置 OpenFeign 客戶(hù)端使用自定義 Encoder

在 Feign 客戶(hù)端配置中,可以指定自定義的 Encoder 實(shí)現(xiàn):

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

然后在 @FeignClient 注解中指定配置類(lèi):

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

小結(jié)一下

Encoder 接口,為請(qǐng)求數(shù)據(jù)的序列化提供了一個(gè)擴(kuò)展點(diǎn)。OpenFeign 提供了默認(rèn)的 JacksonEncoder 實(shí)現(xiàn),它使用 Jackson 庫(kù)來(lái)序列化對(duì)象為 JSON 格式。如果想實(shí)現(xiàn)自定義的 Encoder,來(lái)支持其他數(shù)據(jù)格式,如 XML、Protobuf 等也是非常方便靈活的。

2. Decoder:

OpenFeign 的Decoder 組件負(fù)責(zé)將HTTP響應(yīng)體(通常是字節(jié)流)反序列化為Java對(duì)象。默認(rèn)情況下,OpenFeign支持將響應(yīng)體反序列化為字符串或字節(jié)數(shù)組。如果需要支持更復(fù)雜的對(duì)象反序列化,可以通過(guò)實(shí)現(xiàn)自定義的 Decoder 來(lái)實(shí)現(xiàn)。

下面來(lái)分析 Decoder 組件的源碼實(shí)現(xiàn):

步驟1:定義 Decoder 接口

OpenFeign 定義了一個(gè) Decoder 接口,該接口包含一個(gè) decode 方法,用于將響應(yīng)體反序列化為對(duì)象:

public interface Decoder {
    <T> T decode(Response response, Type type) throws IOException, DecodeException;
}

  • T:要反序列化成的對(duì)象類(lèi)型。
  • response:Feign的 Response 對(duì)象,包含了HTTP響應(yīng)的所有信息。
  • type:要反序列化成的對(duì)象的類(lèi)型信息。

步驟2:實(shí)現(xiàn)默認(rèn) Decoder

OpenFeign 提供了一個(gè)默認(rèn)的 Decoder 實(shí)現(xiàn),通常使用 Jackson 或其他 JSON 庫(kù)來(lái)反序列化JSON響應(yīng)體:

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 {
        // 從響應(yīng)中獲取字節(jié)流
        InputStream inputStream = response.body().asInputStream();
        // 使用 Jackson 庫(kù)將字節(jié)流反序列化為 Java 對(duì)象
        return this.objectFactory.createInstance(type).readValue(inputStream, type);
    }
}

在這個(gè)實(shí)現(xiàn)中,JacksonDecoder 使用 Jackson 庫(kù)將響應(yīng)體的字節(jié)流反序列化為 Java 對(duì)象,并根據(jù)響應(yīng)的狀態(tài)碼拋出相應(yīng)的異常或返回對(duì)象。

步驟3:自定義 Decoder 實(shí)現(xiàn)

如果咱們需要支持其他類(lèi)型的反序列化,可以創(chuàng)建自定義的 Decoder 實(shí)現(xiàn)。比如要支持 XML 反序列化,可以創(chuàng)建一個(gè)使用 JAXB 或其他 XML 庫(kù)的 Decoder

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


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

步驟4:配置 OpenFeign 客戶(hù)端使用自定義 Decoder

在 Feign 客戶(hù)端配置中,可以指定自定義的 Decoder 實(shí)現(xiàn):

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

然后在 @FeignClient 注解中指定配置類(lèi):

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

小結(jié)一下

OpenFeign 提供了默認(rèn)的 JacksonDecoder 實(shí)現(xiàn),它使用 Jackson 庫(kù)來(lái)反序列化 JSON 格式的響應(yīng)體。咱們還可以通過(guò)實(shí)現(xiàn)自定義的 Decoder,可以支持其他數(shù)據(jù)格式,如 XML等。開(kāi)發(fā)中,咱們可以根據(jù)需要選擇或?qū)崿F(xiàn)適合自己業(yè)務(wù)場(chǎng)景的反序列化方式。

3. Contract:

OpenFeign的Contract 組件負(fù)責(zé)將接口的方法和注解轉(zhuǎn)換為HTTP請(qǐng)求。它定義了如何將Java接口映射到HTTP請(qǐng)求上,包括請(qǐng)求的URL、HTTP方法、請(qǐng)求頭、查詢(xún)參數(shù)和請(qǐng)求體等。Contract 組件是Feign中非常核心的部分,因?yàn)樗鼪Q定了Feign客戶(hù)端如何理解和構(gòu)建HTTP請(qǐng)求。

步驟1:定義 Contract 接口

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

public interface Contract {
    List<MethodMetadata> parseAndValidatteMethods(FeignTarget<?> target);
    RequestTemplate create(Request request, Target<?> target, Method method, Object... argv);
}

  • parseAndValidatteMethods:解析并驗(yàn)證目標(biāo)接口的方法,生成 MethodMetadata 列表,每個(gè) MethodMetadata 包含一個(gè)方法的所有元數(shù)據(jù)。
  • create:根據(jù) RequestTarget 創(chuàng)建 RequestTemplate,用于構(gòu)建實(shí)際的HTTP請(qǐng)求。

步驟2:實(shí)現(xiàn)默認(rèn) Contract

Feign提供了一個(gè)默認(rèn)的 Contract 實(shí)現(xiàn),通常使用Java的反射API來(lái)解析接口的方法和注解:

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


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

在實(shí)現(xiàn)中咱們可以看到,FeignContract 會(huì)檢查接口方法上的注解(如 @GetMapping、@PostMapping 等),并根據(jù)這些注解構(gòu)建HTTP請(qǐng)求。

步驟3:自定義 Contract 實(shí)現(xiàn)

同樣的道理,如果需要支持自定義的注解或擴(kuò)展Feign的功能,可以通過(guò)實(shí)現(xiàn)自定義的 Contract 來(lái)實(shí)現(xiàn):

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


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

步驟4:配置 OpenFeign 客戶(hù)端使用自定義 Contract

在Feign客戶(hù)端配置中,可以指定自定義的 Contract 實(shí)現(xiàn):

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

然后在 @FeignClient 注解中指定配置類(lèi):

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

小結(jié)一下

OpenFeign 提供了默認(rèn)的 FeignContract 實(shí)現(xiàn),它使用Java的反射API來(lái)解析接口的方法和注解,并生成 MethodMetadata。這種方式允許Feign自動(dòng)處理標(biāo)準(zhǔn)的JAX-RS注解。咱們可以通過(guò)實(shí)現(xiàn)自定義的 Contract,可以支持自定義注解或改變Feign的請(qǐng)求構(gòu)建邏輯。

4. Client:

Client 組件是負(fù)責(zé)發(fā)送HTTP請(qǐng)求并接收HTTP響應(yīng)的核心組件。OpenFeign 默認(rèn)使用Java標(biāo)準(zhǔn)庫(kù)中的HttpURLConnection來(lái)發(fā)送請(qǐng)求,但也支持使用其他HTTP客戶(hù)端庫(kù),如Apache HttpClient或OkHttp。

步驟1:定義 Client 接口

OpenFeign中定義了一個(gè)Client接口,該接口包含一個(gè)execute方法,用于執(zhí)行HTTP請(qǐng)求:

public interface Client {
    Response execute(Request request, Request.Options options) throws IOException;
}

  • Request:代表要執(zhí)行的HTTP請(qǐng)求。
  • Request.Options:包含請(qǐng)求的配置選項(xiàng),如連接超時(shí)和讀取超時(shí)。
  • Response:代表HTTP響應(yīng)。

步驟2:實(shí)現(xiàn)默認(rèn) Client

OpenFeign 提供了一個(gè)默認(rèn)的Client實(shí)現(xiàn),使用Java的HttpURLConnection

public class HttpURLConnectionClient implements Client {
    @Override
    public Response execute(Request request, Request.Options options) throws IOException {
        // 創(chuàng)建和配置 HttpURLConnection
        URL url = new URL(request.url());
        HttpURLConnection connection = (HttpURLConnection) url.openConnection();
        // 設(shè)置請(qǐng)求方法、頭信息、超時(shí)等
        // ...
        // 發(fā)送請(qǐng)求并獲取響應(yīng)
        // ...
        return response;
    }
}

在這個(gè)實(shí)現(xiàn)中,HttpURLConnectionClient使用HttpURLConnection來(lái)構(gòu)建和發(fā)送HTTP請(qǐng)求,并處理響應(yīng)。

步驟3:自定義 Client 實(shí)現(xiàn)

如果我們需要使用其他HTTP客戶(hù)端庫(kù),可以創(chuàng)建自定義的Client實(shí)現(xiàn)。例如,使用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 構(gòu)建和發(fā)送HTTP請(qǐng)求
        // ...
        return response;
    }
}

步驟4:配置 Feign 客戶(hù)端使用自定義 Client

在Feign配置中,可以指定自定義的Client實(shí)現(xiàn):

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

然后在@FeignClient注解中指定配置類(lèi):

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

小結(jié)一下

OpenFeign 提供了默認(rèn)的HttpURLConnectionClient實(shí)現(xiàn),它使用Java標(biāo)準(zhǔn)庫(kù)中的HttpURLConnection來(lái)發(fā)送請(qǐng)求。這種方式的好處是簡(jiǎn)單且無(wú)需額外依賴(lài)。也可以通過(guò)實(shí)現(xiàn)自定義的Client,如Apache HttpClient或OkHttp。這讓OpenFeign能夠適應(yīng)不同的性能和功能需求。

5. RequestInterceptor:

RequestInterceptor 是一個(gè)非常重要的組件,它允許咱們?cè)谡?qǐng)求發(fā)送之前對(duì)其進(jìn)行攔截,從而可以添加一些通用的處理邏輯,比如設(shè)置認(rèn)證頭、日志記錄、修改請(qǐng)求參數(shù)等。我們來(lái)分析一下 RequestInterceptor 組件的源碼實(shí)現(xiàn):

步驟1:定義 RequestInterceptor 接口

Feign定義了一個(gè) RequestInterceptor 接口,該接口包含一個(gè) apply 方法,用于在請(qǐng)求發(fā)送前對(duì) RequestTemplate 進(jìn)行操作:

public interface RequestInterceptor {
    void apply(RequestTemplate template);
}

  • RequestTemplate:代表即將發(fā)送的HTTP請(qǐng)求,可以修改URL、頭信息、請(qǐng)求體等。

步驟2:實(shí)現(xiàn)默認(rèn) RequestInterceptor

OpenFeign 提供了一些默認(rèn)的 RequestInterceptor 實(shí)現(xiàn),例如用于設(shè)置默認(rèn)頭信息的 RequestInterceptor

public class DefaultRequestInterceptor implements RequestInterceptor {
    private final Configuration configuration;


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


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

在這個(gè)實(shí)現(xiàn)中,DefaultRequestInterceptor 會(huì)在每個(gè)請(qǐng)求中設(shè)置一些默認(rèn)的頭信息。

步驟3:自定義 RequestInterceptor 實(shí)現(xiàn)

咱們可以根據(jù)需要實(shí)現(xiàn)自定義的 RequestInterceptor。例如,添加一個(gè)用于設(shè)置認(rèn)證頭的攔截器:

public class AuthRequestInterceptor implements RequestInterceptor {
    private final String authToken;


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


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

步驟4:配置 OpenFeign 客戶(hù)端使用自定義 RequestInterceptor

在 OpenFeign 配置中,可以指定自定義的 RequestInterceptor 實(shí)現(xiàn):

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

然后在 @FeignClient 注解中指定配置類(lèi):

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

小結(jié)一下

RequestInterceptor讓咱們?cè)诓恍薷拿總€(gè)單獨(dú)請(qǐng)求的情況下,統(tǒng)一處理請(qǐng)求。這使得Feign客戶(hù)端更加靈活和強(qiáng)大,能夠適應(yīng)各種復(fù)雜的業(yè)務(wù)需求。

6. Retryer:

Retryer 組件負(fù)責(zé)定義重試策略,它決定了在遇到特定類(lèi)型的錯(cuò)誤時(shí)是否重試請(qǐng)求,以及重試的次數(shù)和間隔。Retryer 是Feign中的一個(gè)重要組件,特別是在網(wǎng)絡(luò)不穩(wěn)定或服務(wù)不穩(wěn)定的環(huán)境中,大派用場(chǎng),它可以顯著提高系統(tǒng)的健壯性哦。

步驟1:定義 Retryer 接口

Feign定義了一個(gè) Retryer 接口,該接口包含幾個(gè)關(guān)鍵的方法,用于控制重試的行為:

public interface Retryer {
    void continueOrPropagate(RetryableException e);
    Retryer clone();
    long getDelay(RetryableException e, int attempt);
    boolean shouldRetry(RetryableException e, int attempt, int retry);
}

  • continueOrPropagate:決定是繼續(xù)重試還是拋出異常。
  • clone:創(chuàng)建 Retryer 的副本,通常用于每個(gè)請(qǐng)求的獨(dú)立重試策略。
  • getDelay:返回在下一次重試之前的延遲時(shí)間。
  • shouldRetry:決定是否應(yīng)該重試請(qǐng)求。

步驟2:實(shí)現(xiàn)默認(rèn) Retryer

Feign提供了一個(gè)默認(rèn)的 Retryer 實(shí)現(xiàn),通常是一個(gè)簡(jiǎn)單的重試策略,例如:

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) {
        // 根據(jù)異常類(lèi)型和重試次數(shù)決定是否重試
        if (shouldRetry(e, e.getAttempt(), maxAttempts)) {
            // 繼續(xù)重試
        } else {
            // 拋出異常
            throw e;
        }
    }


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


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


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

在這個(gè)實(shí)現(xiàn)中,DefaultRetryer 使用指數(shù)退避策略來(lái)計(jì)算重試延遲,并允許指定最大重試次數(shù)。

步驟3:自定義 Retryer 實(shí)現(xiàn)

當(dāng)咱們需要更復(fù)雜的重試策略時(shí),可以創(chuàng)建自定義的 Retryer 實(shí)現(xiàn)。例如,可以基于特定的異常類(lèi)型或響應(yīng)碼來(lái)決定重試策略:

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 客戶(hù)端使用自定義 Retryer

在Feign配置中,可以指定自定義的 Retryer 實(shí)現(xiàn):

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

然后在 @FeignClient 注解中指定配置類(lèi):

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

小結(jié)一下

OpenFeign 允許我們根據(jù)需要選擇或?qū)崿F(xiàn)適合自己業(yè)務(wù)場(chǎng)景的重試策略,從而提高系統(tǒng)的健壯性和可靠性。問(wèn)題來(lái)了,啥是指數(shù)退避策略?

解釋一下哈,指數(shù)退避策略(Exponential Backoff)是一種在網(wǎng)絡(luò)通信和分布式系統(tǒng)中常用的重試策略,特別是在處理臨時(shí)故障或網(wǎng)絡(luò)延遲時(shí)。這種策略旨在通過(guò)增加連續(xù)重試之間的等待時(shí)間來(lái)減少系統(tǒng)的負(fù)載,并提高重試成功的機(jī)會(huì)。

指數(shù)退避策略的工作原理是這樣的:當(dāng)發(fā)生錯(cuò)誤或故障時(shí),系統(tǒng)首先會(huì)等待一個(gè)初始的短暫延遲,然后重試。如果第一次重試失敗,等待時(shí)間會(huì)指數(shù)增長(zhǎng)。這意味著每次重試的等待時(shí)間都是前一次的兩倍(或另一個(gè)指數(shù)因子)。

通常會(huì)設(shè)置一個(gè)最大嘗試次數(shù),以防止無(wú)限重試。為了避免等待時(shí)間過(guò)長(zhǎng),會(huì)設(shè)定一個(gè)最大延遲時(shí)間,超過(guò)這個(gè)時(shí)間后,即使重試次數(shù)沒(méi)有達(dá)到最大次數(shù),也不會(huì)再增加等待時(shí)間。

為了減少多個(gè)客戶(hù)端同時(shí)重試時(shí)的同步效應(yīng),有時(shí)會(huì)在指數(shù)退避中加入隨機(jī)化因子,使得每次的等待時(shí)間在一定范圍內(nèi)變化。

7. Configuration:

Configuration 組件是一個(gè)關(guān)鍵的設(shè)置類(lèi),它允許用戶(hù)自定義Feign客戶(hù)端的行為。Configuration 類(lèi)通常包含了一系列的設(shè)置,比如連接超時(shí)、讀取超時(shí)、重試策略、編碼器、解碼器、契約(Contract)、日志級(jí)別等。這些設(shè)置可以應(yīng)用于所有的Feign客戶(hù)端,或者特定的Feign客戶(hù)端。

步驟1:定義 Configuration 接口

Feign定義了一個(gè) Configuration 接口,該接口允許用戶(hù)配置Feign客戶(hù)端的各種參數(shù):

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


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


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


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


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


    // 返回配置的日志級(jí)別
    Logger.Level loggerLevel();

    
    // ... 可能還有其他配置方法 ...
}

步驟2:實(shí)現(xiàn)默認(rèn) Configuration

Feign提供了一個(gè)默認(rèn)的 Configuration 實(shí)現(xiàn),這個(gè)實(shí)現(xiàn)包含了Feign的默認(rèn)行為:

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


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


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


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


    // ... 實(shí)現(xiàn)其他配置方法 ...
}

在這個(gè)實(shí)現(xiàn)中,DefaultConfiguration 定義了Feign的默認(rèn)編碼器、解碼器、契約等組件。

步驟3:自定義 Configuration 實(shí)現(xiàn)

用戶(hù)可以創(chuàng)建自定義的 Configuration 實(shí)現(xiàn),以覆蓋默認(rèn)的行為:

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


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


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


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

步驟4:配置 Feign 客戶(hù)端使用自定義 Configuration

在Feign配置中,可以指定自定義的 Configuration 實(shí)現(xiàn):

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

然后在 @FeignClient 注解中指定配置類(lèi):

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

8. Target:

Target 組件代表了Feign客戶(hù)端將要調(diào)用的遠(yuǎn)程服務(wù)的目標(biāo)。它通常包含了服務(wù)的名稱(chēng)和可能的特定配置,例如請(qǐng)求的URL。Target 組件在Feign的動(dòng)態(tài)代理機(jī)制中扮演著重要角色,因?yàn)樗x了如何將方法調(diào)用轉(zhuǎn)換為實(shí)際的HTTP請(qǐng)求。

步驟1:定義 Target 接口

Feign定義了一個(gè) Target 接口,該接口包含一些關(guān)鍵的方法和屬性:

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


    String url();


    Class<T> type();
}

  • name():返回服務(wù)的名稱(chēng),通常用于服務(wù)發(fā)現(xiàn)。
  • url():返回服務(wù)的URL,可以是完整的URL或者是一個(gè)模板。
  • type():返回Feign客戶(hù)端接口的類(lèi)型。

步驟2:實(shí)現(xiàn) Target 接口

Feign提供了 Target 接口的實(shí)現(xiàn),通常是一個(gè)名為 HardCodedTarget 的類(lèi):

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;
    }
}

在這個(gè)實(shí)現(xiàn)中,HardCodedTarget 通過(guò)構(gòu)造函數(shù)接收服務(wù)的名稱(chēng)、URL和接口類(lèi)型,并提供相應(yīng)的getter方法。

步驟3:使用 Target 組件

在Feign客戶(hù)端接口中,可以通過(guò) @FeignClient 注解的 value 屬性指定服務(wù)名稱(chēng),F(xiàn)eign在內(nèi)部會(huì)使用 Target 來(lái)構(gòu)建代理:

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

Feign會(huì)根據(jù)注解信息創(chuàng)建一個(gè) HardCodedTarget 實(shí)例,并使用它來(lái)構(gòu)建動(dòng)態(tài)代理。

步驟4:動(dòng)態(tài)代理和 Target

Feign使用Java的動(dòng)態(tài)代理機(jī)制(是不是哪哪都是動(dòng)態(tài)代理,所以說(shuō)動(dòng)態(tài)代理很重要)來(lái)創(chuàng)建客戶(hù)端代理。在Feign的 ReflectiveFeign 類(lèi)中,會(huì)使用 InvocationHandlerFactory 來(lái)創(chuàng)建 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 創(chuàng)建 InvocationHandler
        InvocationHandler invocationHandler = invocationHandlerFactory.create(target);
        // 創(chuàng)建動(dòng)態(tài)代理
        return (T) Proxy.newProxyInstance(target.type().getClassLoader(),
                new Class<?>[]{target.type()}, invocationHandler);
    }
}

在這個(gè)過(guò)程中,InvocationHandler 會(huì)使用 Target 來(lái)構(gòu)建實(shí)際的HTTP請(qǐng)求。

9. InvocationHandlerFactory:

InvocationHandlerFactory 組件是動(dòng)態(tài)代理的核心,它負(fù)責(zé)創(chuàng)建 InvocationHandler,這是Java動(dòng)態(tài)代理機(jī)制的關(guān)鍵部分。InvocationHandler 定義了代理對(duì)象在被調(diào)用時(shí)的行為。在Feign的上下文中,InvocationHandler 負(fù)責(zé)將方法調(diào)用轉(zhuǎn)換為HTTP請(qǐng)求。

步驟1:定義 InvocationHandlerFactory 接口

Feign定義了一個(gè) InvocationHandlerFactory 接口,該接口包含一個(gè)方法,用于創(chuàng)建 InvocationHandler

public interface InvocationHandlerFactory {
    InvocationHandler create(Target target);
}

  • Target:代表Feign客戶(hù)端的目標(biāo),包含了服務(wù)的名稱(chēng)、URL和接口類(lèi)型。

步驟2:實(shí)現(xiàn) InvocationHandlerFactory

Feign提供了一個(gè)默認(rèn)的 InvocationHandlerFactory 實(shí)現(xiàn),通常是一個(gè)名為 ReflectiveInvocationHandlerFactory 的類(lèi):

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

在這個(gè)實(shí)現(xiàn)中,ReflectiveInvocationHandlerFactory 創(chuàng)建了一個(gè) ReflectiveInvocationHandler 實(shí)例,這個(gè) InvocationHandler 會(huì)處理反射調(diào)用。

步驟3:實(shí)現(xiàn) InvocationHandler

ReflectiveInvocationHandlerInvocationHandler 接口的一個(gè)實(shí)現(xiàn),它負(fù)責(zé)將方法調(diào)用轉(zhuǎn)換為HTTP請(qǐng)求:

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 {
        // 根據(jù)方法和參數(shù)構(gòu)建RequestTemplate
        RequestTemplate template = buildTemplateFromArgs(target, method, args);
        // 發(fā)送請(qǐng)求并獲取響應(yīng)
        Response response = feignClientFactory.create(target).execute(template, options());
        // 根據(jù)響應(yīng)構(gòu)建返回值
        return decode(response, method.getReturnType());
    }


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


    private Response.Options options() {
        // 獲取請(qǐng)求選項(xiàng),如超時(shí)設(shè)置
        // ...
    }


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

代碼中我們發(fā)現(xiàn),invoke 方法是核心,它根據(jù)目標(biāo)對(duì)象、方法和參數(shù)構(gòu)建一個(gè) RequestTemplate,然后使用 FeignClient 發(fā)送請(qǐng)求并獲取響應(yīng)。

步驟4:配置 Feign 客戶(hù)端使用自定義 InvocationHandlerFactory

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

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

然后在 @FeignClient 注解中指定配置類(lèi):

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

小結(jié)一下

為啥說(shuō)程序員需要充分理解設(shè)計(jì)模式的應(yīng)用,如果在面試時(shí)問(wèn)你任何關(guān)于設(shè)計(jì)模式的問(wèn)題,請(qǐng)不要只講概念、不要只講概念、不要只講概念,還要結(jié)合業(yè)務(wù)場(chǎng)景,或者結(jié)合框架源碼的理解來(lái)講,講一講解決了什么問(wèn)題,這是關(guān)鍵所在。

最后

OpenFeign 是 Spring Cloud 生態(tài)系統(tǒng)中的一個(gè)強(qiáng)大工具,它使得微服務(wù)之間的通信變得更加簡(jiǎn)單和高效。通過(guò)使用 OpenFeign,開(kāi)發(fā)者可以專(zhuān)注于業(yè)務(wù)邏輯的實(shí)現(xiàn),而不需要關(guān)心底層的 HTTP 通信細(xì)節(jié)。歡迎關(guān)注威哥愛(ài)編程,原創(chuàng)不易,求個(gè)贊啊兄弟。

以上內(nèi)容是否對(duì)您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號(hào)
微信公眾號(hào)

編程獅公眾號(hào)