智乐活

Spring Cloud Feign 声明式服务调用

2018/04/13 Share

在微服务的实践过程中,Spring Cloud Ribbon 和 Spring Cloud Hystrix 通常一起使用。

Spring Cloud Feign 是对这两个基础工具的更高层次封装,在 Netflix Feign 的基础上扩展了对 Spring MVC 的注解支持,提供了一种声明式的 Web 服务客户端定义方式。

快速入门

启动服务注册中心 eureka-server 及服务提供方 hello-service,创建 spring boot 工程 feign-consumer。

1.添加依赖

1
2
3
4
5
6
7
8
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-eureka</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-feign</artifactId>
</dependency>

2.注解启用 Feign

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package com.ulyssesss.feignconsumer;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.netflix.feign.EnableFeignClients;

@EnableFeignClients
@EnableDiscoveryClient
@SpringBootApplication
public class FeignConsumerApplication {

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

3.定义 HelloService 接口

1
2
3
4
5
6
7
8
9
10
11
12
package com.ulyssesss.feignconsumer.service;

import org.springframework.cloud.netflix.feign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;

@FeignClient("hello-service")
public interface HelloService {

@GetMapping("hello")
String hello(@RequestParam("p1") String p1, @RequestParam("p2") String p2);
}

其中 @FeignClient 指定服务名,Spring MVC 注解绑定具体的 REST 接口及请求参数。

注意在定义参数绑定时,@RequestParam 、@RequestHeader 等注解的 value 不能省略,Spring MVC 会根据参数名作为默认值,但 Feign 中必须通过 value 指定。

4.编写 Controller

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package com.ulyssesss.feignconsumer.web;

import com.ulyssesss.feignconsumer.service.HelloService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class FeignConsumerController {

@Autowired
HelloService helloService;

@GetMapping("hello")
public String hello(@RequestParam String p1, @RequestParam String p2) {
System.out.println("feign consumer get hello");
return helloService.hello(p1, p2);
}
}

5.配置服务注册中心地址

1
2
spring.application.name=feign-consumer
eureka.client.service-url.defaultZone=http://localhost:8761/eureka/

启动全部应用,访问 http://localhost:8080/hello?p1=a&p2=b ,feign-consumer 以声明式的服务调用访问 hello-service,返回 hello, a, b

继承特性

当使用 Spring MVC 注解来绑定服务接口时,几乎可以完全从服务提供方的 Controller 中复制,所以能够利用 Feign 的继承特性做进一步抽象,复用 REST 接口定义,减少编码量。

1.添加依赖

创建 Maven 工程 hello-service-api,由于需要用到 Spring MVC 的注解,所以添加 spring-boot-starter-web 依赖。

1
2
3
4
5
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<scope>provided</scope>
</dependency>

2.定义接口及 DTO

在 hello-service-api 中定义能够复用的 DTO 和接口定义。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package com.ulyssesss.helloserviceapi.service;

import com.ulyssesss.helloserviceapi.dto.User;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;

public interface HelloService {

@GetMapping("hello")
String hello(@RequestParam("p1") String p1, @RequestParam("p2") String p2);

@GetMapping("user")
User user();

@PostMapping("post")
String post();
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package com.ulyssesss.helloserviceapi.dto;

public class User {

private String name;
private int age;

public User() {}

public User(String name, int age) {
this.name = name;
this.age = age;
}

@Override
public String toString() {
return "User{" + "name='" + name + '\'' + ", age=" + age + '}';
}
// set get
}

3.改写 hello-service

在 hello-service 中引入 hello-service-api 依赖,重写 HelloController。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
package com.ulyssesss.helloservice.web;

import com.ulyssesss.helloserviceapi.dto.User;
import com.ulyssesss.helloserviceapi.service.HelloService;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class HelloController implements HelloService {

@Override
public String hello(@RequestParam String p1, @RequestParam String p2) {
System.out.println("hello service get hello");
return "hello, " + p1 + ", " + p2;
}

@Override
public User user() {
System.out.println("hello service get user");
return new User("Jack", 22);
}

@Override
public String post() {
System.out.println("hello service post");
return "post";
}
}

4.改写 feign-consumer

在 feign-consumer 中引入 hello-service-api 依赖,创建 RefactorHelloService 继承自 HelloService。

1
2
3
4
5
6
7
8
package com.ulyssesss.feignconsumer.service;

import com.ulyssesss.helloserviceapi.service.HelloService;
import org.springframework.cloud.netflix.feign.FeignClient;

@FeignClient(name = "hello-service")
public interface RefactorHelloService extends HelloService {
}

修改 FeignConsumerController,注入 RefactorHelloService。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
package com.ulyssesss.feignconsumer.web;

import com.ulyssesss.feignconsumer.service.RefactorHelloService;
import com.ulyssesss.helloserviceapi.dto.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class FeignConsumerController {

@Autowired
RefactorHelloService refactorHelloService;

@GetMapping("hello")
public String hello(@RequestParam String p1, @RequestParam String p2) {
System.out.println("feign consumer get hello");
return refactorHelloService.hello(p1, p2);
}

@GetMapping("user")
public User user() {
System.out.println("feign consumer get user");
return refactorHelloService.user();
}

@PostMapping("post")
public String post() {
System.out.println("feign consumer post");
return refactorHelloService.post();
}
}

启动全部应用,访问 http://localhost:8080/hello?p1=a&p2=b ,feign-consumer 以声明式的服务调用访问 hello-service,返回 hello, a, b

使用 Spring Cloud Feign 的继承特性,可以将接口从 Controller 中玻璃,配合 Maven 私有仓库可以实现接口定义的共享,减少服务消费方的绑定配置。

Ribbon 及 Hystrix 配置

Spring Cloud Feign 的客户端负载均衡通过 Spring Cloud Ribbon 实现,通过配置 Ribbon 可以定义客户端调用参数。Riibon 和 Hystrix 的配置如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
spring.application.name=feign-consumer
eureka.client.service-url.defaultZone=http://localhost:8761/eureka/

## 启用 hystrix
feign.hystrix.enabled=true
## 全局超时熔断时间
hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds=10000

## 全局连接超时时间
ribbon.ConnectTimeout=250
## 全局接口调用超时时间
ribbon.ReadTimeout=10000
## 全局重试所有请求(POST 请求等)开关
ribbon.OkToRetryOnAllOperations=false

## 针对 hello-service 服务,重试切换的实例数
hello-service.ribbon.MaxAutoRetriesNextServer=1
## 针对 hello-service 服务,对当前实例重试次数
hello-service.ribbon.MaxAutoRetries=0

服务降级

Hystrix 提供的服务降级是容错的重要方法,由于 Feign 在定义服务客户端时将 HystrixCommand 的定义进行了封装,导致无法使用 @HystrixCommand 的 fallback 参数指定降级逻辑。

Spring Cloud Feign 提供了一种简单的定义服务降级的方式,创建 HelloServiceFallback 实现 HelloService 接口,通过 @Component 声明为 Bean,在 HelloServiceFallback 中实现具体的降级逻辑,最后在 @FeignClient 中通过 fallback 属性声明处理降级逻辑的 Bean。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package com.ulyssesss.feignconsumer.service;

import com.ulyssesss.helloserviceapi.dto.User;
import org.springframework.stereotype.Component;

@Component
public class HelloServiceFallback implements RefactorHelloService {

@Override
public String hello(String p1, String p2) {
return "error";
}

@Override
public User user() {
return new User("error", 0);
}

@Override
public String post() {
return "error";
}
}
1
2
3
4
5
6
7
8
package com.ulyssesss.feignconsumer.service;

import com.ulyssesss.helloserviceapi.service.HelloService;
import org.springframework.cloud.netflix.feign.FeignClient;

@FeignClient(name = "hello-service", fallback = HelloServiceFallback.class)
public interface RefactorHelloService extends HelloService {
}

启动服务后断开服务提供方 hello-service,访问 feign-consumer 接口,feign-consumer 会按照 HelloServiceFallback 中的定义执行降级逻辑。

示例代码 欢迎 Star

CATALOG
  1. 1. 快速入门
    1. 1.1. 1.添加依赖
    2. 1.2. 2.注解启用 Feign
    3. 1.3. 3.定义 HelloService 接口
    4. 1.4. 4.编写 Controller
    5. 1.5. 5.配置服务注册中心地址
  2. 2. 继承特性
    1. 2.1. 1.添加依赖
    2. 2.2. 2.定义接口及 DTO
    3. 2.3. 3.改写 hello-service
    4. 2.4. 4.改写 feign-consumer
  3. 3. Ribbon 及 Hystrix 配置
  4. 4. 服务降级