最近学习 Spring Cloud OpenFeign,发现最容易混的不是代码本身,而是这些问题:

  • hm-api、服务消费者、服务提供者分别要加什么依赖?
  • @FeignClient@EnableFeignClients 分别加在哪里?
  • 多个 Client 时启动类怎么写?
  • LoadBalancer 是写在 hm-api 还是消费者?
  • OkHttp 连接池怎么配置?
  • Feign 日志怎么开启?
  • Client 方法名和 Controller 方法名必须一样吗?
  • MyBatis-Plus 自带的 updateById 能不能直接写进 Client?

这里按微服务项目中的常见结构整理一下。

一、三个模块分别负责什么

假设项目结构是:

hm-api
  └── com.hmall.api.client
      ├── ItemClient
      ├── CartClient
      └── OrderClient

item-service
cart-service
trade-service
pay-service

可以这样理解:

hm-api:公共调用契约模块,放 FeignClient 接口和 DTO

服务消费者:真正发起远程调用的服务,比如 trade-service 调 cart-service

服务提供者:被调用的服务,比如 cart-service 提供购物车接口

OpenFeign 的核心规则是:

谁调用别人,谁运行时就必须有 OpenFeign、LoadBalancer;如果启用 OkHttp,谁调用别人,谁也要有 OkHttp。

二、hm-api 公共模块怎么配

hm-api 主要放 FeignClient 接口和 DTO。

一般可以在 hm-api 中放这些依赖:

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

<!-- 负载均衡 -->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-loadbalancer</artifactId>
</dependency>

<!-- OkHttp 连接池支持 -->
<dependency>
    <groupId>io.github.openfeign</groupId>
    <artifactId>feign-okhttp</artifactId>
</dependency>

这样消费者只要引入 hm-api,就可以通过 Maven 依赖传递拿到这些依赖。

但是要注意:

依赖可以放在 hm-api 中传递给消费者,但配置开关仍然要写在真正发起调用的消费者服务里。

三、FeignClient 怎么写

比如订单服务要调用购物车服务,删除购物车中的商品。

可以在 hm-api 中定义:

@FeignClient("cart-service")
public interface CartClient {

    @DeleteMapping("/carts")
    void removeByItemIds(@RequestParam("ids") Collection<Long> ids);
}

这里真正重要的是:

服务名:cart-service
请求方式:DELETE
请求路径:/carts
参数名:ids

@FeignClient("cart-service") 中的 cart-service 要和服务提供者的服务名一致:

spring:
  application:
    name: cart-service

四、Client 方法名和服务端方法名必须一样吗

不必须。

比如服务提供者中的方法叫:

deleteCartItemByIds

但是 CartClient 中可以写成:

removeByItemIds

这是可以的。

因为 OpenFeign 看的是 HTTP 信息,不是 Java 方法名。

也就是说,只要下面这些对得上就行:

请求方式
请求路径
参数名
服务名

方法名可以按照调用方自己的业务语义来写。

五、服务消费者怎么配

服务消费者是真正发起远程调用的服务,比如:

trade-service 调 item-service
trade-service 调 cart-service
pay-service 调 trade-service

消费者需要引入 hm-api

<dependency>
    <groupId>com.heima</groupId>
    <artifactId>hm-api</artifactId>
    <version>1.0.0</version>
</dependency>

还需要注册中心依赖,比如 Nacos:

<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>

如果 hm-api 没有传递 OkHttp,也可以在消费者中直接引入:

<dependency>
    <groupId>io.github.openfeign</groupId>
    <artifactId>feign-okhttp</artifactId>
</dependency>

消费者启动类上要加:

@EnableFeignClients(basePackages = "com.hmall.api.client")
@SpringBootApplication
public class TradeApplication {

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

@EnableFeignClients 是加在消费者启动类上的。

也就是:

哪个服务要调用别人,就在哪个服务的启动类上加 @EnableFeignClients

六、多个 Client 时怎么写

如果一个服务同时用了两个 Client:

private final ItemClient itemClient;
private final CartClient cartClient;

启动类不需要写两个 @EnableFeignClients

推荐写法是扫描整个 Client 包:

@EnableFeignClients(basePackages = "com.hmall.api.client")
@SpringBootApplication
public class TradeApplication {
}

只要 ItemClientCartClientOrderClient 都在:

com.hmall.api.client

这个包下,就都能被扫描到。

也可以指定具体 Client:

@EnableFeignClients(clients = {ItemClient.class, CartClient.class})
@SpringBootApplication
public class TradeApplication {
}

但是以后每增加一个 Client 都要改启动类,所以更推荐 basePackages

七、为什么有时必须写 basePackages

如果启动类在:

com.hmall.trade

而 FeignClient 在:

com.hmall.api.client

它们不是父子包关系,默认扫描可能扫不到。

所以最好明确写:

@EnableFeignClients(basePackages = "com.hmall.api.client")

这样最稳。

八、服务提供者怎么配

服务提供者主要负责:

注册到 Nacos
提供 HTTP 接口
实现自己的业务逻辑

提供者至少需要:

<!-- Web -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>

<!-- Nacos 服务注册发现 -->
<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>

提供者如果只是被别人调用,不调用别人,一般不需要:

OpenFeign
LoadBalancer
OkHttp

但是如果提供者自己也要调用别的服务,那它在那个调用关系中也变成了消费者,就也要加这些配置。

九、LoadBalancer 到底写在哪里

LoadBalancer 真正生效的位置是消费者运行时。

但是依赖可以有两种写法。

第一种:写在 hm-api 中,让消费者通过依赖传递拿到。

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

第二种:直接写在消费者中。

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

两种都可以。

关键不是依赖写在哪个 pom.xml,而是:

消费者服务启动时,classpath 中必须有 LoadBalancer。

服务提供者如果只是被调用,不需要单独配置 LoadBalancer。

十、OkHttp 连接池怎么配

OpenFeign 只是封装 HTTP 调用,底层真正发请求还要依赖 HTTP 客户端。

常见有三种:

HttpURLConnection:默认实现,不支持连接池
Apache HttpClient:支持连接池
OkHttp:支持连接池

如果使用 OkHttp,需要先引入依赖:

<dependency>
    <groupId>io.github.openfeign</groupId>
    <artifactId>feign-okhttp</artifactId>
</dependency>

然后在消费者服务中开启:

feign:
  okhttp:
    enabled: true

注意:这个配置写在消费者服务中。

因为 OkHttp 是消费者发起 HTTP 请求时用的。

如果使用的是较新的 Spring Cloud OpenFeign 版本,配置可能是:

spring:
  cloud:
    openfeign:
      okhttp:
        enabled: true

具体看项目版本。

我当前学习项目是 Spring Boot 2.7.x、Spring Cloud 2021.x,所以使用:

feign:
  okhttp:
    enabled: true

十一、要不要自定义 OkHttp 连接池参数

刚开始学习一般不需要。

默认配置够用,先能跑通调用关系更重要。

如果后期并发请求比较多,可以再自定义:

@Configuration
public class FeignOkHttpConfig {

    @Bean
    public okhttp3.OkHttpClient okHttpClient() {
        return new okhttp3.OkHttpClient.Builder()
                .connectionPool(new ConnectionPool(200, 5, TimeUnit.MINUTES))
                .connectTimeout(2, TimeUnit.SECONDS)
                .readTimeout(5, TimeUnit.SECONDS)
                .writeTimeout(5, TimeUnit.SECONDS)
                .build();
    }
}

这段配置的意思是:

最多保留 200 个空闲连接
空闲连接最多保留 5 分钟
连接超时时间 2 秒
读取超时时间 5 秒
写入超时时间 5 秒

学习阶段可以先不配。

十二、MP 的 updateById 能不能直接写进 Client

不能。

比如在某个服务内部可以这样写:

orderService.updateById(order);

这是 MyBatis-Plus 的 Service 方法,是 Java 内部方法,不是 HTTP 接口。

OpenFeign 调用的是 HTTP 接口,不会直接调用对方 Service 层的 updateById

如果别的服务想修改订单状态,正确流程是:

pay-service
  -> OrderClient
  -> trade-service 暴露的 HTTP 接口
  -> orderService.updateById(order)
  -> 数据库

所以 Client 里不能凭空写:

void updateById(Order order);

除非服务提供者真的暴露了对应的 HTTP 接口。

更推荐写成业务语义明确的方法:

@FeignClient("trade-service")
public interface OrderClient {

    @PutMapping("/orders/{id}/status")
    void updateOrderStatus(
            @PathVariable("id") Long id,
            @RequestParam("status") Integer status
    );
}

记住:

Client 里写的是远程 HTTP 接口,不是对方 Service 的 Java 方法。

十三、Feign 日志怎么开启

OpenFeign 调接口时,如果想看到请求地址、请求参数、响应结果,可以开启 Feign 日志。

Feign 日志一般要配两部分:

1. 配置 Feign 的日志级别
2. 配置 Spring Boot 的日志输出级别

第一步,在 hm-api 中写一个公共配置类:

package com.hmall.api.config;

import feign.Logger;
import org.springframework.context.annotation.Bean;

public class DefaultFeignConfig {

    @Bean
    public Logger.Level feignLoggerLevel() {
        return Logger.Level.FULL;
    }
}

Logger.Level 常见有 4 种:

NONE:不记录日志,默认值
BASIC:只记录请求方法、URL、响应状态码、执行时间
HEADERS:在 BASIC 基础上,额外记录请求头和响应头
FULL:记录请求和响应的 header、body、metadata,最详细

学习和排查问题时可以用:

Logger.Level.FULL

第二步,在消费者启动类上引用这个配置:

@EnableFeignClients(
        basePackages = "com.hmall.api.client",
        defaultConfiguration = DefaultFeignConfig.class
)
@SpringBootApplication
public class TradeApplication {
}

如果只想给某一个 Client 单独配置,也可以写在 @FeignClient 上:

@FeignClient(value = "cart-service", configuration = DefaultFeignConfig.class)
public interface CartClient {
}

第三步,在消费者服务的 application.yml 中配置日志级别:

logging:
  level:
    com.hmall.api.client: debug

如果你直接写:

logging:
  level:
    com.hmall: debug

也可以,因为 com.hmall.api.client 属于 com.hmall 下面。

注意:

只写 Logger.Level.FULL,不一定能看到日志;
还要把对应包的 logging.level 设置成 debug。

开发阶段可以开 FULL,方便排查问题。生产环境一般不要长期使用 FULL,因为日志量比较大,还可能打印请求体、响应体等敏感信息。

十四、完整总结表

模块 依赖 注解或配置
hm-api spring-cloud-starter-openfeignspring-cloud-starter-loadbalancerfeign-okhttp Feign 接口上加 @FeignClient
消费者 引入 hm-api、Nacos;运行时要有 LoadBalancer 和 OkHttp 启动类加 @EnableFeignClients(basePackages = "com.hmall.api.client"),配置 feign.okhttp.enabled=true,需要日志时配置 defaultConfigurationlogging.level
提供者 Web、Nacos 配置 spring.application.name,提供 HTTP 接口

十五、最后总结

这次学习可以按下面几句话记:

hm-api:
  定义远程调用契约,放 FeignClient、DTO、Feign 公共配置。

消费者:
  真正发起远程调用。
  引入 hm-api 和 Nacos。
  启动类加 @EnableFeignClients。
  运行时要有 OpenFeign、LoadBalancer。
  如果使用 OkHttp,要有 feign-okhttp,并配置 feign.okhttp.enabled=true。

提供者:
  注册到 Nacos。
  暴露 HTTP 接口。
  如果只是被调用,不调用别人,一般不需要 OpenFeign、LoadBalancer、OkHttp。

LoadBalancer:
  根据服务名选择服务实例,生效在消费者运行时。

OkHttp:
  作为 Feign 底层 HTTP 客户端,支持连接池,也生效在消费者运行时。

Feign 日志:
  Logger.Level 决定日志详细程度。
  logging.level 决定日志是否真正输出。

Client 方法名:
  不要求和服务端 Controller 方法名一致。
  真正要一致的是服务名、请求方式、请求路径、参数名。

MyBatis-Plus 方法:
  updateById 这类方法是服务内部方法,不能直接写进 Client。
  想远程调用,必须先由服务提供者暴露 HTTP 接口。

最核心的一句话:

hm-api 定义调用契约,消费者开启 Feign 并发起调用,提供者暴露接口;LoadBalancer 和 OkHttp 都是在消费者发起远程调用时生效。

Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐