跳到主要内容

高级特性

本章将介绍 OpenFeign 的高级特性,包括接口继承、手动构建客户端、OAuth2 支持、缓存、HATEOAS 支持等内容。

接口继承

OpenFeign 支持接口继承,可以将公共操作抽取到基础接口中,减少重复代码。

基本用法

// 基础接口:定义通用 CRUD 操作
public interface BaseService<T, ID> {

@GetMapping("/{id}")
T getById(@PathVariable("id") ID id);

@GetMapping
List<T> getAll();

@PostMapping
T create(@RequestBody T entity);

@PutMapping("/{id}")
T update(@PathVariable("id") ID id, @RequestBody T entity);

@DeleteMapping("/{id}")
void delete(@PathVariable("id") ID id);
}

// 用户客户端:继承基础接口
@FeignClient(name = "user-service")
public interface UserClient extends BaseService<User, Long> {

// 用户特有的方法
@GetMapping("/search")
User findByUsername(@RequestParam("username") String username);
}

// 订单客户端:继承基础接口
@FeignClient(name = "order-service")
public interface OrderClient extends BaseService<Order, Long> {

// 订单特有的方法
@GetMapping("/user/{userId}")
List<Order> getOrdersByUserId(@PathVariable("userId") Long userId);
}

服务端与客户端共享接口

在服务端,控制器可以实现相同的接口:

// 共享接口模块
public interface UserApi {
@GetMapping("/users/{id}")
User getById(@PathVariable("id") Long id);

@GetMapping("/users")
List<User> getAll();

@PostMapping("/users")
User create(@RequestBody User user);
}

// 服务端实现
@RestController
public class UserController implements UserApi {

private final UserService userService;

public UserController(UserService userService) {
this.userService = userService;
}

@Override
public User getById(Long id) {
return userService.getById(id);
}

@Override
public List<User> getAll() {
return userService.getAll();
}

@Override
public User create(User user) {
return userService.create(user);
}
}

// 客户端接口
@FeignClient(name = "user-service")
public interface UserClient extends UserApi {
// 无需重复定义方法
}
注意

不要在 @FeignClient 接口上使用类级别的 @RequestMapping 注解,这会导致问题。应该在方法级别指定路径。

继承的限制

OpenFeign 只支持单层继承:

// 支持:单层继承
public interface UserClient extends BaseService<User, Long> { }

// 不支持:多层继承
public interface AdvancedUserClient extends UserClient { } // 不推荐

手动构建 Feign 客户端

某些场景下,需要更灵活地构建 Feign 客户端,可以使用 Feign Builder API。

基本用法

@Service
public class DynamicClientService {

private final Client client;
private final Encoder encoder;
private final Decoder decoder;
private final Contract contract;

public DynamicClientService(Client client, Encoder encoder,
Decoder decoder, Contract contract) {
this.client = client;
this.encoder = encoder;
this.decoder = decoder;
this.contract = contract;
}

public UserClient buildUserClient(String baseUrl, String token) {
return Feign.builder()
.client(client)
.encoder(encoder)
.decoder(decoder)
.contract(contract)
.requestInterceptor(template -> {
template.header("Authorization", "Bearer " + token);
})
.target(UserClient.class, baseUrl);
}
}

多环境客户端

@Configuration
public class FeignConfig {

@Bean
public UserClient devUserClient(Client client, Encoder encoder,
Decoder decoder, Contract contract) {
return Feign.builder()
.client(client)
.encoder(encoder)
.decoder(decoder)
.contract(contract)
.logLevel(Logger.Level.FULL)
.target(UserClient.class, "http://dev-api.example.com");
}

@Bean
public UserClient prodUserClient(Client client, Encoder encoder,
Decoder decoder, Contract contract) {
return Feign.builder()
.client(client)
.encoder(encoder)
.decoder(decoder)
.contract(contract)
.logLevel(Logger.Level.BASIC)
.target(UserClient.class, "http://prod-api.example.com");
}
}

动态 URL 客户端

@Service
public class DynamicUrlService {

public <T> T createClient(Class<T> clientClass, String url) {
return Feign.builder()
.encoder(new SpringEncoder(new SpringFormEncoder()))
.decoder(new SpringDecoder(() -> new DefaultMessageConverters()))
.contract(new SpringMvcContract())
.target(clientClass, url);
}
}

// 使用示例
UserClient client = dynamicUrlService.createClient(UserClient.class, "http://custom-url.com");
User user = client.getUserById(1L);

Java 11 HTTP/2 客户端

从 Spring Cloud OpenFeign 4.x 开始,原生支持 Java 11 的 HTTP/2 客户端。这是一个零额外依赖的高性能选择。

启用 HTTP/2 客户端

spring:
cloud:
openfeign:
http2client:
enabled: true
httpclient:
http2:
version: HTTP_2 # 可选值:HTTP_1_1, HTTP_2

HTTP/2 的优势

多路复用

HTTP/2 允许在单个 TCP 连接上并发发送多个请求和响应,无需像 HTTP/1.1 那样排队等待:

HTTP/1.1(队头阻塞):
请求1 ──────────────────▶ 响应1
请求2 ──────────────────▶ 响应2
请求3 ──────────────────▶ 响应3

HTTP/2(多路复用):
请求1 ────▶
请求2 ────▶ 响应1 ◀────
请求3 ────▶ 响应2 ◀────
响应3 ◀────

头部压缩

HTTP/2 使用 HPACK 算法压缩请求和响应头,减少网络传输量:

HTTP/1.1 请求头:
Host: api.example.com
User-Agent: Mozilla/5.0 ...
Accept: application/json
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
Content-Type: application/json
...(通常 500-1000 字节)

HTTP/2 请求头(压缩后):
索引值 + 差异值(通常 50-200 字节)

服务器推送

HTTP/2 支持服务器主动推送资源,客户端可以缓存这些资源以加速后续请求。

配置示例

@Configuration
public class Http2Config {

// HTTP/2 客户端已通过 YAML 配置启用,无需额外 Java 配置

// 如果需要自定义 HTTP/2 客户端
@Bean
public Client feignClient() {
HttpClient client = HttpClient.newBuilder()
.version(HttpClient.Version.HTTP_2)
.connectTimeout(Duration.ofSeconds(10))
.followRedirects(HttpClient.Redirect.NORMAL)
.build();

return new Http2Client(client);
}
}

性能对比

特性HTTP/1.1HTTP/2
连接复用需要多个连接单连接多路复用
头部压缩HPACK 压缩
队头阻塞
服务器推送不支持支持
二进制协议

适用场景

推荐使用 HTTP/2 的场景

  • 服务端支持 HTTP/2
  • 高并发请求场景
  • 需要减少连接数
  • 移动端应用(减少电量消耗)

仍需使用 HTTP/1.1 的场景

  • 服务端不支持 HTTP/2
  • 需要与旧系统兼容
  • 网络中间件不支持 HTTP/2
最佳实践

如果不确定服务端是否支持 HTTP/2,可以配置客户端尝试 HTTP/2,如果失败则降级到 HTTP/1.1:

spring:
cloud:
openfeign:
http2client:
enabled: true
httpclient:
http2:
version: HTTP_2 # 客户端会自动协商

OAuth2 支持

OpenFeign 内置了 OAuth2 支持,可以自动在请求中添加访问令牌。这对于需要调用受保护 API 的微服务非常有用。

添加依赖

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-oauth2-client</artifactId>
</dependency>

基本配置

启用 OAuth2 支持并配置客户端注册:

spring:
cloud:
openfeign:
oauth2:
enabled: true
clientRegistrationId: my-client # OAuth2 客户端注册 ID

security:
oauth2:
client:
registration:
my-client:
client-id: your-client-id
client-secret: your-client-secret
authorization-grant-type: client_credentials
scope: read,write
provider:
my-client:
token-uri: https://auth.example.com/oauth/token

工作原理

当启用 OAuth2 支持后,OpenFeign 会自动:

  1. 在调用 Feign 客户端之前,使用配置的 OAuth2 客户端获取访问令牌
  2. 将获取到的令牌添加到请求的 Authorization 头中(格式:Bearer <token>
  3. 当令牌过期时,自动刷新令牌

负载均衡场景下的配置

对于使用服务发现的 Feign 客户端,可以省略 clientRegistrationId,系统会自动使用服务名作为客户端注册 ID:

spring:
cloud:
openfeign:
oauth2:
enabled: true
# 不指定 clientRegistrationId,使用服务名查找

security:
oauth2:
client:
registration:
# 服务名作为注册 ID
user-service:
client-id: user-service-client
client-secret: secret
authorization-grant-type: client_credentials
// Feign 客户端,服务名为 user-service
@FeignClient(name = "user-service")
public interface UserClient {
@GetMapping("/users/{id}")
User getUserById(@PathVariable("id") Long id);
}
// 系统会自动使用 user-service 这个注册 ID 获取令牌

自定义 OAuth2 拦截器

如果需要更细粒度的控制,可以自定义 OAuth2 拦截器:

@Configuration
public class OAuth2FeignConfig {

@Bean
public OAuth2AccessTokenInterceptor oAuth2AccessTokenInterceptor(
OAuth2AuthorizedClientManager authorizedClientManager) {

// 指定客户端注册 ID
return new OAuth2AccessTokenInterceptor("my-client", authorizedClientManager);
}
}

多客户端 OAuth2 配置

当需要为不同的 Feign 客户端使用不同的 OAuth2 配置时:

spring:
cloud:
openfeign:
client:
config:
user-service:
# 为特定客户端指定 OAuth2 配置
order-service:
# 另一个客户端的配置

security:
oauth2:
client:
registration:
user-client:
client-id: user-client-id
client-secret: user-secret
authorization-grant-type: client_credentials
order-client:
client-id: order-client-id
client-secret: order-secret
authorization-grant-type: client_credentials
// 用户服务客户端配置
public class UserServiceFeignConfig {

@Bean
public OAuth2AccessTokenInterceptor userOAuth2Interceptor(
OAuth2AuthorizedClientManager authorizedClientManager) {
return new OAuth2AccessTokenInterceptor("user-client", authorizedClientManager);
}
}

// 订单服务客户端配置
public class OrderServiceFeignConfig {

@Bean
public OAuth2AccessTokenInterceptor orderOAuth2Interceptor(
OAuth2AuthorizedClientManager authorizedClientManager) {
return new OAuth2AccessTokenInterceptor("order-client", authorizedClientManager);
}
}

// Feign 客户端定义
@FeignClient(name = "user-service", configuration = UserServiceFeignConfig.class)
public interface UserClient { }

@FeignClient(name = "order-service", configuration = OrderServiceFeignConfig.class)
public interface OrderClient { }

与 Spring Security 集成

如果应用同时作为 OAuth2 资源服务器和客户端:

spring:
security:
oauth2:
resourceserver:
jwt:
issuer-uri: https://auth.example.com
client:
registration:
my-client:
client-id: your-client-id
client-secret: your-client-secret
authorization-grant-type: client_credentials
@Configuration
@EnableWebSecurity
public class SecurityConfig {

@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests(auth -> auth
.requestMatchers("/actuator/**").permitAll()
.anyRequest().authenticated()
)
.oauth2ResourceServer(oauth2 -> oauth2.jwt(Customizer.withDefaults()));

return http.build();
}

// OAuth2 客户端管理器
@Bean
public OAuth2AuthorizedClientManager authorizedClientManager(
ClientRegistrationRepository clientRegistrationRepository,
OAuth2AuthorizedClientService authorizedClientService) {

OAuth2AuthorizedClientProvider authorizedClientProvider =
OAuth2AuthorizedClientProviderBuilder.builder()
.clientCredentials()
.build();

AuthorizedClientServiceOAuth2AuthorizedClientManager authorizedClientManager =
new AuthorizedClientServiceOAuth2AuthorizedClientManager(
clientRegistrationRepository, authorizedClientService);
authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider);

return authorizedClientManager;
}
}

Feign 缓存

OpenFeign 支持 Spring Cache 注解,可以缓存 Feign 调用结果。

启用缓存

@SpringBootApplication
@EnableFeignClients
@EnableCaching // 启用缓存
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}

使用缓存注解

@FeignClient(name = "user-service")
public interface UserClient {

@GetMapping("/users/{id}")
@Cacheable(cacheNames = "users", key = "#id") // 缓存结果
User getUserById(@PathVariable("id") Long id);

@PutMapping("/users/{id}")
@CachePut(cacheNames = "users", key = "#id") // 更新缓存
User updateUser(@PathVariable("id") Long id, @RequestBody User user);

@DeleteMapping("/users/{id}")
@CacheEvict(cacheNames = "users", key = "#id") // 删除缓存
void deleteUser(@PathVariable("id") Long id);
}

缓存配置

@Configuration
@EnableCaching
public class CacheConfig {

@Bean
public CacheManager cacheManager() {
CaffeineCacheManager cacheManager = new CaffeineCacheManager();
cacheManager.setCaffeine(Caffeine.newBuilder()
.expireAfterWrite(10, TimeUnit.MINUTES) // 写入后 10 分钟过期
.maximumSize(1000)); // 最多缓存 1000 条
return cacheManager;
}
}

HATEOAS 支持

如果服务端返回 HATEOAS 格式的响应,OpenFeign 可以自动处理。

添加依赖

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-hateoas</artifactId>
</dependency>

使用 HATEOAS 类型

@FeignClient(name = "api-service")
public interface ApiClient {

@GetMapping("/users/{id}")
EntityModel<User> getUser(@PathVariable("id") Long id);

@GetMapping("/users")
CollectionModel<User> getAllUsers();

@GetMapping("/users")
PagedModel<User> getUsers(Pageable pageable);
}

处理响应

@Service
public class UserService {

private final ApiClient apiClient;

public void processUser(Long id) {
EntityModel<User> entityModel = apiClient.getUser(id);

// 获取用户数据
User user = entityModel.getContent();

// 获取链接
Link selfLink = entityModel.getLink("self").orElse(null);
Link ordersLink = entityModel.getLink("orders").orElse(null);
}

public void processUsers() {
CollectionModel<User> collectionModel = apiClient.getAllUsers();

// 获取用户列表
Collection<User> users = collectionModel.getContent();

// 获取分页信息
PagedModel.PageMetadata metadata = ((PagedModel<User>) collectionModel).getMetadata();
}
}

Spring Data 支持

OpenFeign 支持 Spring Data 的 PageSort 类型。

添加依赖

<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-commons</artifactId>
</dependency>

使用分页

@FeignClient(name = "user-service")
public interface UserClient {

@GetMapping("/users")
Page<User> getUsers(Pageable pageable);

@GetMapping("/users")
Page<User> getUsers(
@RequestParam("page") int page,
@RequestParam("size") int size,
@RequestParam("sort") String sort
);
}

调用示例

@Service
public class UserService {

private final UserClient userClient;

public Page<User> getUsers(int page, int size) {
Pageable pageable = PageRequest.of(page, size, Sort.by("name").ascending());
return userClient.getUsers(pageable);
}
}

Micrometer 监控

OpenFeign 与 Micrometer 集成,支持指标收集和分布式追踪。

添加依赖

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

启用监控

spring:
cloud:
openfeign:
micrometer:
enabled: true

自定义监控配置

@Configuration
public class MicrometerConfig {

@Bean
public MicrometerObservationCapability micrometerObservationCapability(
ObservationRegistry registry) {
return new MicrometerObservationCapability(registry);
}
}

查看指标

访问 /actuator/metrics 端点查看 Feign 相关指标:

curl http://localhost:8080/actuator/metrics | grep feign

主要指标包括:

  • feign.Client.request:请求计数和耗时
  • feign.Client.response:响应计数和耗时
  • feign.Client.error:错误计数

异步支持

OpenFeign 本身是同步阻塞的,但可以配合 CompletableFuture 实现异步调用。

异步包装

@Service
public class AsyncUserService {

private final UserClient userClient;
private final ExecutorService executorService;

public AsyncUserService(UserClient userClient) {
this.userClient = userClient;
this.executorService = Executors.newFixedThreadPool(10);
}

public CompletableFuture<User> getUserAsync(Long id) {
return CompletableFuture.supplyAsync(
() -> userClient.getUserById(id),
executorService
);
}

public CompletableFuture<List<User>> getAllUsersAsync() {
return CompletableFuture.supplyAsync(
() -> userClient.getAllUsers(),
executorService
);
}
}

并行调用

@Service
public class ParallelService {

private final UserClient userClient;
private final OrderClient orderClient;

public CompletableFuture<OrderDetail> getOrderDetail(Long orderId) {
// 先获取订单
return CompletableFuture.supplyAsync(() -> orderClient.getOrder(orderId))
.thenCompose(order -> {
// 并行获取用户和商品信息
CompletableFuture<User> userFuture = CompletableFuture.supplyAsync(
() -> userClient.getUserById(order.getUserId()));
CompletableFuture<Product> productFuture = CompletableFuture.supplyAsync(
() -> productClient.getProduct(order.getProductId()));

// 合并结果
return userFuture.thenCombine(productFuture,
(user, product) -> new OrderDetail(order, user, product));
});
}
}

CollectionFormat 支持

OpenFeign 支持不同的集合参数格式。

使用注解

@FeignClient(name = "api-service")
public interface ApiClient {

// 默认格式:ids=1&ids=2&ids=3
@GetMapping("/users")
List<User> getUsersByIds(@RequestParam("ids") List<Long> ids);

// CSV 格式:ids=1,2,3
@CollectionFormat(CollectionFormat.CSV)
@GetMapping("/users")
List<User> getUsersByIdsCsv(@RequestParam("ids") List<Long> ids);

// TSV 格式:ids=1\t2\t3
@CollectionFormat(CollectionFormat.TSV)
@GetMapping("/users")
List<User> getUsersByIdsTsv(@RequestParam("ids") List<Long> ids);

// 管道格式:ids=1|2|3
@CollectionFormat(CollectionFormat.PIPES)
@GetMapping("/users")
List<User> getUsersByIdsPipes(@RequestParam("ids") List<Long> ids);
}

小结

本章介绍了 OpenFeign 的高级特性:

特性说明适用场景
接口继承减少重复代码多个客户端有公共操作
手动构建灵活配置客户端动态 URL、多环境
HTTP/2 客户端Java 11 原生 HTTP/2 支持高并发、减少连接数
OAuth2自动添加访问令牌需要认证的服务调用
缓存缓存调用结果读多写少的场景
HATEOAS处理超媒体响应RESTful API
Micrometer监控和追踪生产环境监控

这些高级特性可以帮助你更好地应对复杂的业务场景,提高代码的可维护性和可观测性。

下一章是性能优化与故障排查,介绍如何优化 Feign 客户端的性能和排查常见问题。