Java SpringCloud Feign

1、OpenFeign

1.1 概述

– Feign是一个声明性web服务客户机。它使编写web服务客户机变得更容易。
– 它的使用方法是定义一个服务接口并在上面添加注解
– Feign支持可插拔编码器和解码器。
– Spring Cloud对Feign进行了封装,使其支持SpringMVC标准注解和HttpttpMessageConverters。Feign可以与Eureka和Ribbon组合使用以支持负载均衡。

1.2 Feign的作用

Feign旨在使编写Java Http客户端变得更容易。
前面在使用Ribbon+RestTemplate时,利用RestTemplate对http请求的封装处理,形成了一套模版化的调用方法。但在实际开发中,由于对服务依赖的调用可能不止一处,往往一个接口可能被多处调用,所以通常都会针对每个微服务自行封装一些客户端类来包装这些依赖服务的调用。所以,Feign在此基础上做了进一步封装,由它来定义和实现依赖服务接口的定义。在Feign的实现下,只需要创建一个接口并使用注解的方式来配置它(以前是Dao接口上面标注Mapper注解,现在是一个微服务接口上标注一个Feign注解即可),即可完成对服务提供方的的接口绑定,简化了使用Spring Cloud Ribbon时,自动封装服务调用客户端的开发量。

1.3 Feign集成了Ribbon

利用Ribbon维护了Payment的服务列表信息,并且通过轮询实现了客户端的负载均衡。而与Ribbon不同的是,通过Feign只需要定义服务绑定接口且以声明式的方法,优雅而简单的实现了服务的调用。

1.4 Feign和OpenFeign的区别

Feign

Feign是SpringCloud组件中一个轻量级RESTful的HTTP服务客户端,Feign内置了Ribbon,用来做客户端负载均衡,去调用服务注册中心的服务。Feign的使用方式是:使用Feign的注解定义接口,调用这个接口,就可以调用服务注册中心的服务。

  1. <dependency>
  2. <groupId>org.springframework.cloud</groupId>
  3. <artifactId>spring-cloud-starter-feign</artifactId>
  4. </dependency>

OpenFeign

OpenFeign是SpringCloud在Feign的基础上支持了SpringMVC的注解,如@RequestMapping等。OpenFeign 的@FeignClient可以解析SpringMVC的@RequestMapping注解下的接口,并通过动态代理的方式产生实现类,实现类中做负载均衡并调用其他服务。

  1. <dependency>
  2. <groupId>org.springframework.cloud</groupId>
  3. <artifactId>spring-cloud-starter-openfeign</artifactId>
  4. </dependency>

2、Fegin的使用

A.在启动类上使用@EnableFeignClients注解开启Feign客户端,并注入RestTemplate

  1. package com.fcant.userservice;
  2. import org.springframework.boot.SpringApplication;
  3. import org.springframework.cloud.client.SpringCloudApplication;
  4. import org.springframework.cloud.client.loadbalancer.LoadBalanced;
  5. import org.springframework.cloud.openfeign.EnableFeignClients;
  6. import org.springframework.context.annotation.Bean;
  7. import org.springframework.web.client.RestTemplate;
  8. /**
  9. * UserServiceApplication
  10. *
  11. * encoding:UTF-8
  12. * @author Fcant 9:54 2019/12/9
  13. */
  14. @EnableFeignClients
  15. @SpringCloudApplication
  16. public class UserServiceApplication {
  17. public static void main(String[] args) {
  18. SpringApplication.run(UserServiceApplication.class, args);
  19. }
  20. @Bean
  21. @LoadBalanced
  22. public RestTemplate restTemplate() {
  23. return new RestTemplate();
  24. }
  25. }

B.Fegin的Service层

  • 在接口层使用注解**@FeignClient(name = "area-service")**指定请求的服务名
  • 请求中有参数需要通过**@PathVariable(name = "countryId")**注解的name属性指定才能解析反射值 ```java package com.fcant.userservice.feign;

import com.fcant.userservice.bean.Country; import org.springframework.cloud.openfeign.FeignClient; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable;

/**

  • ICountryService
  • 通过@FeignClient注解指定Feign客户端,名称为要访问的服务名
  • encoding:UTF-8 *
  • @author Fcant 10:24 2019/12/9 */ @FeignClient(name = “area-service”) public interface ICountryService {

    /**

    • queryCountry
    • Feign中没有指定@PathVariable(name = “countryId”)变量映射关系,必须通过name属性指定 *
    • @param countryId 城市ID
    • @return com.fcant.userservice.bean.Country
    • @author Fcant 10:53 2019/12/9 */ @GetMapping(“/country/{countryId}”) Country queryCountry(@PathVariable(name = “countryId”) long countryId); }
  1. <a name="rpeH4"></a>
  2. ### C.接口层
  3. 接口层的调用和使用和平常的Controller没什么区别,把Service层注入即可
  4. ```java
  5. package com.fcant.userservice.controller;
  6. import com.fcant.userservice.bean.Country;
  7. import com.fcant.userservice.bean.User;
  8. import com.fcant.userservice.feign.ICountryService;
  9. import org.springframework.web.bind.annotation.GetMapping;
  10. import org.springframework.web.bind.annotation.RequestMapping;
  11. import org.springframework.web.bind.annotation.RestController;
  12. import java.util.ArrayList;
  13. import java.util.List;
  14. /**
  15. * FeignController
  16. * <p>
  17. * encoding:UTF-8
  18. *
  19. * @author Fcant 10:06 2019/12/9
  20. */
  21. @RestController
  22. @RequestMapping("/user")
  23. public class FeignController {
  24. private static final List<User> USER_LIST;
  25. static {
  26. List<User> userList = new ArrayList<>();
  27. userList.add(new User(3L, "User 03", "男", "user03@fcant.com", 1L, ""));
  28. userList.add(new User(4L, "User 04", "女", "user04@fcant.com", 2L, ""));
  29. USER_LIST = userList;
  30. }
  31. private final ICountryService countryService;
  32. public FeignController(ICountryService countryService) {
  33. this.countryService = countryService;
  34. }
  35. @GetMapping("/feign")
  36. public List<User> queryUser() {
  37. List<User> userList = new ArrayList<>(USER_LIST);
  38. userList.forEach(user -> {
  39. Country country = countryService.queryCountry(user.getCountryId());
  40. user.setCountryName(country.getCountryName());
  41. });
  42. return userList;
  43. }
  44. }

3、Fegin异常处理的回调

A.在接口的注解上添加fallback属性指定异常回调的类

  1. package com.fcant.userservice.feign;
  2. import com.fcant.userservice.bean.Country;
  3. import com.fcant.userservice.feign.impl.CountryServiceImpl;
  4. import org.springframework.cloud.openfeign.FeignClient;
  5. import org.springframework.web.bind.annotation.GetMapping;
  6. import org.springframework.web.bind.annotation.PathVariable;
  7. /**
  8. * ICountryService
  9. * 通过@FeignClient注解指定Feign客户端,名称为要访问的服务名
  10. * <p>
  11. * encoding:UTF-8
  12. *
  13. * @author Fcant 10:24 2019/12/9
  14. */
  15. @FeignClient(name = "area-service", fallback = CountryServiceImpl.class)
  16. public interface ICountryService {
  17. /**
  18. * queryCountry
  19. * Feign中没有指定@PathVariable(name = "countryId")变量映射关系,必须通过name属性指定
  20. *
  21. * @param countryId 城市ID
  22. * @return com.fcant.userservice.bean.Country
  23. * @author Fcant 10:53 2019/12/9
  24. */
  25. @GetMapping("/country/{countryId}")
  26. Country queryCountry(@PathVariable(name = "countryId") long countryId);
  27. }

B.在指定的处理异常的类中实现接口的方法进行处理

  • 该类实现FeginClients注解指定的接口
  • 该类添加@Component注解将其作为Bean注入 ```java package com.fcant.userservice.feign.impl;

import com.fcant.userservice.bean.Country; import com.fcant.userservice.feign.ICountryService; import org.springframework.stereotype.Component;

/**

  • CountryServiceImpl
  • encoding:UTF-8 *
  • @author Fcant 14:24 2019/12/9 */ @Component public class CountryServiceImpl implements ICountryService {

    private static final Country ERROR = new Country(1L, “ERROR”); /**

    • queryCountry
    • Feign中没有指定@PathVariable(name = “countryId”)变量映射关系,必须通过name属性指定 *
    • @param countryId 城市ID
    • @return com.fcant.userservice.bean.Country
    • @author Fcant 10:53 2019/12/9 */ @Override public Country queryCountry(long countryId) { return ERROR; } }
  1. <a name="OArxo"></a>
  2. ### C.Fegin的异常处理属于熔断的范围,要在配置文件配置开启熔断-application.yml
  3. ```yaml
  4. feign:
  5. hystrix:
  6. enabled: true

4、关于Feign访问超时的配置

如果不配置访问超时则会报如下异常

  1. feign.RetryableException: Read timed out executing POST http://******

因为Feign接口调用分两层,Ribbon的调用Hystrix调用,理论上设置Ribbon的时间即可,但是Ribbon的超时时间和Hystrix的超时时间需要结合起来,按照木桶原则最低的就是Feign的超时时间,建议最好配置超时时间一致。经过配置一下application设置后,完美解决了问题。因为第三方接口中需要3~20秒不等的时间,所以这个数值也是根据自己的业务系统情况设置的。

  1. # hystrix的超时时间
  2. hystrix:
  3. command:
  4. default:
  5. execution:
  6. timeout:
  7. enabled: true
  8. isolation:
  9. thread:
  10. timeoutInMilliseconds: 30000
  11. # ribbon的超时时间-设置feign客户端超时时间(openfeign默认支持ribbon)
  12. ribbon:
  13. # 指的是建立连接所用的时间,适用于网络状况正常的情况下,两端连接所用的时间
  14. ReadTimeout: 30000
  15. # 指的是建立连接后从服务器读取到可用资源所用的时间
  16. ConnectTimeout: 30000

5、OpenFeign日志增强

Feign提供了日志打印功能,可以通过配置来调整日志级别,从而了解Feign中Http请求的细节。对Feign接口的调用情况进行检控和输出。

  • NONE:默认的,不显示任何日志。
  • BASIC:仅记录请求方法、URL、响应状态吗即执行时间。
  • HEADERS:除了BASIC中定义的信息之外,还有请求和响应的头信息。
  • FULL:除了HEADERS中定义的信息之外,还有请求和响应的正文及元数据。

    (1)添加配置类指明日志打印级别

    1. import feign.Logger;
    2. import org.springframework.context.annotation.Bean;
    3. import org.springframework.context.annotation.Configuration;
    4. @Configuration
    5. public class FeignConfig {
    6. @Bean
    7. Logger.Level feignLoggerLevel(){
    8. return Logger.Level.FULL;
    9. }
    10. }

    (2)application.yml/properties配置文件开启日志的Feign客户端

    1. logging:
    2. level:
    3. #feign日志以什么级别监控哪个接口
    4. com.atguigu.springcloud.service.PaymentFeignService: debug