SpringCloud之声明式服务调用Feign

测试账号 2021-01-18 01:27 861阅读 0赞

在前面几篇文章中,主要和大家介绍了服务的注册与消费。在介绍过程中,我们从最最原始的手动利用 DiscoveryClient 发现服务开始,手动实现负载均衡,再到最后的自动化配置。相信经过前面几篇文章的学习,大家对微服务之间的调用应该有了一个基本的认知。但是我们前面的所有服务调用都是手动写 RestTemplate 来实现的,大家可能已经发现这样写有点麻烦,每次都要写请求 Url 、配置响应数据类型,最后还要组装参数,更重要的是这些都是一些重复的工作,代码高度相似,每个请求只有 Url 不同,请求方法不同、参数不同,其它东西基本都是一样的,既然如此,那有没有办法简化请求呢?有!这就是本文我们要聊的声明式微服务调用 Feign。不对,严格来说,应该叫 OpenFeign,为什么这么说呢?早期我们用的是叫 Netflix Feign,不过这个东西的最近一次更新还停留在 2016年7月,OpenFeign 则是 Spring Cloud 团队在 Netflix Feign 基础上开发出来的声明式服务调用组件,OpenFeign也一直在维护,具体的迁移工作,大家参考

准备工作

和前面几篇文章的步骤一样,首先我们还是要先搭建一个父工程,然后创建一个服务注册中心。OK,那就开始吧! 首先创建一个名为 Feign 的 Maven 父工程,然后在父工程中创建一个 eureka 工程充当我们的服务注册中心,具体步骤我这里就不再赘述了,大家如果忘了如何搭建服务注册中心,可以参考前面的文章。

服务注册中心搭建成功后,接下来我们还要再搭建一个 provider 用来提供服务。这个 provider 和前面 provider 的搭建也是基本一致的。 provider 搭建成功后,依然提供一个 HelloController 接口,里边配上一个 /hello的接口:

  1. @RestController
  2. public class HelloController {
  3. @GetMapping("/hello")
  4. public String hello(String name) {
  5. return "hello " + name + " !";
  6. }
  7. }

然后分别启动服务注册中心 eureka 以及服务提供者 provider ,然后在浏览器中输入http://localhost:1111 可以看到我们的实例情况
在这里插入图片描述

如何使用Feign

准备工作完成后,我们创建一个feign-consumer的SpringBoot工程,项目创建好后依赖如下 :

  1. <dependencies>
  2. <dependency>
  3. <groupId>org.springframework.boot</groupId>
  4. <artifactId>spring-boot-starter-web</artifactId>
  5. </dependency>
  6. <dependency>
  7. <groupId>org.springframework.cloud</groupId>
  8. <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
  9. </dependency>
  10. <dependency>
  11. <groupId>org.springframework.cloud</groupId>
  12. <artifactId>spring-cloud-starter-openfeign</artifactId>
  13. </dependency>
  14. <dependency>
  15. <groupId>cn.com.scitc</groupId>
  16. <artifactId>commons</artifactId>
  17. <version>1.0-SNAPSHOT</version>
  18. </dependency>
  19. <dependency>
  20. <groupId>org.springframework.boot</groupId>
  21. <artifactId>spring-boot-devtools</artifactId>
  22. <scope>runtime</scope>
  23. <optional>true</optional>
  24. </dependency>
  25. <dependency>
  26. <groupId>org.springframework.boot</groupId>
  27. <artifactId>spring-boot-starter-test</artifactId>
  28. <scope>test</scope>
  29. </dependency>
  30. </dependencies>
  31. <dependencyManagement>
  32. <dependencies>
  33. <dependency>
  34. <groupId>org.springframework.cloud</groupId>
  35. <artifactId>spring-cloud-dependencies</artifactId>
  36. <version>${spring-cloud.version}</version>
  37. <type>pom</type>
  38. <scope>import</scope>
  39. </dependency>
  40. </dependencies>
  41. </dependencyManagement>

创建好了后,我们在application.yml中将我们的feign -consumer 注册到服务中心 (eureka)中

  1. spring:
  2. application:
  3. name: feigin-consumer
  4. eureka:
  5. client:
  6. service-url:
  7. defaultZone: http://localhost:1111/eureka
  8. server:
  9. port: 5002

最后在我们的feign-consumer中的主启动类中添加@EnableFeignClients
注解,开启Feign的支持

  1. @SpringBootApplication
  2. @EnableFeignClients
  3. public class FeiginConsumerApplication {
  4. public static void main(String[] args) {
  5. SpringApplication.run(FeiginConsumerApplication.class, args);
  6. }
  7. }

下面我们创建一个HelloService接口,用来消费provider提供的接口

  1. @FeignClient("provider")
  2. public interface HelloService {
  3. @GetMapping("/hello")
  4. String hello(@RequestParam("name") String name);
  5. }

这个接口做了两件事:

  1. 使用 @FeignClient(“provider”) 注解将当前接口和 provider 服务绑定, provider 是服务名,可以忽略大小写;
  2. 然后使用 SpringMVC 的 @GetMapping(“/hello”) 注解将 hello 方法和 provider 中的 hello 接口绑定在一起。需要注意的是,在 SpringMVC 中,在需要给参数设置默认值或者要求参数必填的情况下才需要用到 @RequestParam 注解,而在这里,这个注解一定要加。

经过这样的步骤之后,我们就可以在一个 Controller 中注入 HelloService 接口并使用它了,而 HelloService 接口也会去调用相关的服务。我的 Controller 如下:

  1. @RestController
  2. public class HelloController {
  3. @Autowired
  4. HelloService helloService;
  5. @GetMapping("/hello")
  6. public String hello(String name) {
  7. return helloService.hello(name);
  8. }
  9. }

配置好了后,我们在浏览器上访问http://localhost:5002/hello?name=技术无止境,显示的效果如下:
在这里插入图片描述
可以看到我们这样写代码 比之前用restTemplate 清爽了许多。

参数传递和JSON

上面和大家展示了 Feign 的基本使用。接下来和大家说说 Feign 中的参数传递问题,相信对于大多数人而言,在开发中,参数传递无非就是 key/value 形式的参数、放在 body 中的参数、放在 Url 路径上的参数以及放在请求头上的参数,这四种是较为常见的四种传参方式,因此,这里就重点和大家分享下这四种不同的传参方式在 Feign 中要如何使用。

我们再创建一个叫commons普通的maven工程 作为Feign的子项目,然后在commons项目中添加一个UserDTO 如下:

  1. public class UserDTO {
  2. private Integer id;
  3. private String nickname;
  4. private String address;
  5. }

这里省略get/set方法,然后我们在provider和feign-consumer中加入commons这个依赖,就可以使用了。

然后我们在provider 中添加UserController

  1. @RestController
  2. public class UserController {
  3. private Logger logger = LoggerFactory.getLogger(this.getClass());
  4. @PostMapping("/user")
  5. public ResponseEntity<UserDTO> addUser(@RequestBody UserDTO userDTO) {
  6. logger.info("provider提供了addUser服务");
  7. return new ResponseEntity<UserDTO>(userDTO, HttpStatus.OK);
  8. }
  9. @PutMapping("/user")
  10. public ResponseEntity<Object> updateUser(@RequestBody UserDTO userDTO) {
  11. HashMap<String,Object> map = new HashMap<>();
  12. map.put("name",userDTO.getNickname());
  13. map.put("id", userDTO.getId());
  14. return new ResponseEntity<Object>(map,HttpStatus.OK);
  15. }
  16. @GetMapping("/user")
  17. public ResponseEntity<UserDTO> getUserDTOByName(@RequestParam String name) {
  18. UserDTO userDTO = new UserDTO();
  19. userDTO.setNickname(name);
  20. logger.info("provider提供了getUserDTOByName服务");
  21. return new ResponseEntity<UserDTO>(userDTO, HttpStatus.OK);
  22. }
  23. @DeleteMapping("/user/{id}")
  24. public ResponseEntity<Integer> deleteUserDTOById(@PathVariable Integer id) {
  25. logger.info("provider提供了deleteUserDTOById服务");
  26. return new ResponseEntity<Integer>(id, HttpStatus.OK);
  27. }
  28. }

上面的几个方法我做几个解释
@PostMapping(“/user”) 是一个创建的方法
@PutMapping(“/user”) 是一个更新方法
@GetMapping(“/user”) 是一个查询的方法
@DeleteMapping(“/user/{id}“) 根据id来删除的方法

在provider中添加完成后,我们在feign-consumer中添加代码如下:

  1. @FeignClient("provider")
  2. public interface HelloService {
  3. @GetMapping("/hello")
  4. String hello(@RequestParam("name") String name);
  5. @PostMapping("/user")
  6. String addUser(@RequestBody UserDTO userDTO);
  7. @PutMapping("/user")
  8. String updateUser(@RequestBody UserDTO userDTO);
  9. @DeleteMapping("/user/{id}")
  10. String deleteUserDTOById(@PathVariable Integer id);
  11. @GetMapping("/user")
  12. String getUserDTOByName(@RequestParam String name);
  13. }

这里的调用,基本也和前面一样,无需赘述,但是有一个地方需要强调,那就是不同于 provider ,这里的参数如果是 key/value 形式的,一定要在 @RequestParam 注解中指明 name 属性,如果是在 header 中传递的,则一定要在 @RequestHeader 注解中添加 name 属性,如果参数放在 Url 路径中,那么一定需要在 @PathVariable 注解中添加 name 属性指明参数名称。

配置完成后 我们在feign-consumer 中的HelloController中添加 一个接口测试如下:

  1. @PostMapping("/user")
  2. public ResponseEntity<String> addUser(@RequestBody UserDTO userDTO ) {
  3. String s = helloService.addUser(userDTO);
  4. log.info("消费了provider的addUser");
  5. return new ResponseEntity<String>(s, HttpStatus.OK);
  6. }

我们用postman 或者 restlet 工具来测试 http://localhost:5002/user接口 返回结果如下图所示 :
在这里插入图片描述
然后看我们provider 日志输出:

在这里插入图片描述
再看我们的feign-consumer的日志输出
在这里插入图片描述
其它的方法就不演示了,详情看代码,我这里非常的建议大家自己去写一些服务然后去测试,这样的话就非常的好理解。

总结

本节主要通过两个案例介绍了声明式服务调用的一个基本用法。相信有一部分读者在读完本节后,会很容易想到 MyBatis,很多人在刚开始学习 MyBatis 的时候,并不是一上来就使用 Mapper ,而是先从简单的 SqlSessionFactory 开始,在使用的过程中,发现了大量的模板化代码,于是才有了 Mapper ,使用了动态代理启动生成调用逻辑,开发者只需要生命最最关键的部分。MyBatis 中的这一演变过程和我们这里的演变如出一辙, 直接使用 RestTemplate 也带来了大量的模板化代码,通过 Feign ,我们只需要定义一下方法中最最关键的部分,就能实现调用。那么有人要问了,用了 Feign ,我们在 RestTemplate 中使用的负载均衡还能继续用吗?答案当然是可以继续使用,关于这个问题以及 Feign 的一些高级用法,我将在下篇文章中和大伙儿分享。

源码地址

github

发表评论

表情:
评论列表 (有 0 条评论,861人围观)

还没有评论,来说两句吧...

相关阅读