跳到主要内容

高级特性

本章将介绍 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);

OAuth2 支持

OpenFeign 内置了 OAuth2 支持,可以自动在请求中添加访问令牌。

启用 OAuth2 支持

添加依赖:

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

配置:

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

OAuth2 客户端配置

spring:
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 拦截器

@Bean
public OAuth2AccessTokenInterceptor oAuth2AccessTokenInterceptor(
OAuth2AuthorizedClientManager authorizedClientManager) {
return new OAuth2AccessTokenInterceptor("my-client", 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、多环境
OAuth2自动添加访问令牌需要认证的服务调用
缓存缓存调用结果读多写少的场景
HATEOAS处理超媒体响应RESTful API
Micrometer监控和追踪生产环境监控

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

下一章是速查表,汇总 OpenFeign 的常用配置和用法。