高级特性
本章将介绍 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 的 Page 和 Sort 类型。
添加依赖
<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 的常用配置和用法。