OpenFeign深入学习条记

打印 上一主题 下一主题

主题 857|帖子 857|积分 2571

OpenFeign 是一个声明式的 Web 服务客户端,它使得编写 Web 服务客户端变得更加容易。OpenFeign 是在 Spring Cloud 生态体系中的一个组件,它整合了 Ribbon(客户端负载均衡器)和 Eureka(服务发现组件),从而简化了微服务之间的调用。
在 SpringCloud 应用中,我们经常会 使用 OpenFeign,比如通过定义一个接口并使用注解的方式来创建一个 Web 服务客户端,而不需要编写大量的模板代码。OpenFeign 会自动生成接口的实现类,并使用 Ribbon 来调用相应的服务。
我们先来上手用一下,在 Spring Cloud 项目中使用 OpenFeign:
需求:我们的业务场景是如许的:一个电子商务平台,其中包含一个商品服务(product-service)和一个订单服务(order-service)。我们要使用 OpenFeign 来实现订单服务调用商品服务的接口。
步骤1:创建商品服务(product-service)


  • 添加依赖(pom.xml):
  1.    <dependencies>
  2.       
  3.        <dependency>
  4.            <groupId>org.springframework.boot</groupId>
  5.            <artifactId>spring-boot-starter-web</artifactId>
  6.        </dependency>
  7.       
  8.        <dependency>
  9.            <groupId>org.springframework.boot</groupId>
  10.            <artifactId>spring-boot-starter-actuator</artifactId>
  11.        </dependency>
  12.    </dependencies>
复制代码

  • 主应用类(ProductApplication.java):
  1.    import org.springframework.boot.SpringApplication;
  2.    import org.springframework.boot.autoconfigure.SpringBootApplication;
  3.    @SpringBootApplication
  4.    public class ProductApplication {
  5.        public static void main(String[] args) {
  6.            SpringApplication.run(ProductApplication.class, args);
  7.        }
  8.    }
复制代码

  • 商品控制器(ProductController.java):
  1.    import org.springframework.web.bind.annotation.GetMapping;
  2.    import org.springframework.web.bind.annotation.PathVariable;
  3.    import org.springframework.web.bind.annotation.RestController;
  4.    @RestController
  5.    public class ProductController {
  6.        @GetMapping("/products/{id}")
  7.        public String getProduct(@PathVariable("id") Long id) {
  8.            // 模拟数据库中获取商品信息
  9.            return "Product with ID: " + id;
  10.        }
  11.    }
复制代码
步骤2:创建订单服务(order-service)


  • 添加依赖(pom.xml):
  1.    <dependencies>
  2.       
  3.        <dependency>
  4.            <groupId>org.springframework.boot</groupId>
  5.            <artifactId>spring-boot-starter-web</artifactId>
  6.        </dependency>
  7.       
  8.        <dependency>
  9.            <groupId>org.springframework.cloud</groupId>
  10.            <artifactId>spring-cloud-starter-openfeign</artifactId>
  11.        </dependency>
  12.    </dependencies>
复制代码

  • 主应用类(OrderApplication.java):
  1.    import org.springframework.boot.SpringApplication;
  2.    import org.springframework.boot.autoconfigure.SpringBootApplication;
  3.    import org.springframework.cloud.openfeign.EnableFeignClients;
  4.    @SpringBootApplication
  5.    @EnableFeignClients
  6.    public class OrderApplication {
  7.        public static void main(String[] args) {
  8.            SpringApplication.run(OrderApplication.class, args);
  9.        }
  10.    }
复制代码

  • Feign 客户端接口(ProductClient.java):
  1.    import org.springframework.cloud.openfeign.FeignClient;
  2.    import org.springframework.web.bind.annotation.GetMapping;
  3.    import org.springframework.web.bind.annotation.PathVariable;
  4.    @FeignClient(name = "product-service", url = "http://localhost:8081")
  5.    public interface ProductClient {
  6.        @GetMapping("/products/{id}")
  7.        String getProduct(@PathVariable("id") Long id);
  8.    }
复制代码

  • 订单控制器(OrderController.java):
  1.    import org.springframework.beans.factory.annotation.Autowired;
  2.    import org.springframework.web.bind.annotation.GetMapping;
  3.    import org.springframework.web.bind.annotation.PathVariable;
  4.    import org.springframework.web.bind.annotation.RestController;
  5.    @RestController
  6.    public class OrderController {
  7.        private final ProductClient productClient;
  8.        @Autowired
  9.        public OrderController(ProductClient productClient) {
  10.            this.productClient = productClient;
  11.        }
  12.        @GetMapping("/orders/{id}/product")
  13.        public String getOrderProduct(@PathVariable("id") Long id) {
  14.            // 调用商品服务获取商品信息
  15.            return productClient.getProduct(id);
  16.        }
  17.    }
复制代码
步骤3:运行和测试


  • 启动商品服务(ProductApplication):

    • 运行 ProductApplication 的 main 方法。

  • 启动订单服务(OrderApplication):

    • 运行 OrderApplication 的 main 方法。

  • 测试调用

    • 使用欣赏器或 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 方法,用于将对象序列化为字节数组:
  1. public interface Encoder {
  2.     void encode(Object object, Type bodyType, RequestTemplate template) throws EncodeException;
  3. }
复制代码

  • object:要序列化的对象。
  • bodyType:对象的范例信息,通常用于确定如何序列化对象。
  • template:RequestTemplate 对象,用于设置哀求的主体(body)。
步骤2:实现默认 Encoder

OpenFeign 提供了一个默认的 Encoder 实现,通常使用 Jackson 或其他 JSON 库来序列化对象为 JSON 格式:
  1. public class JacksonEncoder extends SpringEncoder implements Encoder {
  2.     public JacksonEncoder(ObjectFactory objectFactory) {
  3.         super(objectFactory);
  4.     }
  5.     @Override
  6.     public void encode(Object object, Type bodyType, RequestTemplate template) throws EncodeException {
  7.         // 将对象序列化为 JSON 格式,并设置到 RequestTemplate 中
  8.         byte[] body = this.objectFactory.createInstance(bodyType).writeValueAsBytes(object);
  9.         template.body(body);
  10.         template.requestBody(Request.Body.create(body, ContentType.APPLICATION_JSON));
  11.     }
  12. }
复制代码
在这个实现中,JacksonEncoder 使用 Jackson 库将对象序列化为 JSON,并设置哀求的内容范例为 APPLICATION_JSON。
步骤3:自定义 Encoder 实现

假如我们需要支持其他范例的序列化,可以创建自定义的 Encoder 实现。例如,假如要支持 XML 序列化,可以创建一个使用 JAXB 或其他 XML 库的 Encoder:
  1. public class XmlEncoder implements Encoder {
  2.     @Override
  3.     public void encode(Object object, Type bodyType, RequestTemplate template) throws EncodeException {
  4.         // 使用 XML 库将对象序列化为 XML 格式
  5.         String xml = serializeToXml(object);
  6.         template.body(xml, ContentType.APPLICATION_XML);
  7.     }
  8.     private String serializeToXml(Object object) {
  9.         // 实现对象到 XML 的序列化逻辑
  10.         // ...
  11.         return xml;
  12.     }
  13. }
复制代码
步骤4:设置 OpenFeign 客户端使用自定义 Encoder

在 Feign 客户端设置中,可以指定自定义的 Encoder 实现:
  1. @Configuration
  2. public class FeignConfig {
  3.     @Bean
  4.     public Encoder encoder() {
  5.         return new XmlEncoder();
  6.     }
  7. }
复制代码
然后在 @FeignClient 注解中指定设置类:
  1. @FeignClient(name = "myClient", configuration = FeignConfig.class)
  2. public interface MyClient {
  3.     // ...
  4. }
复制代码
小结一下

Encoder 接口,为哀求数据的序列化提供了一个扩展点。OpenFeign 提供了默认的 JacksonEncoder 实现,它使用 Jackson 库来序列化对象为 JSON 格式。假如想实现自定义的 Encoder,来支持其他数据格式,如 XML、Protobuf 等也是非常方便灵活的。
2. Decoder:

OpenFeign 的Decoder 组件负责将HTTP响应体(通常是字节流)反序列化为Java对象。默认情况下,OpenFeign支持将响应体反序列化为字符串或字节数组。假如需要支持更复杂的对象反序列化,可以通过实现自定义的 Decoder 来实现。
下面来分析 Decoder 组件的源码实现:
步骤1:定义 Decoder 接口

OpenFeign 定义了一个 Decoder 接口,该接口包含一个 decode 方法,用于将响应体反序列化为对象:
  1. public interface Decoder {
  2.     <T> T decode(Response response, Type type) throws IOException, DecodeException;
  3. }
复制代码

  • T:要反序列化成的对象范例。
  • response:Feign的 Response 对象,包含了HTTP响应的所有信息。
  • type:要反序列化成的对象的范例信息。
步骤2:实现默认 Decoder

OpenFeign 提供了一个默认的 Decoder 实现,通常使用 Jackson 或其他 JSON 库来反序列化JSON响应体:
  1. public class JacksonDecoder extends SpringDecoder implements Decoder {
  2.     public JacksonDecoder(ObjectFactory objectFactory) {
  3.         super(objectFactory);
  4.     }
  5.     @Override
  6.     public <T> T decode(Response response, Type type) throws IOException, DecodeException {
  7.         // 从响应中获取字节流
  8.         InputStream inputStream = response.body().asInputStream();
  9.         // 使用 Jackson 库将字节流反序列化为 Java 对象
  10.         return this.objectFactory.createInstance(type).readValue(inputStream, type);
  11.     }
  12. }
复制代码
在这个实现中,JacksonDecoder 使用 Jackson 库将响应体的字节流反序列化为 Java 对象,并根据响应的状态码抛出相应的异常或返回对象。
步骤3:自定义 Decoder 实现

假如咱们需要支持其他范例的反序列化,可以创建自定义的 Decoder 实现。比如要支持 XML 反序列化,可以创建一个使用 JAXB 或其他 XML 库的 Decoder:
  1. public class XmlDecoder implements Decoder {
  2.     @Override
  3.     public <T> T decode(Response response, Type type) throws IOException, DecodeException {
  4.         // 从响应中获取字节流
  5.         InputStream inputStream = response.body().asInputStream();
  6.         // 使用 XML 库将字节流反序列化为 Java 对象
  7.         T object = deserializeFromXml(inputStream, type);
  8.         return object;
  9.     }
  10.     private <T> T deserializeFromXml(InputStream inputStream, Type type) {
  11.         // 实现 XML 到 Java 对象的反序列化逻辑
  12.         // ...
  13.         return null;
  14.     }
  15. }
复制代码
步骤4:设置 OpenFeign 客户端使用自定义 Decoder

在 Feign 客户端设置中,可以指定自定义的 Decoder 实现:
  1. @Configuration
  2. public class FeignConfig {
  3.     @Bean
  4.     public Decoder decoder() {
  5.         return new XmlDecoder();
  6.     }
  7. }
复制代码
然后在 @FeignClient 注解中指定设置类:
  1. @FeignClient(name = "myClient", configuration = FeignConfig.class)
  2. public interface MyClient {
  3.     // ...
  4. }
复制代码
小结一下

OpenFeign 提供了默认的 JacksonDecoder 实现,它使用 Jackson 库来反序列化 JSON 格式的响应体。咱们还可以通过实现自定义的 Decoder,可以支持其他数据格式,如 XML等。开发中,咱们可以根据需要选择或实现得当本身业务场景的反序列化方式。
3. Contract:

OpenFeign的Contract 组件负责将接口的方法和注解转换为HTTP哀求。它定义了如何将Java接口映射到HTTP哀求上,包括哀求的URL、HTTP方法、哀求头、查询参数和哀求体等。Contract 组件是Feign中非常核心的部门,由于它决定了Feign客户端如何理解和构建HTTP哀求。
步骤1:定义 Contract 接口

Feign定义了一个 Contract 接口,该接口包含两个主要的方法:
  1. public interface Contract {
  2.     List<MethodMetadata> parseAndValidatteMethods(FeignTarget<?> target);
  3.     RequestTemplate create(Request request, Target<?> target, Method method, Object... argv);
  4. }
复制代码

  • parseAndValidatteMethods:解析并验证目标接口的方法,生成 MethodMetadata 列表,每个 MethodMetadata 包含一个方法的所有元数据。
  • create:根据 Request 和 Target 创建 RequestTemplate,用于构建实际的HTTP哀求。
步骤2:实现默认 Contract

Feign提供了一个默认的 Contract 实现,通常使用Java的反射API来解析接口的方法和注解:
  1. public class FeignContract implements Contract {
  2.     @Override
  3.     public List<MethodMetadata> parseAndValidateMethods(FeignTarget<?> target) {
  4.         // 解析目标接口的方法,生成 MethodMetadata 列表
  5.         // ...
  6.     }
  7.     @Override
  8.     public RequestTemplate create(Request request, Target<?> target, Method method, Object... argv) {
  9.         // 根据 MethodMetadata 和参数创建 RequestTemplate
  10.         // ...
  11.     }
  12. }
复制代码
在实现中咱们可以看到,FeignContract 会检查接口方法上的注解(如 @GetMapping、@PostMapping 等),并根据这些注解构建HTTP哀求。
步骤3:自定义 Contract 实现

同样的道理,假如需要支持自定义的注解或扩展Feign的功能,可以通过实现自定义的 Contract 来实现:
  1. public class MyCustomContract implements Contract {
  2.     @Override
  3.     public List<MethodMetadata> parseAndValidateMethods(FeignTarget<?> target) {
  4.         // 自定义解析逻辑
  5.         // ...
  6.     }
  7.     @Override
  8.     public RequestTemplate create(Request request, Target<?> target, Method method, Object... argv) {
  9.         // 自定义创建 RequestTemplate 的逻辑
  10.         // ...
  11.     }
  12. }
复制代码
步骤4:设置 OpenFeign 客户端使用自定义 Contract

在Feign客户端设置中,可以指定自定义的 Contract 实现:
  1. @Configuration
  2. public class FeignConfig {
  3.     @Bean
  4.     public Contract contract() {
  5.         return new MyCustomContract();
  6.     }
  7. }
复制代码
然后在 @FeignClient 注解中指定设置类:
  1. @FeignClient(name = "myClient", configuration = FeignConfig.class)
  2. public interface MyClient {
  3.     // ...
  4. }
复制代码
小结一下

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哀求:
  1. public interface Client {
  2.     Response execute(Request request, Request.Options options) throws IOException;
  3. }
复制代码

  • Request:代表要执行的HTTP哀求。
  • Request.Options:包含哀求的设置选项,如连接超时和读取超时。
  • Response:代表HTTP响应。
步骤2:实现默认 Client

OpenFeign 提供了一个默认的Client实现,使用Java的HttpURLConnection:
  1. public class HttpURLConnectionClient implements Client {
  2.     @Override
  3.     public Response execute(Request request, Request.Options options) throws IOException {
  4.         // 创建和配置 HttpURLConnection
  5.         URL url = new URL(request.url());
  6.         HttpURLConnection connection = (HttpURLConnection) url.openConnection();
  7.         // 设置请求方法、头信息、超时等
  8.         // ...
  9.         // 发送请求并获取响应
  10.         // ...
  11.         return response;
  12.     }
  13. }
复制代码
在这个实现中,HttpURLConnectionClient使用HttpURLConnection来构建和发送HTTP哀求,并处理响应。
步骤3:自定义 Client 实现

假如我们需要使用其他HTTP客户端库,可以创建自定义的Client实现。例如,使用Apache HttpClient:
  1. public class HttpClientClient implements Client {
  2.     private final CloseableHttpClient httpClient;
  3.     public HttpClientClient(CloseableHttpClient httpClient) {
  4.         this.httpClient = httpClient;
  5.     }
  6.     @Override
  7.     public Response execute(Request request, Request.Options options) throws IOException {
  8.         // 使用 Apache HttpClient 构建和发送HTTP请求
  9.         // ...
  10.         return response;
  11.     }
  12. }
复制代码
步骤4:设置 Feign 客户端使用自定义 Client

在Feign设置中,可以指定自定义的Client实现:
  1. @Configuration
  2. public class FeignConfig {
  3.     @Bean
  4.     public Client httpClientClient() {
  5.         CloseableHttpClient httpClient = HttpClients.createDefault();
  6.         return new HttpClientClient(httpClient);
  7.     }
  8. }
复制代码
然后在@FeignClient注解中指定设置类:
  1. @FeignClient(name = "myClient", configuration = FeignConfig.class)
  2. public interface MyClient {
  3.     // ...
  4. }
复制代码
小结一下

OpenFeign 提供了默认的HttpURLConnectionClient实现,它使用Java标准库中的HttpURLConnection来发送哀求。这种方式的利益是简朴且无需额外依赖。也可以通过实现自定义的Client,如Apache HttpClient或OkHttp。这让OpenFeign可以或许适应不同的性能和功能需求。
5. RequestInterceptor:

RequestInterceptor 是一个非常紧张的组件,它允许咱们在哀求发送之前对其进行拦截,从而可以添加一些通用的处理逻辑,比如设置认证头、日记记录、修改哀求参数等。我们来分析一下 RequestInterceptor 组件的源码实现:
步骤1:定义 RequestInterceptor 接口

Feign定义了一个 RequestInterceptor 接口,该接口包含一个 apply 方法,用于在哀求发送前对 RequestTemplate 进行操作:
  1. public interface RequestInterceptor {
  2.     void apply(RequestTemplate template);
  3. }
复制代码

  • RequestTemplate:代表即将发送的HTTP哀求,可以修改URL、头信息、哀求体等。
步骤2:实现默认 RequestInterceptor

OpenFeign 提供了一些默认的 RequestInterceptor 实现,例如用于设置默认头信息的 RequestInterceptor:
  1. public class DefaultRequestInterceptor implements RequestInterceptor {
  2.     private final Configuration configuration;
  3.     public DefaultRequestInterceptor(Configuration configuration) {
  4.         this.configuration = configuration;
  5.     }
  6.     @Override
  7.     public void apply(RequestTemplate template) {
  8.         // 设置默认的头信息,比如Content-Type
  9.         template.header("Content-Type", configuration.getContentType().toString());
  10.         // 可以添加更多的默认处理逻辑
  11.     }
  12. }
复制代码
在这个实现中,DefaultRequestInterceptor 会在每个哀求中设置一些默认的头信息。
步骤3:自定义 RequestInterceptor 实现

咱们可以根据需要实现自定义的 RequestInterceptor。例如,添加一个用于设置认证头的拦截器:
  1. public class AuthRequestInterceptor implements RequestInterceptor {
  2.     private final String authToken;
  3.     public AuthRequestInterceptor(String authToken) {
  4.         this.authToken = authToken;
  5.     }
  6.     @Override
  7.     public void apply(RequestTemplate template) {
  8.         // 在每个请求中添加认证头
  9.         template.header("Authorization", "Bearer " + authToken);
  10.     }
  11. }
复制代码
步骤4:设置 OpenFeign 客户端使用自定义 RequestInterceptor

在 OpenFeign 设置中,可以指定自定义的 RequestInterceptor 实现:
  1. @Configuration
  2. public class FeignConfig {
  3.     @Bean
  4.     public RequestInterceptor authRequestInterceptor() {
  5.         // 假设从配置文件或环境变量中获取认证令牌
  6.         String authToken = "your-auth-token";
  7.         return new AuthRequestInterceptor(authToken);
  8.     }
  9. }
复制代码
然后在 @FeignClient 注解中指定设置类:
  1. @FeignClient(name = "myClient", configuration = FeignConfig.class)
  2. public interface MyClient {
  3.     // ...
  4. }
复制代码
小结一下

RequestInterceptor让咱们在不修改每个单独哀求的情况下,统一处理哀求。这使得Feign客户端更加灵活和强盛,可以或许适应各种复杂的业务需求。
6. Retryer:

Retryer 组件负责定义重试策略,它决定了在碰到特定范例的错误时是否重试哀求,以及重试的次数和间隔。Retryer 是Feign中的一个紧张组件,特别是在网络不稳定或服务不稳定的环境中,大派用场,它可以显著提高体系的健壮性哦。
步骤1:定义 Retryer 接口

Feign定义了一个 Retryer 接口,该接口包含几个关键的方法,用于控制重试的行为:
  1. public interface Retryer {
  2.     void continueOrPropagate(RetryableException e);
  3.     Retryer clone();
  4.     long getDelay(RetryableException e, int attempt);
  5.     boolean shouldRetry(RetryableException e, int attempt, int retry);
  6. }
复制代码

  • continueOrPropagate:决定是继续重试还是抛出异常。
  • clone:创建 Retryer 的副本,通常用于每个哀求的独立重试策略。
  • getDelay:返回在下一次重试之前的延迟时间。
  • shouldRetry:决定是否应该重试哀求。
步骤2:实现默认 Retryer

Feign提供了一个默认的 Retryer 实现,通常是一个简朴的重试策略,例如:
  1. public class DefaultRetryer implements Retryer {
  2.     private final long period;
  3.     private final long maxPeriod;
  4.     private final int maxAttempts;
  5.     public DefaultRetryer(long period, long maxPeriod, int maxAttempts) {
  6.         this.period = period;
  7.         this.maxPeriod = maxPeriod;
  8.         this.maxAttempts = maxAttempts;
  9.     }
  10.     @Override
  11.     public void continueOrPropagate(RetryableException e) {
  12.         // 根据异常类型和重试次数决定是否重试
  13.         if (shouldRetry(e, e.getAttempt(), maxAttempts)) {
  14.             // 继续重试
  15.         } else {
  16.             // 抛出异常
  17.             throw e;
  18.         }
  19.     }
  20.     @Override
  21.     public Retryer clone() {
  22.         return new DefaultRetryer(period, maxPeriod, maxAttempts);
  23.     }
  24.     @Override
  25.     public long getDelay(RetryableException e, int attempt) {
  26.         // 计算重试延迟
  27.         return Math.min(period * (long) Math.pow(2, attempt), maxPeriod);
  28.     }
  29.     @Override
  30.     public boolean shouldRetry(RetryableException e, int attempt, int retry) {
  31.         // 根据异常类型和重试次数决定是否重试
  32.         return attempt < retry;
  33.     }
  34. }
复制代码
在这个实现中,DefaultRetryer 使用指数退避策略来计算重试延迟,并允许指定最大重试次数。
步骤3:自定义 Retryer 实现

当咱们需要更复杂的重试策略时,可以创建自定义的 Retryer 实现。例如,可以基于特定的异常范例或响应码来决定重试策略:
  1. public class CustomRetryer implements Retryer {
  2.     // ... 自定义重试逻辑 ...
  3.     @Override
  4.     public void continueOrPropagate(RetryableException e) {
  5.         // 自定义重试逻辑
  6.     }
  7.     @Override
  8.     public Retryer clone() {
  9.         return new CustomRetryer();
  10.     }
  11.     @Override
  12.     public long getDelay(RetryableException e, int attempt) {
  13.         // 自定义延迟逻辑
  14.         return ...;
  15.     }
  16.     @Override
  17.     public boolean shouldRetry(RetryableException e, int attempt, int retry) {
  18.         // 自定义重试条件
  19.         return ...;
  20.     }
  21. }
复制代码
步骤4:设置 Feign 客户端使用自定义 Retryer

在Feign设置中,可以指定自定义的 Retryer 实现:
  1. @Configuration
  2. public class FeignConfig {
  3.     @Bean
  4.     public Retryer retryer() {
  5.         return new CustomRetryer();
  6.     }
  7. }
复制代码
然后在 @FeignClient 注解中指定设置类:
  1. @FeignClient(name = "myClient", configuration = FeignConfig.class)
  2. public interface MyClient {
  3.     // ...
  4. }
复制代码
小结一下

OpenFeign 允许我们根据需要选择或实现得当本身业务场景的重试策略,从而提高体系的健壮性和可靠性。问题来了,啥是指数退避策略?
解释一下哈,指数退避策略(Exponential Backoff)是一种在网络通信和分布式体系中常用的重试策略,特别是在处理暂时故障或网络延迟时。这种策略旨在通过增加连续重试之间的等待时间来减少体系的负载,并提高重试成功的机会。
指数退避策略的工作原理是如许的:当发生错误或故障时,体系起首会等待一个初始的短暂延迟,然后重试。假如第一次重试失败,等待时间会指数增长。这意味着每次重试的等待时间都是前一次的两倍(或另一个指数因子)。
通常会设置一个最大尝试次数,以防止无穷重试。为了避免等待时间过长,会设定一个最大延迟时间,超过这个时间后,纵然重试次数没有达到最大次数,也不会再增加等待时间。
为了减少多个客户端同时重试时的同步效应,有时会在指数退避中加入随机化因子,使得每次的等待时间在一定范围内变化。
7. Configuration:

Configuration 组件是一个关键的设置类,它允许用户自定义Feign客户端的行为。Configuration 类通常包含了一系列的设置,比如连接超时、读取超时、重试策略、编码器、解码器、契约(Contract)、日记级别等。这些设置可以应用于所有的Feign客户端,或者特定的Feign客户端。
步骤1:定义 Configuration 接口

Feign定义了一个 Configuration 接口,该接口允许用户设置Feign客户端的各种参数:
  1. public interface Configuration {
  2.     // 返回配置的编码器
  3.     Encoder encoder();
  4.     // 返回配置的解码器
  5.     Decoder decoder();
  6.     // 返回配置的契约
  7.     Contract contract();
  8.     // 返回配置的请求拦截器
  9.     RequestInterceptor requestInterceptor();
  10.     // 返回配置的重试策略
  11.     Retryer retryer();
  12.     // 返回配置的日志级别
  13.     Logger.Level loggerLevel();
  14.    
  15.     // ... 可能还有其他配置方法 ...
  16. }
复制代码
步骤2:实现默认 Configuration

Feign提供了一个默认的 Configuration 实现,这个实现包含了Feign的默认行为:
  1. public class DefaultConfiguration implements Configuration {
  2.     // ... 定义默认的编码器、解码器、契约等 ...
  3.     @Override
  4.     public Encoder encoder() {
  5.         return new JacksonEncoder(...);
  6.     }
  7.     @Override
  8.     public Decoder decoder() {
  9.         return new JacksonDecoder(...);
  10.     }
  11.     @Override
  12.     public Contract contract() {
  13.         return new SpringMvcContract(...);
  14.     }
  15.     // ... 实现其他配置方法 ...
  16. }
复制代码
在这个实现中,DefaultConfiguration 定义了Feign的默认编码器、解码器、契约等组件。
步骤3:自定义 Configuration 实现

用户可以创建自定义的 Configuration 实现,以覆盖默认的行为:
  1. public class CustomConfiguration extends DefaultConfiguration {
  2.     // ... 自定义特定的配置 ...
  3.     @Override
  4.     public Encoder encoder() {
  5.         // 返回自定义的编码器
  6.         return new CustomEncoder(...);
  7.     }
  8.     @Override
  9.     public Decoder decoder() {
  10.         // 返回自定义的解码器
  11.         return new CustomDecoder(...);
  12.     }
  13.     // ... 可以覆盖其他配置方法 ...
  14. }
复制代码
步骤4:设置 Feign 客户端使用自定义 Configuration

在Feign设置中,可以指定自定义的 Configuration 实现:
  1. @Configuration
  2. public class FeignConfig {
  3.     @Bean
  4.     public Configuration feignConfiguration() {
  5.         return new CustomConfiguration(...);
  6.     }
  7. }
复制代码
然后在 @FeignClient 注解中指定设置类:
  1. @FeignClient(name = "myClient", configuration = FeignConfig.class)
  2. public interface MyClient {
  3.     // ...
  4. }
复制代码
8. Target:

Target 组件代表了Feign客户端将要调用的远程服务的目标。它通常包含了服务的名称和可能的特定设置,例如哀求的URL。Target 组件在Feign的动态署理机制中饰演偏紧张脚色,由于它定义了如何将方法调用转换为实际的HTTP哀求。
步骤1:定义 Target 接口

Feign定义了一个 Target 接口,该接口包含一些关键的方法和属性:
  1. public interface Target<T> {
  2.     String name();
  3.     String url();
  4.     Class<T> type();
  5. }
复制代码

  • name():返回服务的名称,通常用于服务发现。
  • url():返回服务的URL,可以是完整的URL或者是一个模板。
  • type():返回Feign客户端接口的范例。
步骤2:实现 Target 接口

Feign提供了 Target 接口的实现,通常是一个名为 HardCodedTarget 的类:
  1. public class HardCodedTarget<T> implements Target<T> {
  2.     private final String name;
  3.     private final String url;
  4.     private final Class<T> type;
  5.     public HardCodedTarget(Class<T> type, String name, String url) {
  6.         this.type = type;
  7.         this.name = name;
  8.         this.url = url;
  9.     }
  10.     @Override
  11.     public String name() {
  12.         return name;
  13.     }
  14.     @Override
  15.     public String url() {
  16.         return url;
  17.     }
  18.     @Override
  19.     public Class<T> type() {
  20.         return type;
  21.     }
  22. }
复制代码
在这个实现中,HardCodedTarget 通过构造函数接收服务的名称、URL和接口范例,并提供相应的getter方法。
步骤3:使用 Target 组件

在Feign客户端接口中,可以通过 @FeignClient 注解的 value 属性指定服务名称,Feign在内部会使用 Target 来构建署理:
  1. @FeignClient(value = "myService", url = "http://localhost:8080")
  2. public interface MyClient extends MyServiceApi {
  3.     // 定义服务方法
  4. }
复制代码
Feign会根据注解信息创建一个 HardCodedTarget 实例,并使用它来构建动态署理。
步骤4:动态署理和 Target

Feign使用Java的动态署理机制(是不是哪哪都是动态署理,以是说动态署理很紧张)来创建客户端署理。在Feign的 ReflectiveFeign 类中,会使用 InvocationHandlerFactory 来创建 InvocationHandler:
  1. public class ReflectiveFeign extends Feign {
  2.     private final InvocationHandlerFactory invocationHandlerFactory;
  3.     public ReflectiveFeign(InvocationHandlerFactory invocationHandlerFactory) {
  4.         this.invocationHandlerFactory = invocationHandlerFactory;
  5.     }
  6.     @Override
  7.     public <T> T newInstance(Target<T> target) {
  8.         // 使用 InvocationHandlerFactory 创建 InvocationHandler
  9.         InvocationHandler invocationHandler = invocationHandlerFactory.create(target);
  10.         // 创建动态代理
  11.         return (T) Proxy.newProxyInstance(target.type().getClassLoader(),
  12.                 new Class<?>[]{target.type()}, invocationHandler);
  13.     }
  14. }
复制代码
在这个过程中,InvocationHandler 会使用 Target 来构建实际的HTTP哀求。
9. InvocationHandlerFactory:

InvocationHandlerFactory 组件是动态署理的核心,它负责创建 InvocationHandler,这是Java动态署理机制的关键部门。InvocationHandler 定义了署理对象在被调用时的行为。在Feign的上下文中,InvocationHandler 负责将方法调用转换为HTTP哀求。
步骤1:定义 InvocationHandlerFactory 接口

Feign定义了一个 InvocationHandlerFactory 接口,该接口包含一个方法,用于创建 InvocationHandler:
  1. public interface InvocationHandlerFactory {
  2.     InvocationHandler create(Target target);
  3. }
复制代码

  • Target:代表Feign客户端的目标,包含了服务的名称、URL和接口范例。
步骤2:实现 InvocationHandlerFactory

Feign提供了一个默认的 InvocationHandlerFactory 实现,通常是一个名为 ReflectiveInvocationHandlerFactory 的类:
  1. public class ReflectiveInvocationHandlerFactory implements InvocationHandlerFactory {
  2.     @Override
  3.     public InvocationHandler create(Target target) {
  4.         return new ReflectiveInvocationHandler(target);
  5.     }
  6. }
复制代码
在这个实现中,ReflectiveInvocationHandlerFactory 创建了一个 ReflectiveInvocationHandler 实例,这个 InvocationHandler 会处理反射调用。
步骤3:实现 InvocationHandler

ReflectiveInvocationHandler 是 InvocationHandler 接口的一个实现,它负责将方法调用转换为HTTP哀求:
  1. public class ReflectiveInvocationHandler implements InvocationHandler {
  2.     private final Target<?> target;
  3.     private final FeignClientFactory feignClientFactory;
  4.     public ReflectiveInvocationHandler(Target<?> target, FeignClientFactory feignClientFactory) {
  5.         this.target = target;
  6.         this.feignClientFactory = feignClientFactory;
  7.     }
  8.     @Override
  9.     public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
  10.         // 根据方法和参数构建RequestTemplate
  11.         RequestTemplate template = buildTemplateFromArgs(target, method, args);
  12.         // 发送请求并获取响应
  13.         Response response = feignClientFactory.create(target).execute(template, options());
  14.         // 根据响应构建返回值
  15.         return decode(response, method.getReturnType());
  16.     }
  17.     private RequestTemplate buildTemplateFromArgs(Target<?> target, Method method, Object[] args) {
  18.         // 构建请求模板逻辑
  19.         // ...
  20.     }
  21.     private Response.Options options() {
  22.         // 获取请求选项,如超时设置
  23.         // ...
  24.     }
  25.     private Object decode(Response response, Type type) {
  26.         // 将响应解码为方法返回类型的逻辑
  27.         // ...
  28.     }
  29. }
复制代码
代码中我们发现,invoke 方法是核心,它根据目标对象、方法和参数构建一个 RequestTemplate,然后使用 FeignClient 发送哀求并获取响应。
步骤4:设置 Feign 客户端使用自定义 InvocationHandlerFactory

自定义 InvocationHandlerFactory,可以在Feign设置中指定:
  1. @Configuration
  2. public class FeignConfig {
  3.     @Bean
  4.     public InvocationHandlerFactory invocationHandlerFactory() {
  5.         return new CustomInvocationHandlerFactory();
  6.     }
  7. }
复制代码
然后在 @FeignClient 注解中指定设置类:
  1. @FeignClient(name = "myClient", configuration = FeignConfig.class)
  2. public interface MyClient {
  3.     // ...
  4. }
复制代码
小结一下

为啥说步伐员需要充实理解计划模式的应用,假如在面试时问你任何关于计划模式的问题,请不要只讲概念、不要只讲概念、不要只讲概念,还要结合业务场景,或者结合框架源码的理解来讲,讲一讲解决了什么问题,这是关键所在。
最后

OpenFeign 是 Spring Cloud 生态体系中的一个强盛工具,它使得微服务之间的通信变得更加简朴和高效。通过使用 OpenFeign,开发者可以专注于业务逻辑的实现,而不需要关心底层的 HTTP 通信细节。接待关注威哥爱编程,原创不易,求个赞啊兄弟。

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

何小豆儿在此

金牌会员
这个人很懒什么都没写!
快速回复 返回顶部 返回列表