Spring-Cloud之Feign

2021年11月20日 阅读数:2
这篇文章主要向大家介绍Spring-Cloud之Feign,主要内容包括基础应用、实用技巧、原理机制等方面,希望对大家有所帮助。

原文:https://blog.vchar.top/java/1621167133.htmlhtml

Feign的引入可让咱们经过接口注解的形式实现服务间的调用,让调用者无需再关心接口地址这些配置,同时对于接口须要的参数可以更清晰地了解,简化了调用的流程。下面咱们经过一些Feign的示例带你快速了解如何使用它。示例使用的Spring-Cloud的版本是Hoxton.SR8,Spring-Boot的版本是2.3.4.RELEASE。示例项目的源代码java

添加相关的依赖

feign中已经对ribbon进行集成支持,在spring最新版本已经开始弃用ribbon了,而推荐使用Cloud LoadBalancer(feign也支持)。git

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>

使用示例

feign实际上是一个伪RPC的组件;咱们以前在服务间调用须要的使用者去手动设置请求地址,在加入ribbon后,虽然省略了具体的服务器地址和端口,可是接口地址仍是须要手动编写;而引入feign后就能够解决这个问题,由接口的开发者提供给调用者一个接口类的方法的jar包,调用者直接依赖该包,而后注入该接口类便可像调用方法同样调用其余的服务。同时feign提供了在方法调用失败时的处理策略。这样若是接口地址发生改变了,那么只须要更新jar包便可;同时调用者在使用的时候也会更加清晰具体的参数。github

要使feign生效,咱们还须要在启动类上添加以下注解来启用feign:@EnableFeignClients ;下面对该注解主要的属性进行说明:spring

  • value和name:服务名称;这个2个随意选择一个配置便可;
  • path:请求路径的前缀;当多个接口地址有相同的前缀时,能够将前缀配置到path中;
  • fallback和fallbackFactory:这2个都是对接口熔断降级处理,也就是异常处理,只能选择其中一个;

简单示例

定义一个接口,在里面编写须要调用的某个服务的restful接口,而后在类上添加 @FeignClient ,在上面配置要调用的服务名称,也就是说feign会帮助咱们拼接这个请求地址(也就是咱们在ribbon那里作的事情),所以必须正确才能够。json

@FeignClient(name = "feign-server-b") // 服务名称,必须匹配,不然待会将没法替换为真实的地址
public interface GoodsFeignClientDemo1 {

    /**
     * 这里的路径必须和对应要调用的接口地址对应
     */
    @GetMapping("/goods")
    Goods findById(@RequestParam(value = "id")Long id);
}

在业务代码中使用服务器

@Service
public class OrderServiceImpl implements OrderService {
    
    // 注入该接口的feign接口
    @Autowired
    private GoodsFeignClientDemo1 goodsFeignClientDemo1;

    @Override
    public void demo1() {
        Goods goods = goodsFeignClientDemo1.findById(1L);
        System.out.println("示例1:"+goods);
    }
}

编写feign客户端接口的参数注解示例

因为feign最终是要根据咱们编写的接口方法来组装请求的参数和方式的,所以为了让feign可以正常识别咱们的参数,须要加上相关注解才可行;好比GET请求的参数加上@RequestParam注解,若是是个对象就须要加上@SpringQueryMap注解。下面是一些经常使用的示例。restful

  • GET请求参数中有多个参数
@GetMapping("/goods/demo2")
Goods findById(@RequestParam(value = "id") Long id, @RequestParam(value = "goodsName")String goodsName);
  • GET请求参数是一个自定义对象
@GetMapping("/goods/demo3")
Goods findById(@SpringQueryMap GoodsParams params);
  • GET请求参数是一个自定义对象和普通类型的组合
@GetMapping("/goods/demo4")
Goods findById(@SpringQueryMap GoodsParams params, @RequestParam(value = "id") Long id);
  • GET请求参数在URL路径上
@GetMapping("/goods/demo5/{id}")
Goods findGoods(@PathVariable(value = "id") Long id);
  • 请求参数在body里面
@PostMapping("/goods/demo6")
String checkGoods(@RequestBody GoodsParams params);

为feign添加接口异常处理

能够经过配置@EnableFeignClients注解的fallback或者是fallbackFactory的属性来实如今请求接口异常时返回默认的数据。注意须要在配置中打开feign的hystrix配置:app

feign:
  hystrix:
    enabled: true

使用fallback

注意:使用fallback没法打印具体的异常信息ide

@FeignClient(name = "feign-server-b", fallback = GoodsFeignClientFallback.class)
public interface GoodsFeignClientDemo3 {

    @GetMapping("/goods")
    Goods findById(@RequestParam(value = "id")Long id);
}

异常处理实现类

@Component
public class GoodsFeignClientFallback implements GoodsFeignClientDemo3 {

    @Override
    public Goods findById(Long id) {
        System.out.println(id+"请求接口异常");
        return null;
    }
}

使用fallbackFactory

@FeignClient(name = "feign-server-b", fallbackFactory = GoodsFeignClientFallbackFactory.class)
public interface GoodsFeignClientDemo4 {
    
    @GetMapping("/goods")
    Goods findById(@RequestParam(value = "id")Long id);
}

异常处理实现类

@Component
public class GoodsFeignClientFallbackFactory implements FallbackFactory<GoodsFeignClientDemo4> {
    @Override
    public GoodsFeignClientDemo4 create(Throwable throwable) {
        // 这里经过匿名实现
        return new GoodsFeignClientDemo4() {
            @Override
            public Goods findById(Long id) {
                // 这里能够进行异常的处理
                System.out.println(id+"请求接口异常: "+throwable.getMessage());
                return null;
            }
        };
    }
}

经过feign自定义拦截器实现请求统一参数添加

当咱们须要在请求中添加一些默认的统一参数,那么能够经过添加自定义拦截器来实现。首先须要实现RequestInterceptor接口

public class CustomRequestInterceptor implements RequestInterceptor {

    @Override
    public void apply(RequestTemplate requestTemplate) {
        System.out.println("拦截请求...作统一处理");
        requestTemplate.header("uid", "9527");
    }
}

定义bean配置类

public class FeignConfiguration {

    @Bean
    public RequestInterceptor requestInterceptor(){
        return new CustomRequestInterceptor();
    }
}

在feignClient上配置

@FeignClient(name = "feign-server-b", configuration = FeignConfiguration.class)
public interface GoodsFeignClientDemo5 {

    @GetMapping("/goods")
    Goods findById(@RequestParam(value = "id") Long id);
}

一些可能碰见的问题

当一个服务须要暴露的接口较多时,一般咱们会选择分模块分包分类来处理,此时对一个服务来讲就要定义多个feign client接口;因为最终bean的名称是根据服务名来生成,所以会出现同名的bean致使启动失败。也就是会出现以下提示:

2021-05-17 17:02:49.325 ERROR 39740 --- [           main] o.s.b.d.LoggingFailureAnalysisReporter   : 

***************************
APPLICATION FAILED TO START
***************************

Description:

The bean 'feign-server-b.FeignClientSpecification' could not be registered. A bean with that name has already been defined and overriding is disabled.

Action:

Consider renaming one of the beans or enabling overriding by setting spring.main.allow-bean-definition-overriding=true

这样只须要在配置文件里面加上以下配置便可:spring.main.allow-bean-definition-overriding=true

同时项目中接口地址和feign中地址必须保持惟一(也就是RequestMapping中配置的值),不然将可能会出现问题。

feign配置

对请求参数和响应的数据进行压缩,默认是没有开启的。

feign:
  compression:
    # 请求和响应压缩配置
    request:
      enabled: true
      mime-types: text/xml,application/xml,application/json
      min-request-size: 2048
    response:
      enabled: true

对服务进行个性化配置

feign:
  client:
    config:
      # 全局配置
      default:
        connectTimeout: 1000
        readTimeout: 1000
        loggerLevel: BASIC
      # 单独某个服务配置
      feign-server-b:
        connectTimeout: 5000
        readTimeout: 5000
        loggerLevel: full

在开发测试环境打开feign的请求日志打印

feign:
  client:
    config:
      # 单独某个服务配置
      feign-server-b:
        connectTimeout: 5000
        readTimeout: 5000
        loggerLevel: full
# 将feign客户端请求类的日志级别调整为 DEBUG  
logging.level.top.vchar.feign.GoodsFeignClientDemo5: DEBUG

加上以后控制台会打印以下日志:

...
2021-05-18 10:10:15.551 DEBUG 41344 --- [eign-server-b-1] top.vchar.feign.GoodsFeignClientDemo5    : [GoodsFeignClientDemo5#findById] ---> GET http://feign-server-b/goods?id=1 HTTP/1.1
2021-05-18 10:10:15.551 DEBUG 41344 --- [eign-server-b-1] top.vchar.feign.GoodsFeignClientDemo5    : [GoodsFeignClientDemo5#findById] Accept-Encoding: gzip
2021-05-18 10:10:15.551 DEBUG 41344 --- [eign-server-b-1] top.vchar.feign.GoodsFeignClientDemo5    : [GoodsFeignClientDemo5#findById] Accept-Encoding: deflate
2021-05-18 10:10:15.551 DEBUG 41344 --- [eign-server-b-1] top.vchar.feign.GoodsFeignClientDemo5    : [GoodsFeignClientDemo5#findById] uid: 9527
2021-05-18 10:10:15.551 DEBUG 41344 --- [eign-server-b-1] top.vchar.feign.GoodsFeignClientDemo5    : [GoodsFeignClientDemo5#findById] ---> END HTTP (0-byte body)
...