什么是Spring Cloud Feign
Spring Cloud Feign 是基于 Netflix Feign 实现的,整合了 Spring Cloud Ribbon 和 Spring Cloud Hystrix,除了提供这两者的强大功能之外,还提供了一种声明式的 Web 服务客户端定义方式。
一、快速入门
1、创建一个 Spring Boot 基础工程,取名为 feign-consumer,并在 pom.xml 中引入 spring-cloud-starter-eureka 和 spring-cloud-starter-feign 依赖。
1
2
3
4
5
6 1<dependency>
2 <groupId>org.springframework.cloud</groupId>
3 <artifactId>spring-cloud-starter-feign</artifactId>
4</dependency>
5
6
2、在主类上通过 @EnableFeignClients 注解开启 Spring Cloud Feign 的支持功能。
1
2
3
4
5
6
7
8
9
10 1@SpringBootApplication
2@EnableDiscoveryClient
3@EnableFeignClients
4public class FeignConsumerApplication {
5 public static void main(String[] args) {
6 SpringApplication.run(FeignConsumerApplication.class, args);
7 }
8}
9
10
3、定义 HelloService 接口,通过 @FeignClient 注解指定服务名来绑定服务,然后再使用 Spring MVC 的注解来绑定具体该服务提供的 REST 接口。
1
2
3
4
5
6
7 1@FeignClient(value = "hello-service")
2public interface HelloService {
3 @RequestMapping(value = "/hello")
4 String hello();
5}
6
7
4、创建一个 ConsumerController 来实现对 Feign 客户端的调用。使用 @Autowired 直接注入上面定义的 HelloService 实例,并在 helloConsumer 函数中调用这个绑定了 hello-service 服务接口的客户端来向该服务发起 /hello接口的调用。
1
2
3
4
5
6
7
8
9
10
11
12
13 1@RestController
2public class ConsumerController {
3
4 @Autowired
5 HelloService helloService;
6
7 @RequestMapping(value = "feign-consumer", method = RequestMethod.GET)
8 public String helloConsumer(){
9 return helloService.hello();
10 }
11}
12
13
5、在 application.properties 中指定注册中心,并定义自身的服务名为 feign-consumer
1
2
3
4
5
6 1spring.application.name=feign-consumer
2server.port=9001
3
4eureka.client.service-url.defaultZone=http://localhost:1111/eureka/
5
6
6、验证
启动 eureka-server 和 两个hello-service,然后启动 feign-consumer,发送请求到 http://localhost:9001/feign-consumer,正确返回。
与 Ribbon 不同的是,通过 Feign 我们只需定义服务绑定接口,以声明式的方法,优雅而简单地实现了服务调用。
二、参数绑定
传入参数和反回复杂对象的用法:
1、先扩展一下服务提供方 hello-service 。增加下面这些接口定义。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 1 @RequestMapping(value = "/hello1", method = RequestMethod.GET)
2 public String hello1(@RequestParam String name){
3 return "HELLO " + name;
4 }
5
6 @RequestMapping(value = "/hello2", method = RequestMethod.GET)
7 public User hello2(@RequestHeader String name, @RequestHeader Integer age){
8 return new User(name, age);
9 }
10
11 @RequestMapping(value = "/hello3", method = RequestMethod.POST)
12 public String hello3(@RequestBody User user){
13 return "HELLO," + user.getName()+","+user.getAge();
14 }
15
16
2、定义User 对象,需要注意,这里必须要有User 的默认构造函数。不然,Spring Cloud Feign 根据 JSON 字符串转换 User 对象会抛出异常。
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
35
36
37
38
39 1public class User {
2 private String name;
3
4 private Integer age;
5
6 public User() {
7 }
8
9 public User(String name, Integer age) {
10 this.name = name;
11 this.age = age;
12 }
13
14 public String getName() {
15 return name;
16 }
17
18 public void setName(String name) {
19 this.name = name;
20 }
21
22 public Integer getAge() {
23 return age;
24 }
25
26 public void setAge(Integer age) {
27 this.age = age;
28 }
29
30 @Override
31 public String toString() {
32 return "User{" +
33 "name='" + name + '\'' +
34 ", age=" + age +
35 '}';
36 }
37}
38
39
3、在 feign-consumer 中创建与上面一样的 User 类。
4、在 HelloService 接口中增加对上述三个新增接口的绑定声明。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 1@FeignClient(value = "hello-service")
2public interface HelloService {
3
4 @RequestMapping(value = "/index")
5 String hello();
6
7 @RequestMapping(value = "/hello1", method = RequestMethod.GET)
8 String hello1(@RequestParam(value = "name") String name);
9
10 @RequestMapping(value = "/hello2", method = RequestMethod.GET)
11 User hello2(@RequestParam(value = "name") String name, @RequestHeader(value = "age") Integer age);
12
13 @RequestMapping(value = "/hello3", method = RequestMethod.POST)
14 String hello3(@RequestBody User user);
15}
16
17
5、在 ConsumerController 中新增一个 /feign-consumer2 接口
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22 1@RestController
2public class ConsumerController {
3
4 @Autowired
5 HelloService helloService;
6
7 @RequestMapping(value = "feign-consumer", method = RequestMethod.GET)
8 public String helloConsumer(){
9 return helloService.hello();
10 }
11
12 @RequestMapping(value = "/feign-consumer2", method = RequestMethod.GET)
13 public String helloConsumer2(){
14 StringBuilder sb = new StringBuilder();
15 sb.append(helloService.hello1("didi")).append("\n");
16 sb.append(helloService.hello2("didi", 18)).append("\n");
17 sb.append(helloService.hello3(new User("didi", 20))).append("\n");
18 return sb.toString();
19 }
20}
21
22
6、验证
http://localhost:9001/feign-consumer2 ,触发 HelloService 对新增接口的调用
三、继承特性
通过 Spring Cloud Feign 的继承特性来实现 REST 接口定义的复用
1、为了能够复用 DTO 与接口定义,我们先创建一个基础的 Maven 工程,命名为 hello-service-api。
2、在 hello-service-api 中需要定义可同时复用于服务端与客户端的接口,我们要使用到 Spring MVC 的注解,所以在 pom.xml 中引入 spring-boot-starter-web 依赖
3、将 User 对象复制到 hello-service-api 工程中。
4、在 hello-service-api 工程中创建 HelloService 接口
1
2
3
4
5
6
7
8
9
10
11
12
13
14 1@RequestMapping(value = "/refactor")
2public interface HelloService {
3
4 @RequestMapping(value = "/hello4", method = RequestMethod.GET)
5 String hello4(@RequestParam(value = "name") String name);
6
7 @RequestMapping(value = "/hello5", method = RequestMethod.GET)
8 User hello5(@RequestHeader(value = "name") String name, @RequestHeader(value = "age") Integer age);
9
10 @RequestMapping(value = "/hello6", method = RequestMethod.POST)
11 String hello6(@RequestBody User user);
12}
13
14
5、执行命令 mvn install 将该模块构建到本地仓库。
6、对 hello-service 进行重构,在 pom.xml 的 dependency 节点中,新增对 hello-service-api 的依赖。
1
2
3
4
5
6
7 1<dependency>
2 <groupId>com.didispace</groupId>
3 <artifactId>hello-service-api</artifactId>
4 <version>0.0.1-SNAPSHOT</version>
5</dependency>
6
7
7、创建 RefactorHelloController 类实现 hello-service-api 中定义的 HelloService 接口,并参考之前的 HelloController 来实现这三个接口
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20 1@RestController
2public class RefactorHelloController implements HelloService {
3
4 @Override
5 public String hello4(@RequestParam(value = "name") String name) {
6 return "HELLO " + name;
7 }
8
9 @Override
10 public User hello5(@RequestHeader(value = "name") String name, @RequestHeader(value = "age") Integer age) {
11 return new User(name, age);
12 }
13
14 @Override
15 public String hello6(@RequestBody User user) {
16 return "HELLO," + user.getName()+","+user.getAge();
17 }
18}
19
20
8、完成了服务提供者的重构,接下来在服务消费者 feign-consumer 的 pom.xml 文件中,如在服务提供者一样,新增对 hello-service-api 的依赖。
9、创建 RefactorHelloService 接口,并继承 hello-service-api 包中的 HelloService 接口,然后添加 @FeignClient 注解来绑定服务。
1
2
3
4
5 1@FeignClient (value="Hello-Service")
2public interface RefactorHelloService extends HelloService{
3}
4
5
10、在ConsumerController中注入RefactorHelloService 并新增一个请求来触发
1
2
3
4
5
6
7
8
9
10 1@RequestMapping(value = "/feign-consumer3",method=RequestMethod.GET)
2 public String helloConsumer3(){
3 StringBuilder sb = new StringBuilder();
4 sb.append(refactorHelloService.hello("MIMI")).append("\n");
5 sb.append(refactorHelloService.hello("MIMI","123456")).append("\n");
6 sb.append(refactorHelloService.hello(new com.didispace.dto.User("MIMI","123456"))).append("\n");
7 return sb.toString();
8 }
9
10
四、Ribbon配置
1、全局配置
1
2
3
4 1ribbon.ConnectTimeout=500
2ribbon.ReadTimeout=5000
3
4
2、指定服务配置
1
2
3
4
5
6
7 1HELLO-SERVICE.ribbon.ConnectTimeout=500
2HELLO-SERVICE.ribbon.ReadTimeout=2000
3HELLO-SERVICE.ribbon.okToRetryOnAllOperations=true
4HELLO-SERVICE.ribbon.MaxAutoRetriesNextServer=2
5HELLO-SERVICE.ribbon.MaxAutoRetries=1
6
7
3、重试机制
默认实现
五、Hystrix配置
1、全局配置
1
2
3
4
5
6
7 1hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds=5000
2#关闭 Hystrix 功能
3#feign.hystrix.enabled=false
4#关闭熔断功能
5#hystrix.command.default.execution.timeout.enabled=false
6
7
2、禁用Hystrix
1
2
3 1feign.hystrix.enabled=false
2
3
针对某个客户端关闭 Hystrix ,通过使用 @Scope(“prototype”) 注解为指定的客户端配置 Feign.Builder 实例
构建一个关闭 Hystrix 的配置类
1
2
3
4
5
6
7
8
9
10
11
12 1@Configuration
2public class DisableHystrixConfiguration {
3
4 @Bean
5 @Scope("prototype")
6 public Feign.Builder feignBuilder(){
7 return Feign.builder();
8 }
9
10}
11
12
在 HelloService 的 @FeignClient 注解中,通过 configuration 参数引入上面实例的配置
1
2
3
4
5
6
7 1@FeignClient(value = "SERIVCE-USER",configuration = DisableHystrixConfiguration.class)
2@Service
3public interface HelloService {
4 ···
5}
6
7
4、指定命令配置
对 /hello 接口的熔断时间的配置
1
2
3 1hystrix.command.hello.execution.isolation.thread.timeoutInMilliseconds=5000
2
3
5、服务降级配置
对 feign-consumer 工程进行改造,为HelloService接口实现一个服务降级类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24 1@Component
2public class HelloServiceFallBack implements HelloService {
3 @Override
4 public String hello() {
5 return "error";
6 }
7
8 @Override
9 public String hello(@RequestParam("name") String name) {
10 return "error";
11 }
12
13 @Override
14 public User hello(@RequestHeader("name") String name,@RequestHeader("password") String password) {
15 return new User("未知","0");
16 }
17
18 @Override
19 public String hello(User user) {
20 return "error";
21 }
22}
23
24
在服务绑定接口 HelloService 中,通过 @FeignClient 注解的 fallback 属性来指定对应的服务降级实现类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18 1@FeignClient(value = "SERIVCE-USER",fallback = HelloServiceFallBack.class)
2@Service
3public interface HelloService {
4
5 @RequestMapping("/hello")
6 String hello();
7
8 @GetMapping(value = "/hello1")
9 String hello(@RequestParam("name") String name);
10
11 @GetMapping(value = "/hello2")
12 User hello(@RequestHeader("name") String name,@RequestHeader("password") String password);
13
14 @PostMapping(value = "/hello3")
15 String hello(@RequestBody User user);
16}
17
18
六、其他配置
1、请求压缩
1
2
3
4
5
6
7 1feign.compression.request.enabled=true
2feign.compression.response.enabled=true
3#设置压缩的大小下限,超过的才进行压缩,以下配置为默认值
4feign.compression.request.mime-types=text/xml,application/xml,application/json
5feign.compression.request.min-request-size=2048
6
7
2、日志配置
1
2
3 1logging.level.com.didispace.web.HelloService=DEBUG
2
3
feign-consumer 启动类配置
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 1@SpringBootApplication
2@EnableFeignClients
3@EnableDiscoveryClient
4public class SpringcloudFeignApplication {
5
6 @Bean
7 Logger.Level feignLoggerLevel(){
8 return Logger.Level.FULL;
9 }
10
11 public static void main(String[] args) {
12 SpringApplication.run(SpringcloudFeignApplication.class, args);
13 }
14}
15
16
也可以通过实现配置类,然后在具体的Feign 客户端来指定配置类以实现是否要调整不同的日志界别
1
2
3
4
5
6
7
8
9
10
11 1@Configuration
2public class FullLogConfiguration {
3
4 @Bean
5 Logger.Level feignLoggerLevel(){
6 return Logger.Level.FULL;
7 }
8
9}
10
11
1
2
3
4
5
6 1@FeignClient(name="HELLO_SERVICE",configuration = FullLogConfiguration.class
2public interface HelloService{
3 ...
4}
5
6
Feign的Logger级别:
NONE
BASIC
HEADERS
FULL