黑马商城学习项目04-OpenFeign
OpenFeign:让远程调用如丝般顺滑
想象一下,在我们的微服务世界里,cart-service(购物车服务)和 item-service(商品服务)是两个独立的部门。购物车部门需要查询某个商品的价格和库存,就必须去 “拜访” 一下商品部门。
在上一章,我们学会了使用 RestTemplate 这个 “信使” 来完成这次拜访。但你可能已经发现了,这个过程有点繁琐和死板:
- 准备拜访函:你需要手动拼接 URL,比如
http://item-service/items?ids=...。如果服务地址变了,或者参数复杂,拼接起来就像在做填字游戏,很容易出错。 - 明确沟通方式:你需要明确告诉
RestTemplate是用getForObject还是postForObject,就像告诉信使是送信还是取包裹。 - 翻译结果:
RestTemplate带回来的信息(通常是 JSON 字符串),你还需要告诉它应该 “翻译” 成哪个 Java 对象,比如ItemDTO。
整个过程下来,代码显得很笨重,而且这种 “网络通信” 式的代码和我们平时写的 “本地方法调用”(比如 cartService.getCart())风格迥异,让代码的可读性和统一性大打折扣。
那么,有没有一种更优雅的方式呢?
当然有!这就是 OpenFeign 闪亮登场的时候了。
OpenFeign 的核心思想是:你只需要用 Java 接口的方式告诉我想做什么,剩下的事情都交给我。 它是一个声明式的 HTTP 客户端,我们通过简单的注解,就能将一个 Java 接口 “伪装” 成一个远程服务的代理。调用这个接口的方法,就如同调用本地方法一样简单,而 OpenFeign 则在幕后默默地为我们完成了所有复杂的网络通信工作。
让我们来看看 OpenFeign 是如何施展魔法的。一次远程调用的核心要素无非是四个:
- 请求方式:GET、POST、PUT 还是 DELETE?
- 请求路径:要访问哪个 URL?
- 请求参数:需要携带哪些数据?
- 返回值类型:期望返回什么样的数据?
OpenFeign 巧妙地利用了我们早已熟悉的 Spring MVC 注解,来声明这四大要素。
1. OpenFeign 快速入门
我们就以购物车服务(cart-service)查询商品信息为例,看看 OpenFeign 是如何让代码焕然一新的。
1.1. 引入 “装备” (添加依赖)
首先,我们需要给我们的 cart-service 配备 OpenFeign 这套 “高级装备”。在 pom.xml 文件中加入两个依赖:
1 | <dependency> |
spring-cloud-starter-openfeign:这是 OpenFeign 的核心启动器。spring-cloud-starter-loadbalancer:这个很重要!OpenFeign 知道我们要调用"item-service",但它不知道这个服务具体在哪台机器上(IP 和端口)。负载均衡器会去 Nacos 等服务注册中心询问,并从多个可用的item-service实例中智能地选择一个进行调用。
1.2. 开启 “魔法开关” (启用 OpenFeign)
光有装备还不行,我们得在项目里按下一个 “启动开关”。在 cart-service 的主启动类 CartApplication 上,添加 @EnableFeignClients 注解。
1 |
|
这个注解告诉 Spring:“请扫描我的项目,找到所有被 @FeignClient 标记的接口,并为它们创建代理实现类。”
1.3. 编写 “契约接口” (Feign 客户端)
这是最核心的一步!我们不再编写 RestTemplate 的调用代码,而是定义一个接口,这个接口就是我们和 item-service 之间的 “通信契约”。
在 cart-service 中创建一个 ItemClient 接口:
1 | package com.hmall.cart.client; |
让我们来解读一下这个 “契约”:
@FeignClient("item-service"):这是最重要的注解!它告诉 OpenFeign,这个接口下的所有方法都是用来调用名为"item-service"的微服务的。这个名字必须和它在 Nacos 上注册的名字完全一致。@GetMapping("/items"):这和item-service中 Controller 层的注解一模一样。它清晰地指明了我们要发起一个 GET 请求,访问的目标路径是/items。List<ItemDTO> queryItemByIds(...):方法签名部分定义了请求参数和返回值。@RequestParam("ids") Collection<Long> ids:表示我们需要传递一个名为ids的请求参数。当我们调用这个方法时,OpenFeign 会自动将ids集合拼接到 URL 后面,形成?ids=1,2,3...这样的形式。List<ItemDTO>:这指定了我们期望的返回类型。OpenFeign 在收到item-service返回的 JSON 响应后,会自动帮我们反序列化成一个List<ItemDTO>对象。
看到了吗?我们只写了一个接口,没有写任何实现代码。但通过这些注解,OpenFeign 就掌握了所有必要的信息,它会在幕后利用动态代理技术,为我们生成一个实现了这个接口的类。
1.4. 像调用本地方法一样使用它
现在,我们可以在 CartServiceImpl 中彻底告别 RestTemplate,像注入一个普通的 Service 一样注入并使用 ItemClient。
1 |
|
对比一下之前的 RestTemplate 代码,是不是感觉清爽了无数倍?服务发现、负载均衡、HTTP 请求构建、JSON 解析…… 所有脏活累活,OpenFeign 都替我们干了。
2. 性能进阶:启用连接池
OpenFeign 底层依赖于某个 HTTP 客户端来真正地发送网络请求。默认情况下,它使用的是 Java 自带的 HttpURLConnection,这个客户端有一个缺点:不支持连接池。
什么是连接池呢?
打个比方,每次远程调用就像开车去另一个城市。
- 没有连接池:每次出门都需要重新修一条路(建立 TCP 连接),到达目的地后就把路拆掉。修路和拆路的过程非常耗时。
- 有连接池:我们预先修好几条高速公路(保持一些 TCP 连接活跃),每次出门直接上高速,用完后也不拆,留给下一次使用。这样就大大节省了 “修路” 的时间。
因此,为了提升性能,我们通常会换用更专业的、支持连接池的 HTTP 客户端,比如 OkHttp 或 Apache HttpClient。
2.1. 引入 OkHttp 依赖
在 cart-service 的 pom.xml 中,加入 OkHttp 的依赖:
1 | </dependency> |
2.2. 开启连接池配置
然后在 application.yml 文件中,添加配置来启用它:
1 | feign: |
只需这两步,重启服务后,OpenFeign 底层的通信工具就会自动从 HttpURLConnection 切换到 OkHttp,并享受连接池带来的性能提升。
3. 最佳实践:抽取 Feign 客户端
随着项目越来越大,我们会发现一个问题。cart-service 需要调用 item-service,将来新增的 trade-service(交易服务)在下单时也需要调用 item-service 来查询商品信息。
难道我们要在 trade-service 里再把 ItemClient 接口和 ItemDTO 类原封不动地复制一遍吗?这显然不符合 “不要重复你自己”(DRY)的原则。
解决方案就是:抽取!
我们将这些所有服务都可能用到的 Feign 客户端接口和相关的 DTO 对象,抽取到一个公共的 hm-api 模块中。
4.3.1. 创建公共 API 模块
我们创建一个新的 Maven 模块,比如叫 hm-api。然后将 ItemClient.java 和 ItemDTO.java 从 cart-service 移动到这个新模块中。
这个 hm-api 模块会非常轻量,它只包含接口和 DTO,以及 OpenFeign 的必要依赖。
3.2. 消费者服务引入 API 模块
现在,任何需要调用 item-service 的微服务(比如 cart-service 和 trade-service),只需要在自己的 pom.xml 中引入 hm-api 模块的依赖即可。
1 | <dependency> |
3.3. 解决包扫描问题
引入依赖后,删掉 cart-service 中原有的 ItemClient 和 ItemDTO,然后重启,你可能会遇到一个错误:ItemClient 这个 Bean 找不到了!
为什么呢?
因为 Spring Boot 默认只会扫描主启动类所在包及其子包下的组件。我们的 CartApplication 在 com.hmall.cart 包下,而现在 ItemClient 被移动到了 com.hmall.api.client 包下,超出了扫描范围。
解决方法很简单,二选一:
- 指定扫描包:在
@EnableFeignClients注解中明确告诉它要去哪里寻找 Feign 客户端。
1 |
- 指定客户端类:更精确地指定要加载哪个 Feign 客户端。
1 |
这样一来,代码就实现了复用,维护起来也更加方便。
4. 调试利器:配置日志
有时候远程调用失败了,或者返回的结果不符合预期,我们很想知道 OpenFeign 到底发送了一个什么样的 HTTP 请求,又收到了什么样的响应。这时,开启日志就非常有用了。
OpenFeign 的日志有四个级别:
NONE:不记录任何日志(默认值)。BASIC:仅记录请求方法、URL、响应状态码和执行时间。HEADERS:在BASIC基础上,额外记录请求和响应的头信息。FULL:记录最详细的信息,包括头信息、请求体、元数据等,是调试时的首选。
4.1. 定义日志级别配置类
我们可以在公共的 hm-api 模块中创建一个配置类,来定义我们想要的日志级别。
1 | package com.hmall.api.config; |
4.2. 应用日志配置
配置好了级别,还需要告诉 OpenFeign 去使用它。同样有两种方式:
- 局部生效:只对某一个 Feign 客户端生效。
1 |
|
- 全局生效:对项目中所有的 Feign 客户端都生效。
1 |
|
最后,别忘了:你还需要在 application.yml 中,将 Feign 客户端接口所在包的日志级别设置为 DEBUG,否则日志信息无法输出。
1 | logging: |
完成配置后,当你再次发起远程调用,就会在控制台看到详细的请求和响应日志,这对于排查问题非常有帮助!
希望这份详细的讲解能帮助你更好地理解和使用 OpenFeign!
-
感谢你赐予我前进的力量