Spring Cloud Feign 使用

/ java / 没有评论 / 430浏览

介绍

Spring Cloud Feign 基于Netflix Feign实现, 整合了Spring Cloud RibbonSpring Cloud Hystrix, 除了提供这两个的功能外, 还提供了一种声明式的Web服务客户端定义方式,

在使用Spring Cloud Ribbon时, 通常会利用它对RestTemplate的请求拦截实现对依赖服务的接口调用, 而RestTemplate实现了对HTTP请求的封装处理, 形成了一套模板化的调用方法, Spring Cloud Feign在次基础上做了进一步的封装, 定义和实现以来服务接口的定义. 在Spring Cloud Feign的实现下, 只需要创建一个接口, 并用注解的方式配置, 即可完成对服务提供方的接口绑定.

使用

引入依赖


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

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>

在启动类加入注解@EnableFeignClients开启Spring Cloud Feign功能

@EnableFeignClients
@EnableDiscoveryClient
@SpringBootApplication
public class AnybboFeignConsumerApplication {

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

}

定义HelloService接口, 通过@FeignClient注解指定服务名来绑定服务, 然后使用Spring MVC的注解来绑定具体该服务提供的REST接口. 服务名不区分大小写

@FeignClient("HELLO-SERVICE")
public interface HelloService {

    @GetMapping("/hello")
    String hello();

}

接着在Controller中可以直接调用HelloService中的hello() 方法.

@RestController
public class ConsumerController {

    @Resource
    private HelloService helloService;

    @GetMapping(value = "/feign-consumer")
    public String helloConsumer() {
        return helloService.hello();
    }
}

最后, 在application.properties中指定服务注册中心, 并定义自身的服务名为feign-consumer, 以及端口

spring.application.name=feign-consumer
server.port=2112

eureka.client.service-url.defaultZone=http://localhost:1111/eureka/

启动注册中心, HELLO-SERVICE服务, 以及feign-consumer, 发送请求到http://localhost:2112/feign-consumer 可以实现和Ribbon一样的效果.

Feign实现的消费者,依然利用Ribbon维护了针对HELLO-SERVICE的服务列表信息, 并通过轮询实现了客户端的负载均衡, 而与Ribbon不同的是, 通过Feign我们只需定义服务绑定接口, 以声明式的方法,优雅而简单的实现服务调用.

参数绑定

  1. 在接口定义中加入请求参数, @RequestParam, @RequestHeader 的value不能为空, 在Spring MVC程序中, 注解会根据参数名作为默认值, 但是Feign 中绑定参数必须通过value属性来指明具体的参数名.
@GetMapping("/hello1")
String hello1(@RequestParam("name") String name);

@GetMapping("/hello2")
String hello2(@RequestHeader("name") String name, @RequestHeader("age") Integer age);

@PostMapping("/hello3")
String hello3(@RequestBody User user);
  1. 请求调用
@GetMapping(value = "/feign-consumer2")
public String helloConsumer2() {
    StringBuilder builder = new StringBuilder();
    builder.append(helloService.hello()).append("\n");
    builder.append(helloService.hello1("ANYBBO")).append("\n");
    builder.append(helloService.hello2("ANYBBO1", 18)).append("\n");
    builder.append(helloService.hello3(new User("ANYBBO2", 23))).append("\n");
    return builder.toString();
}

继承

在使用Spring MVC的注解绑定服务接口时, 基本上都使用复制操作, 特别的繁琐, Spring Cloud Feign提供了继承的特性解决复制操作.

  1. 抽象出 hello-service-api项目用来定义接口
@RequestMapping("/refactor")
public interface HelloService {

    @GetMapping("/hello4")
    String hello4(@RequestParam("name") String name);

    @GetMapping("/hello5")
    User hello5(@RequestHeader("name") String name, @RequestHeader("age") Integer age);

    @PostMapping("/hello6")
    String hello6(@RequestBody User user);

}
  1. 服务提供者引入hello-service-api依赖, 并实现接口. 在Controller中不再需要定义请求映射 @RequestMapping, 参数的注解定义在重写的时候会自动带过来
@Log4j2
@RestController
public class RefactorHelloController implements HelloService {

    @Override
    public String hello4(@RequestParam("name") String name)  {
        return "Hello " + name;
    }

    @Override
    public User hello5(@RequestHeader("name") String name, @RequestHeader("age") Integer age) {
        return new User(name, age);
    }

    @Override
    public String hello6(@RequestBody User user) {
        return "Hello " + user.getName() + ", " + user.getAge();
    }
}
  1. 服务消费者引入hello-service-api依赖, 继承接口并添加@FeignClient注解绑定服务.
@FeignClient("HELLO-SERVICE")
public interface RefactorHelloService extends HelloService {

}
  1. 服务调用
@Resource
private RefactorHelloService refactorHelloService;

@GetMapping(value = "/feign-consumer3")
public String helloConsumer3() {
    StringBuilder builder = new StringBuilder();
    builder.append(refactorHelloService.hello4("ANYBBO")).append("\n");
    builder.append(refactorHelloService.hello5("ANYBBO1", 18)).append("\n");
    builder.append(refactorHelloService.hello6(new User("ANYBBO2", 23))).append("\n");
    return builder.toString();
}

优点: - 将接口定义从 Controller中剥离, 配合Maven 私有仓库实现接口定义共享 - 实现构建期的接口绑定, 减少客户端的绑定配置

缺点: - 由于接口在构建期间就建立了依赖, 如果接口变动会对整个项目造成影响, 假如服务提供方修改接口定义, 会导致客户端工程构建失败.