Feign的使用指导

摘要:本文介绍了如何在ECP项目中使用Feign进行微服务开发。我们从基础配置开始,讨论了如何使用Spring Cloud OpenFeign结合ECP 工程,并详细解释了Feign的默认配置。然后,我们重点介绍了单个Feign服务如何创建。通过学习本文,您将掌握如何高效使用Feign构建可靠的微服务通信。

1. 引言

随着微服务架构的兴起,服务之间的通信变得至关重要。Feign是一个声明式、模板化的HTTP客户端,用于简化服务之间的通信。在ECP项目中,我们可以使用Spring Cloud OpenFeign库来轻松实现服务间的调用。

2. 基础使用

2.1 Feign基础使用

在ECP项目中,我们可以利用Spring MVC的注解和特性来定义Feign接口。通过在接口上添加@FeignClient注解,我们可以指定要调用的服务名称、配置类、请求拦截器等。例如:

2.2 ECP工程的Feign默认配置

在ECP项目中,我们可以通过在配置文件中添加Feign的默认配置来自定义Feign客户端的行为。例如,在application.yml文件中可以配置如下内容:

通过以上配置,我们设置基本配置好了feign。

3. 具体示例

使用场景:我们需要在 ecp-desk 的服务中调用 ecp-provider-feign 中接口,分两端 服务端 和 消费端

ecp-provider-feign 服务端代码如下

package cn.histo.ecp.provider.controller;

import cn.histo.provider.feign.entity.ProvideNotice;
import lombok.extern.slf4j.Slf4j;
import org.springblade.core.tenant.annotation.NonDS;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import springfox.documentation.annotations.ApiIgnore;

import java.util.concurrent.TimeUnit;

/**
 * @author Verite
 * @date 2023/6/9 0:04
 */
@NonDS
@ApiIgnore()
@RestController
@Slf4j
@RequestMapping("client/provider")
public class ProviderController {

   @Value("${server.port}")
   String port;

   /**
    * 默认调用
    */
@GetMapping("index")
   public String index(@RequestParam String param) throws InterruptedException {
      TimeUnit.MILLISECONDS.sleep(3000);
      log.info(param);
      return "端口:" + port + "; 参数:" + param;
   }

   /**
    * 使用 PathVariable 注解
    *
    * @param current 当前页
    * @param size    每页显示条数
    * @return PathVariable 的参数
    */
@GetMapping("usePathVariable/{current}/{size}")
   public String usePathVariable(@PathVariable("current") Integer current, @PathVariable("current") Integer size) {
      return "使用 PathVariable 注解 。当前页:" + current + "; 每页显示条数:" + size;
   }

   /**
    * 使用 RequestParam 注解
    *
    * @param current 当前页
    * @param size    每页显示条数
    * @return RequestParam 的参数
    */
@GetMapping("useRequestParam")
   public String useRequestParam(@RequestParam Integer current, @RequestParam Integer size) {
      return "使用 RequestParam 注解 。当前页:" + current + "; 每页显示条数:" + size;
   }

   /**
    * 使用 RequestParam 注解
    *
    * @param notice 参数
    * @return notice 的参数
    */
@GetMapping("useBody")
   public ProvideNotice useBody(@RequestBody ProvideNotice notice) {
      return notice;
   }
}

 


 

ecp-desk 消费端 的 feign 的使用 如下步骤

A,在启动器中加入 feign的 注解 @EnableFeignClients({"org.springblade", "cn.histo"})

B,我们将在IUserFeignClient 列举 四种方式 来说明如何使用feign,以及针对 IUserFeignClient  的独立服务配置,和服务失败进行处理的方式

针对 IUserFeignClient (必须,feign客户端)

UserConfiguration(非必须,需要单独配置参数则创建) 

IUserFeignClientFallback (非必须,此服务需要有服务失败进行单独处理则创建)

在ECP 工程中,文件的创建位置 以及 使用 如下图:

C,  以下是 IUserFeignClient 文件 ,创建常用的四种创建方式。

package cn.histo.desk.feign;

import cn.histo.desk.feign.config.UserConfiguration;
import cn.histo.provider.feign.entity.ProvideNotice;
import io.swagger.annotations.ApiParam;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestParam;

/**
 * IUserFeignClient接口:这是一个定义Feign客户端的接口。它包含了一些需要调用的远程服务的方法。
 *
 * @author Chill
 */
//@FeignClient(必须) 这是一个Feign客户端的注解,用于标识该接口是一个Feign客户端接口。
@FeignClient(
   contextId = "user" // contextId(非必须)是该客户端的上下文ID
   , value = "blade-provider-feign" // value | name(必须 ) 推荐使用name
   , fallback = IUserFeignClientFallback.class // 熔断处理
   , configuration = UserConfiguration.class // (非必须) 是指定的Feign客户端配置类
)
public interface IUserFeignClient {

   /**
    * 默认是以 /client 开头
    */
String API_PREFIX = "/client/provider/";

   /**
    * 默认接口 第一种使用方式
    */
String INDEX = "index";

   /**
    * 第二种使用 usePathVariable 注解的方式
    */
String USE_PATH_VARIABLE = "/usePathVariable/{current}/{size}";

   /**
    * 第三种使用 useRequestParam 注解的方式
    */
String USE_REQUEST_PARAM = "useRequestParam";

   /**
    * 第四种使用 RequestBody 注解的方式
    */
String USE_BODY = "useBody";

   /**
    * 默认调用
    */
@GetMapping(API_PREFIX + INDEX)
   void index();

   /**
    * 使用 PathVariable 注解
    *
    * @param current 当前页
    * @param size    每页显示条数
    * @return PathVariable 的参数
    */
@GetMapping(API_PREFIX + USE_PATH_VARIABLE)
   String usePathVariable(@ApiParam(value = "当前页") @PathVariable Integer current, @ApiParam(value = "每页显示条数") @PathVariable Integer size);

   /**
    * 使用 RequestParam 注解
    *
    * @param current 当前页
    * @param size    每页显示条数
    * @return RequestParam 的参数
    */
@GetMapping(API_PREFIX + USE_REQUEST_PARAM)
   String useRequestParam(@RequestParam Integer current, @RequestParam Integer size);

   /**
    * 使用 Body 注解
    *
    * @param provideNotice 提示类
    * @return 返回 Notice
    */
@PostMapping(API_PREFIX + USE_BODY)
   ProvideNotice useBody(@RequestBody ProvideNotice provideNotice);
}

 


 

D,以下是 UserConfiguration文件 ,针对 IUserFeignClient  服务单独配置。 

package cn.histo.desk.feign.config;

import cn.histo.desk.feign.config.requestInterceptor.AuthInterceptor;
import cn.histo.desk.feign.config.coder.FastJsonDecoder;
import cn.histo.desk.feign.config.coder.FastJsonEncoder;
import feign.codec.Decoder;
import feign.codec.Encoder;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.openfeign.encoding.FeignClientEncodingProperties;
import org.springframework.context.annotation.Bean;

/**
 * @author Verite
 * @date 2023/6/1 17:24
 */
@Slf4j
public class UserConfiguration {
}
E,以下是 IUserFeignClientFallback  文件 ,针对 IUserFeignClient  服务失败进行处理。 

package cn.histo.desk.feign;

import cn.histo.provider.feign.entity.ProvideNotice;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.RequestBody;

/**
 * 远程调用失败处理类
 *
 * @author lihao
 */
@Component
@Slf4j
public class IUserFeignClientFallback implements IUserFeignClient {

   /**
    * 默认调用
    */
@Override
   public void index() {
      
   }

   /**
    * 使用 PathVariable 注解
    *
    * @param current 当前页
    * @param size    每页显示条数
    * @return PathVariable 的参数
    */
@Override
   public String usePathVariable(Integer current, Integer size) {
      return "User fallback : usePathVariable";
   }

   /**
    * 使用 RequestParam 注解
    *
    * @param current 当前页
    * @param size    每页显示条数
    * @return RequestParam 的参数
    */
@Override
   public String useRequestParam(Integer current, Integer size) {
      return "User fallback : useRequestParam";
   }

   /**
    * 使用 Body 注解
    *
    * @param provideNotice 提示类
    * @return 返回 Notice
    */
@Override
   public ProvideNotice useBody(@RequestBody ProvideNotice provideNotice) {
      provideNotice.setContent("fallback : useBody");
      return provideNotice;
   }
}

 

 

F,   使用的一种方式


package cn.histo.desk.controller;

import cn.histo.desk.feign.IOrderFeignClient;
import cn.histo.desk.feign.IUserFeignClient;
import cn.histo.provider.feign.entity.ProvideNotice;
import cn.histo.provider.feign.feign.IProviderNoticeClient;
import com.github.xiaoymin.knife4j.annotations.ApiOperationSupport;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springblade.core.boot.ctrl.BladeController;
import org.springblade.core.tenant.annotation.NonDS;
import org.springblade.core.tool.api.R;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

/**
 * 控制器
 *
 * @author Verite
 */
@Slf4j
@NonDS
@RestController
@RequestMapping("normalFeign")
@AllArgsConstructor
@Api(value = "feign 远程调用示例", tags = "feign 调用示例")
public class DemoFeignController extends BladeController {

   private final IUserFeignClient userFeignClient;

   /**
    * 默认调用
    */
@GetMapping("/userIndex")
   @ApiOperationSupport(order = 1)
   @ApiOperation(value = "默认调用", notes = "index")
   public R<Object> userIndex(@RequestParam String param, @RequestParam String base) {
      String res = userFeignClient.index(param, base);
      return R.success("user index:" + res);
   }

   /**
    * 使用 PathVariable
    */
@GetMapping("/usePathVariable/{current}/{size}")
   @ApiOperationSupport(order = 2)
   @ApiOperation(value = "usePathVariable", notes = "使用 RequestParam")
   public R<String> usePathVariable(@ApiParam(value = "当前页") @PathVariable Integer current, @ApiParam(value = "每页显示条数") @PathVariable Integer size) {
      String page = userFeignClient.usePathVariable(current, size);
      return R.data(page);
   }

   /**
    * 使用 RequestParam
    */
@GetMapping("userRequestParam")
   @ApiOperationSupport(order = 3)
   @ApiOperation(value = "userRequestParam", notes = "使用 userRequestParam")
   public R<String> useRequestParam(@RequestParam Integer current, @RequestParam Integer size) {
      String page = userFeignClient.useRequestParam(current, size);
      return R.data(page);
   }

   /**
    * 使用 RequestBody
    */
@PostMapping("useBody")
   @ApiOperationSupport(order = 4)
   @ApiOperation(value = "useBody", notes = "使用 body")
   public R<ProvideNotice> useBody(@RequestBody ProvideNotice provideNotice) {
      ProvideNotice resProvideNotice = userFeignClient.useBody(provideNotice);
      return R.data(resProvideNotice);
   }

}

 


 

4. 注意事项

在本节中,我们将列举一些开发者常见的问题,并提供具体的解决方法。这些问题可能涉及Feign的配置、使用过程中的错误、性能优化等方面。我们将通过实际案例和解决方案来帮助开发者更好地应对这些问题。

1,在ecp-desk中 创建 IUserFeignClient.usePathVarable(@PathVarable) 等等接口时,类似 @PathVarable 等等缺失,远程调用会找不到 ecp-provider-feign的对应接口。

2,yml配置中 feign.client.config.default 是全局服务的配置 ,请谨慎修改。

3,yml配置中 feign.client.config.user 是对以上代码中的 IUserFeignClient文件 →注解@FeignClients context_id为 user的配置 请谨慎修改。

4,使用hystrix 熔断生效 需要三步 :1>启动器要加载 @EnableHystrix 注解 ;2> yml配置文件开始 feign.circuitbreaker.enabled: true ;3> 在@FeignClients 中的fallback参数加载IUserFeignClientFallback 文件并实现。

5,UserConfiguration 文件如果使用了 @Configuration 或者 @Componet,其中的拦截器会全局生效,请谨慎使用 。

本文详细介绍了使用Feign简化微服务开发的方法。我们从基础配置开始,探讨了Spring Cloud OpenFeign和ECP工程的集成,并介绍了Feign的默认配置。然后,我们重点讲解了单个Feign服务如何在ECP中的使用。通过学习本文,您将掌握使用Feign构建可靠的微服务通信的技巧和最佳实践。

希望本文对正在使用Feign进行微服务开发的开发者提供了实用的指导和建议。通过合理配置和使用Feign,您可以极大地简化微服务通信的开发过程,提高系统的可靠性和可扩展性。

参考资料

nacos官方介绍 https://nacos.io/zh-cn/docs/v2/quickstart/quick-start.html

feign官方介绍 https://docs.spring.io/spring-cloud-openfeign/docs/current/reference/html/#spring-cloud-feign

Hystrix官方介绍 https://www.kancloud.cn/ur_champaign/hytrix/840124

feign 简介和使用 https://zhuanlan.zhihu.com/p/416699563

feign配置原理讲解 https://blog.csdn.net/weixin_42039228/article/details/123714356

Feign的使用及原理剖析https://blog.csdn.net/weixin_50117915/article/details/128236218

posted @ 2023-07-10 13:19  Verite  阅读(247)  评论(0)    收藏  举报