SpringCloud之声明式服务调用Feign
在前面几篇文章中,主要和大家介绍了服务的注册与消费。在介绍过程中,我们从最最原始的手动利用 DiscoveryClient 发现服务开始,手动实现负载均衡,再到最后的自动化配置。相信经过前面几篇文章的学习,大家对微服务之间的调用应该有了一个基本的认知。但是我们前面的所有服务调用都是手动写 RestTemplate 来实现的,大家可能已经发现这样写有点麻烦,每次都要写请求 Url 、配置响应数据类型
在前面几篇文章中,主要和大家介绍了服务的注册与消费。在介绍过程中,我们从最最原始的手动利用 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
的接口:
@RestController
public class HelloController {
@GetMapping("/hello")
public String hello(String name) {
return "hello " + name + " !";
}
}
然后分别启动服务注册中心 eureka 以及服务提供者 provider ,然后在浏览器中输入http://localhost:1111 可以看到我们的实例情况
如何使用Feign
准备工作完成后,我们创建一个feign-consumer的SpringBoot工程,项目创建好后依赖如下 :
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<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>
<dependency>
<groupId>cn.com.scitc</groupId>
<artifactId>commons</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
创建好了后,我们在application.yml中将我们的feign -consumer 注册到服务中心 (eureka)中
spring:
application:
name: feigin-consumer
eureka:
client:
service-url:
defaultZone: http://localhost:1111/eureka
server:
port: 5002
最后在我们的feign-consumer中的主启动类中添加@EnableFeignClients
注解,开启Feign的支持
@SpringBootApplication
@EnableFeignClients
public class FeiginConsumerApplication {
public static void main(String[] args) {
SpringApplication.run(FeiginConsumerApplication.class, args);
}
}
下面我们创建一个HelloService接口,用来消费provider提供的接口
@FeignClient("provider")
public interface HelloService {
@GetMapping("/hello")
String hello(@RequestParam("name") String name);
}
这个接口做了两件事:
- 使用 @FeignClient(“provider”) 注解将当前接口和 provider 服务绑定, provider 是服务名,可以忽略大小写;
- 然后使用 SpringMVC 的 @GetMapping("/hello") 注解将 hello 方法和 provider 中的 hello 接口绑定在一起。需要注意的是,在 SpringMVC 中,在需要给参数设置默认值或者要求参数必填的情况下才需要用到 @RequestParam 注解,而在这里,这个注解一定要加。
经过这样的步骤之后,我们就可以在一个 Controller 中注入 HelloService 接口并使用它了,而 HelloService 接口也会去调用相关的服务。我的 Controller 如下:
@RestController
public class HelloController {
@Autowired
HelloService helloService;
@GetMapping("/hello")
public String hello(String name) {
return helloService.hello(name);
}
}
配置好了后,我们在浏览器上访问http://localhost:5002/hello?name=技术无止境,显示的效果如下:
可以看到我们这样写代码 比之前用restTemplate 清爽了许多。
参数传递和JSON
上面和大家展示了 Feign 的基本使用。接下来和大家说说 Feign 中的参数传递问题,相信对于大多数人而言,在开发中,参数传递无非就是 key/value 形式的参数、放在 body 中的参数、放在 Url 路径上的参数以及放在请求头上的参数,这四种是较为常见的四种传参方式,因此,这里就重点和大家分享下这四种不同的传参方式在 Feign 中要如何使用。
我们再创建一个叫commons普通的maven工程 作为Feign的子项目,然后在commons项目中添加一个UserDTO 如下:
public class UserDTO {
private Integer id;
private String nickname;
private String address;
}
这里省略get/set方法,然后我们在provider和feign-consumer中加入commons这个依赖,就可以使用了。
然后我们在provider 中添加UserController
@RestController
public class UserController {
private Logger logger = LoggerFactory.getLogger(this.getClass());
@PostMapping("/user")
public ResponseEntity<UserDTO> addUser(@RequestBody UserDTO userDTO) {
logger.info("provider提供了addUser服务");
return new ResponseEntity<UserDTO>(userDTO, HttpStatus.OK);
}
@PutMapping("/user")
public ResponseEntity<Object> updateUser(@RequestBody UserDTO userDTO) {
HashMap<String,Object> map = new HashMap<>();
map.put("name",userDTO.getNickname());
map.put("id", userDTO.getId());
return new ResponseEntity<Object>(map,HttpStatus.OK);
}
@GetMapping("/user")
public ResponseEntity<UserDTO> getUserDTOByName(@RequestParam String name) {
UserDTO userDTO = new UserDTO();
userDTO.setNickname(name);
logger.info("provider提供了getUserDTOByName服务");
return new ResponseEntity<UserDTO>(userDTO, HttpStatus.OK);
}
@DeleteMapping("/user/{id}")
public ResponseEntity<Integer> deleteUserDTOById(@PathVariable Integer id) {
logger.info("provider提供了deleteUserDTOById服务");
return new ResponseEntity<Integer>(id, HttpStatus.OK);
}
}
上面的几个方法我做几个解释
@PostMapping("/user") 是一个创建的方法
@PutMapping("/user") 是一个更新方法
@GetMapping("/user") 是一个查询的方法
@DeleteMapping("/user/{id}") 根据id来删除的方法
在provider中添加完成后,我们在feign-consumer中添加代码如下:
@FeignClient("provider")
public interface HelloService {
@GetMapping("/hello")
String hello(@RequestParam("name") String name);
@PostMapping("/user")
String addUser(@RequestBody UserDTO userDTO);
@PutMapping("/user")
String updateUser(@RequestBody UserDTO userDTO);
@DeleteMapping("/user/{id}")
String deleteUserDTOById(@PathVariable Integer id);
@GetMapping("/user")
String getUserDTOByName(@RequestParam String name);
}
这里的调用,基本也和前面一样,无需赘述,但是有一个地方需要强调,那就是不同于 provider ,这里的参数如果是 key/value 形式的,一定要在 @RequestParam 注解中指明 name 属性,如果是在 header 中传递的,则一定要在 @RequestHeader 注解中添加 name 属性,如果参数放在 Url 路径中,那么一定需要在 @PathVariable 注解中添加 name 属性指明参数名称。
配置完成后 我们在feign-consumer 中的HelloController中添加 一个接口测试如下:
@PostMapping("/user")
public ResponseEntity<String> addUser(@RequestBody UserDTO userDTO ) {
String s = helloService.addUser(userDTO);
log.info("消费了provider的addUser");
return new ResponseEntity<String>(s, HttpStatus.OK);
}
我们用postman 或者 restlet 工具来测试 http://localhost:5002/user接口 返回结果如下图所示 :
然后看我们provider 日志输出:
再看我们的feign-consumer的日志输出
其它的方法就不演示了,详情看代码,我这里非常的建议大家自己去写一些服务然后去测试,这样的话就非常的好理解。
总结
本节主要通过两个案例介绍了声明式服务调用的一个基本用法。相信有一部分读者在读完本节后,会很容易想到 MyBatis,很多人在刚开始学习 MyBatis 的时候,并不是一上来就使用 Mapper ,而是先从简单的 SqlSessionFactory 开始,在使用的过程中,发现了大量的模板化代码,于是才有了 Mapper ,使用了动态代理启动生成调用逻辑,开发者只需要生命最最关键的部分。MyBatis 中的这一演变过程和我们这里的演变如出一辙, 直接使用 RestTemplate 也带来了大量的模板化代码,通过 Feign ,我们只需要定义一下方法中最最关键的部分,就能实现调用。那么有人要问了,用了 Feign ,我们在 RestTemplate 中使用的负载均衡还能继续用吗?答案当然是可以继续使用,关于这个问题以及 Feign 的一些高级用法,我将在下篇文章中和大伙儿分享。
源码地址
更多推荐
所有评论(0)