Spring Cloud Feign(声明式/模板化的HTTP客户端)

来源

Feign使得 Java HTTP 客户端编写更方便。Feign 灵感来源于RetrofitJAXRS-2.0WebSocketFeign 最初是为了降低统一绑定DenominatorHTTP API 的复杂度,不区分是否支持 Restful

Retrofit是Square开发的一个AndroidJavaREST客户端库。
github地址:https://github.com/square/retrofit

JAX-RS(Java API for RESTful Web Services) 2.0 又称JSR 339 不仅定义了一套用于构建 RESTful 网络服务的 API,同时也通过增强客户端 API 功能简化了REST 客户端的构建过程。
github地址:https://github.com/eclipse-ee4j/jaxrs-api
WebSocket 是一种网络通信协议。RFC6455 定义了它的通信标准。
协议说明:https://tools.ietf.org/html/rfc6455

Denominator是一个用于操作DNS云的可移植Java
github地址:https://github.com/Netflix/Denominator

简介

Spring Cloud Netflix 的微服务都是以 HTTP 接口的形式暴露的,
所以调用方式有:
JDK原生的URLConnection
Apache的Http Client
Netty的异步HTTP Client
Spring的RestTemplate
Feign
而 Feign 是一个使用起来更加方便的 HTTP 客戶端,使用起来就像是调用自身工程的方法,而感觉不到是调用远程方法。feign还支持可插拔的编码器和解码器,Spring在用的时候增加了对@requestMapping的处理,Feign 的目的是尽量的减少资源和代码来实现和HTTP API的连接。通过自定义的编码解码器以及错误处理,可以编写任何基于文本的HTTP API

github地址:https://github.com/OpenFeign/feign
在源码中可以看到子模块包括RibbonHystrix,所以feign是基于RibbonHystrix的声明式服务调用组件。Feign是一种声明式、模板化的HTTP客户端。

`

实现声明式REST客户端Feign

引入Spring Cloud Feign依赖以及相关依赖

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>

注:网上有的文章说引入的依赖是spring-cloud-starter-feign,其实官方不再推荐使用,推荐使用spring-cloud-starter-openfeign

在应用主类中通过@EnableFeignClients注解开启Feign功能,因为要注册服务,所以还要使用@EnableEurekaClient

@SpringBootApplication
@EnableFeignClients
@EnableEurekaClient
public class SpringcloudFeignApplication {

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

修改yml配置文件

server:
  port: 8088
eureka:
  client:
    service-url:
      defaultZone: http://localhost:9090/eureka
spring:
  application:
    name: springcloud-feign-client

重点:Feign声明式/模块化体现

接下来定义服务接口类
使用@FeignClient("springcloud-eureka-client")注解来绑定该接口对应springcloud-eureka-client服务

@Component
@FeignClient(name = "springcloud-eureka-client")
public interface Client {
    @GetMapping("/client")
    public String getInfo();
}

通过声明式的注解,提供一个供其它服务调用的 Client。
@FeignClient用于通知Feign组件对该接口进行代理(不需要编写接口实现),使用者可直接通过@Autowired注入
注:多个feignCLient类中@FeignClient注解中的name值不能重复,url可以重复
Spring Cloud应用在启动时,Feign会扫描标有@FeignClient注解的接口,生成代理,并注册到Spring容器中。生成代理时Feign会为每个接口方法创建一个RequetTemplate对象,该对象封装了HTTP请求需要的全部信息,请求参数名、请求方法等信息都是在这个过程中确定的,Feign的模板化就体现在这里。

注意:绑定的接口服务必须是对应服务上存在的接口
例如我把接口名改为client123便会报如下错误

image.png

也就是找不到对应的接口服务,所绑定的springcloud-eureka-client没有client123接口

这是springcloud-eureka-client服务中的client接口

@RestController
public class DiscoveryController {
    @Autowired
    private DiscoveryClient discoveryClient;
    @Value("${server.port}")
    private String ip;

    @GetMapping("/client")
    public String client() {
        String services = "调用的服务是: " + discoveryClient.getServices()+" 对应的端口号 :"+ip;
        System.out.println("调用的服务是: " + discoveryClient.getServices()+" 对应的端口号 :"+ip);
        return services;
    }
}

调用上面定义的接口

@RestController
public class FeignClientController {
    @Autowired
    Client client;
    @GetMapping("/info")
    public String getInfo(){
        return client.getInfo();
    };
}

这里的接口名就任意取了,这里主要是获取绑定服务的接口的信息。

整体项目结构

Eureka服务端 端口号是9090
Eureka客户端 端口号是8082 服务名springcloud-eureka-client
feign消费者客户端 端口号是8088
(如需测试负载均衡的可以再启动一个端口号为8081的服务,服务名同样是springcloud-eureka-client)

启动服务进行测试

先启动服务端再启动客户端,客户端启动顺序没要求
访问http://localhost:9090服务注册中心

image.png

在服务注册中心可以看到,服务提供者和消费者都注册好了

访问feign定义的接口/infohttp://localhost:8088/info

image.png

测试负载均衡

服务列表

image.png

访问http://localhost:8088/info
image.png

刷新下,再次访问
image.png

实现了与Ribbon相同的负载均衡功能

实现Feign熔断

需要先写一个调用延迟或失败时调用的类

@Component
public class FailClass implements Client{
    @Override
    public String getInfo() {
        return "服务中断连接,请联系管理员";
    }
}

需要实现自定义服务接口的方法,以至于显示对应服务的对应接口调用失败的返回信息
然后在自定义服务接口中添加fallback异常处理回调。

@Component
@FeignClient(name = "springcloud-eureka-client",fallback = FailClass.class)
public interface Client {
    @GetMapping("/client")
    public String getInfo();
}

还需要在yml配置文件中开启熔断,默认为false

feign:
  hystrix:
    enabled: true

关闭两个springcloud-eureka-client服务后访问http://localhost:8088/info会显示

image.png

总结:

其实通过Feign封装了HTTP调用服务方法,使得客户端像调用本地方法那样直接调用方法,Feign本质上是个HTTP客户端
Feign继承特性用起来确实很方便,但是也带来一个问题,就是服务提供者和服务消费者的耦合度太高,此时如果服务提供者修改了一个接口的定义,服务消费者可能也得跟着变化,进而带来很多未知的工作量,如果通过此方法来实现接口共享的话,建议严格遵守面向对象的开闭原则,尽可能低做好前后版本兼容,防止因为版本原因造成接口定义的不一致。

推荐文章
Feign相关参数配置:https://blog.csdn.net/u012702547/article/details/78327668?locationNum=10&fps=1
Feign源码分析:https://blog.csdn.net/forezp/article/details/73480304

推荐阅读更多精彩内容