1 概述

Feign 是声明式 HTTP 客户端,它屏蔽了底层 HTTP 的调用过程,让编写 Web 服务客户端更加容易,使用 Feign 只需要创建接口并添加上简单的注解,就可以以面向接口编程的方式发起远程 HTTP 服务调用。Feign 具有可插拔的注解支持,包括 Feign 注解和 JAX-RS 注解。Feign 还支持可插拔编码器和解码器。Spring Cloud 添加了对 Spring MVC 注释的支持,并支持使用 Spring Web 中默认使用的相同 HttpMessageConverters。 Spring Cloud 集成了 Ribbon 和 Eureka 以及 Spring Cloud LoadBalancer,以在使用 Feign 时提供负载均衡的 HTTP 客户端。

通过前面的文章我们已经知道 Ribbon 是一个客户端负载均衡器,而 Feign 是 Spring Cloud 组件中的一个轻量级 RESTful 的 HTTP 客户端,Feign 内置了 Ribbon 。OpenFeign 是 Spring Cloud 在 Feign 的基础上增加了对 Spring MVC 的注解支持,OpenFeign 的 @FeignClient 可以解析SpringMVC 的 @RequestMapping 注解下的接口,并通过动态代理的方式产生代理类,通过代理类发起远程服务调用。

2 使用OpenFeign

2.1环境准备

为了模拟使用 OpenFeign 发起远程调用,我们需要准备三个服务,注册中心服务 cloud-eureka-server、提供者服务 cloud-feign-provider、消费者服务 cloud-feign-consumer 。并先启动注册中心服务 cloud-eureka-server 。

2.2 编写提供者服务

提供者服务就是一个普通的微服务,向外提供 Web 服务接口,POM 依赖如下:

<dependencies>
     <!--公共包-->
     <dependency>
         <groupId>com.luke.springcloud</groupId>
         <artifactId>cloud-api-commons</artifactId>
         <version>1.0</version>
     </dependency>
     <!--spring boot-->
     <dependency>
         <groupId>org.springframework.boot</groupId>
         <artifactId>spring-boot-starter-web</artifactId>
     </dependency>
     <dependency>
         <groupId>org.springframework.boot</groupId>
         <artifactId>spring-boot-starter-actuator</artifactId>
     </dependency>
     <!--eureka-client-->
     <dependency>
         <groupId>org.springframework.cloud</groupId>
         <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
     </dependency>
     <!--lombok-->
     <dependency>
         <groupId>org.projectlombok</groupId>
         <artifactId>lombok</artifactId>
     </dependency>
 </dependencies>

application.yml 文件内容:

server:
  port: 8084

spring:
  application:
    name: cloud-feign-provider

#连接Eureka注册中心
eureka:
  instance:
    prefer-ip-address: true #使用ip地址注册
  client:
    register-with-eureka: true
    fetch-registry: true
    service-url:
      defaultZone: http://admin:123456@localhost:7001/eureka

编写一个 Controller 类 FeignProviderController:

@Slf4j
@RequestMapping("/provider")
@RestController
public class FeignProviderController {

    @GetMapping("/echo/{id}")
    public UserInfo echo(@PathVariable("id") Long id){
        log.info("echo,id:{}",id);
        return new UserInfo(id,"luke",18,new Date());
    }

}

主启动类 CloudFeignProviderMain8084 :

@SpringBootApplication
@EnableDiscoveryClient
public class CloudFeignProviderMain8084 {

    public static void main(String[] args) {
        SpringApplication.run(CloudFeignProviderMain8084.class,args);

    }
}

2.3 编写消费者服务

cloud-feign-consumer 除了常规依赖外,额外添加 openfeign 依赖:

<dependencies>
        <dependency>
            <groupId>com.luke.springcloud</groupId>
            <artifactId>cloud-api-commons</artifactId>
            <version>1.0</version>
        </dependency>
        <!--spring boot-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>
        <!--eureka-client-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        </dependency>
        <!--OpenFeign-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-openfeign</artifactId>
        </dependency>
        <!--lombok-->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>
    </dependencies>

application.yml 文件内容:

server:
  port: 8085

spring:
  application:
    name: cloud-feign-consumer

#连接Eureka注册中心
eureka:
  instance:
    prefer-ip-address: true #使用ip地址注册
  client:
    register-with-eureka: true
    fetch-registry: true
    service-url:
      defaultZone: http://admin:123456@localhost:7001/eureka

定义一个 OpenFeign 接口,使用 OpenFeign 将通过面向接口编程,简化 HTTP 远程调用过程:

@FeignClient(name = "cloud-feign-provider") //指定目标服务的名称
public interface ProviderFeignClient {

    @GetMapping("/provider/echo/{id}")
    UserInfo echo(@PathVariable("id") Long id); //目标服务接口信息

}

定义一个 Controller 类 FeignConsumerController,通过该 FeignConsumerController 依赖 ProviderFeignClient 发起远程服务调用:

@Slf4j
@RequestMapping("/consumer")
@RestController
public class FeignConsumerController {

	/**依赖OpenFeign远程服务接口*/
    @Resource
    private ProviderFeignClient providerFeignClient;

    @GetMapping("/echo/{id}")
    public UserInfo consumerEcho(@PathVariable("id") Long id){
        log.info("consumerEcho,id:{}",id);
        return providerFeignClient.echo(id); //远程服务调用
    }

}

主启动类添加 @EnableFeignClients 注解,开启 OpenFeign 功能:

@SpringBootApplication
@EnableDiscoveryClient
@EnableFeignClients //开启Feign功能
public class CloudFeignConsumerMain8085 {

    public static void main(String[] args) {
        SpringApplication.run(CloudFeignConsumerMain8085.class,args);
    }

}

使用到的 Bean 类型 UserInfo 放在了 公共依赖模块 cloud-api-commons :

@NoArgsConstructor
@AllArgsConstructor
@Data
public class UserInfo {

    private Long id;

    private String username;

    private Integer age;

    private Date birthday;

}

2.3 测试

先启动注册中心服务,后启动一个提供者服务和两个消费者服务,在浏览器打开注册中心管理台 http://localhost:7001/ :

springcloud实现异步远程调用 springcloud远程调用原理_OpenFeign


在浏览器输入地址 http://localhost:8085/consumer/echo/10 连续访问多次,可以在控制台看到会轮询调用两个提供者服务,OpenFeign 使用 Ribbon 去做负载均衡,默认使用轮询负载均衡策略。

springcloud实现异步远程调用 springcloud远程调用原理_springcloud实现异步远程调用_02

3 日志级别

Feign 默认情况下是不打印任何调用日志的,在实际开发中尤其是接口联调时,很多时候我们希望查看到 Feign 的日志,以便在出现问题时及时处理。开启 Feign 日志的方式主要有两种:代码方式和配置文件方式,另外每种配置方式还分为局部配置和全局配置。

Feign 的日志级别和 Spring Boot 的不一样,所以不能直接通过配置 Spring Boot 的日志级别来开启。下表是 Feign 日志级别说明:

日志级别

打印内容

NONE(默认)

不答应任何日志

BASIC

仅记录请求方法、URL、响应状态代码及执行时间

HEADERS

记录 BASIC 级别内容的继承上,还记录请求响应的 HTTP 协议头部信息

FULL

记录整合请求、响应过程的header、body 和 元数据信息

3.1 代码方式配置

定义一个 Feign 配置类 MyFeignConfig ,指定日志级别,内容如下:

public class MyFeignConfig {

    @Bean
    public Logger.Level level(){
        return Logger.Level.FULL;
    }
    
}

局部配置方式:使用在某个 Feign 接口上。

@FeignClient(name = "cloud-feign-provider",configuration = MyFeignConfig.class)
public interface ProviderFeignClient {

    @GetMapping("/provider/echo/{id}")
    UserInfo echo(@PathVariable("id") Long id);

}

在 application.yml 指定该 ProviderFeignClient 日志级别为 debug:

logging:
  level: 
    #这里需要配置为debug,否则feign的日志级别配置不会生效
    com.luke.springcloud.feign.ProviderFeignClient: debug

全局配置方式:对所有 Feign 接口生效。

@SpringBootApplication
@EnableDiscoveryClient
@EnableFeignClients(defaultConfiguration = MyFeignConfig.class) //开启Feign功能,指定配置类
public class CloudFeignConsumerMain8085 {

    public static void main(String[] args) {
        SpringApplication.run(CloudFeignConsumerMain8085.class,args);
    }

}

application.yml 文件指定 Feign 接口所在包级别为 debug:

logging:
  level:
    com.luke.springcloud.feign: debug #局部为指定类,全局为所在包

3.2 配置文件方式配置

局部配置方式:使用在某个 Feign 接口上。

# Feign日志级别
feign:
  client:
    config:
      cloud-feign-provider:
        loggerLevel: full

logging:
  level:
    com.luke.springcloud.feign.ProviderFeignClient: debug

全局配置方式:对所有 Feign 接口生效。

# Feign日志级别
feign:
  client:
    config:
      default:
        loggerLevel: full

logging:
  level:
    com.luke.springcloud.feign: debug

3.3 测试日志级别

配置完成后,重启消费者服务 cloud-feign-consumer ,在浏览器输入地址 http://localhost:8085/consumer/echo/10 可以在控制台看到较完整的 HTTP 调用过程日志:

2020-05-12 14:25:21.753 DEBUG 128656 --- [nio-8085-exec-1] c.l.s.feign.ProviderFeignClient          : [ProviderFeignClient#echo] <--- HTTP/1.1 200 (259ms)
2020-05-12 14:25:21.753 DEBUG 128656 --- [nio-8085-exec-1] c.l.s.feign.ProviderFeignClient          : [ProviderFeignClient#echo] connection: keep-alive
2020-05-12 14:25:21.753 DEBUG 128656 --- [nio-8085-exec-1] c.l.s.feign.ProviderFeignClient          : [ProviderFeignClient#echo] content-type: application/json
2020-05-12 14:25:21.753 DEBUG 128656 --- [nio-8085-exec-1] c.l.s.feign.ProviderFeignClient          : [ProviderFeignClient#echo] date: Tue, 12 May 2020 06:25:21 GMT
2020-05-12 14:25:21.753 DEBUG 128656 --- [nio-8085-exec-1] c.l.s.feign.ProviderFeignClient          : [ProviderFeignClient#echo] keep-alive: timeout=60
2020-05-12 14:25:21.753 DEBUG 128656 --- [nio-8085-exec-1] c.l.s.feign.ProviderFeignClient          : [ProviderFeignClient#echo] transfer-encoding: chunked
2020-05-12 14:25:21.753 DEBUG 128656 --- [nio-8085-exec-1] c.l.s.feign.ProviderFeignClient          : [ProviderFeignClient#echo] 
2020-05-12 14:25:21.755 DEBUG 128656 --- [nio-8085-exec-1] c.l.s.feign.ProviderFeignClient          : [ProviderFeignClient#echo] {"id":10,"username":"luke","age":18,"birthday":"2020-05-12T06:25:21.745+0000"}
2020-05-12 14:25:21.755 DEBUG 128656 --- [nio-8085-exec-1] c.l.s.feign.ProviderFeignClient          : [ProviderFeignClient#echo] <--- END HTTP (78-byte body)

4 小结

文章首先介绍了 OpenFeign 组件,它是一个声明式的 HTTP 客户端,内部整合了 Ribbon 作为其负载均衡实现。接着我们通过一个案例快速将 OpenFeign 整合到项目中,成功发起远程 HTTP 服务调用。最后我们还介绍了 OpenFeign 的日志级别,配置 OpenFeign 日志输出能够在开发调试过程中帮助开发者了解底层 HTTP 的调用过程,快速处理 bug ,生产环境建议关闭。