跳到主要内容

服务调用 OpenFeign

在微服务架构中,服务之间的调用是核心问题。OpenFeign 是 Spring Cloud 提供的声明式 HTTP 客户端,让服务调用变得简单优雅。

什么是 OpenFeign?

OpenFeign 是一个声明式的 Web Service 客户端,它使得编写 Web 服务客户端变得更加简单。只需要创建一个接口并添加注解,即可完成服务间的调用。

核心特点

特点说明
声明式调用通过接口和注解定义 HTTP 请求
集成 Ribbon默认集成负载均衡
集成 Sentinel支持熔断降级
支持压缩支持 GZIP 压缩
支持日志可配置请求日志级别

OpenFeign vs RestTemplate

// RestTemplate 方式
String url = "http://user-service/user/" + userId;
User user = restTemplate.getForObject(url, User.class);

// OpenFeign 方式
@FeignClient("user-service")
public interface UserClient {
@GetMapping("/user/{id}")
User getUser(@PathVariable("id") Long id);
}

// 调用
User user = userClient.getUser(userId);

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>

启用 Feign

@SpringBootApplication
@EnableFeignClients
public class OrderServiceApplication {
public static void main(String[] args) {
SpringApplication.run(OrderServiceApplication.class, args);
}
}

定义 Feign Client

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

@GetMapping("/user/{id}")
User getUserById(@PathVariable("id") Long id);

@GetMapping("/user")
List<User> getAllUsers();

@PostMapping("/user")
User createUser(@RequestBody User user);

@PutMapping("/user/{id}")
User updateUser(@PathVariable("id") Long id, @RequestBody User user);

@DeleteMapping("/user/{id}")
void deleteUser(@PathVariable("id") Long id);
}

使用 Feign Client

@Service
public class OrderService {

@Autowired
private UserClient userClient;

public Order createOrder(Long userId, List<Long> productIds) {
// 调用用户服务获取用户信息
User user = userClient.getUserById(userId);

// 创建订单逻辑
Order order = new Order();
order.setUserId(userId);
order.setUserName(user.getName());

return order;
}
}

Feign Client 配置

基本配置

feign:
client:
config:
default:
# 连接超时时间
connectTimeout: 5000
# 读取超时时间
readTimeout: 5000
# 日志级别
loggerLevel: BASIC

针对特定服务配置

feign:
client:
config:
user-service:
connectTimeout: 3000
readTimeout: 3000
loggerLevel: FULL
order-service:
connectTimeout: 10000
readTimeout: 10000

日志级别

级别说明
NONE不记录日志(默认)
BASIC仅记录请求方法、URL、响应状态码和执行时间
HEADERS记录 BASIC 级别的基础上,记录请求和响应的 header
FULL记录请求和响应的 header、body 和元数据

请求参数传递

路径参数

@GetMapping("/user/{id}")
User getUser(@PathVariable("id") Long id);

@GetMapping("/user/{userId}/order/{orderId}")
Order getOrder(@PathVariable("userId") Long userId,
@PathVariable("orderId") Long orderId);

查询参数

// 单个参数
@GetMapping("/user/search")
User findByName(@RequestParam("name") String name);

// 多个参数
@GetMapping("/user/search")
List<User> search(@RequestParam("name") String name,
@RequestParam("age") Integer age);

// 多值参数
@GetMapping("/user/search")
List<User> findByIds(@RequestParam("ids") List<Long> ids);

请求体

// JSON 请求体
@PostMapping("/user")
User createUser(@RequestBody User user);

// 表单请求
@PostMapping("/user/login")
User login(@RequestParam("username") String username,
@RequestParam("password") String password);

请求头

// 单个请求头
@GetMapping("/user/info")
User getUserInfo(@RequestHeader("Authorization") String token);

// 多个请求头
@GetMapping("/user/info")
User getUserInfo(@RequestHeader("Authorization") String token,
@RequestHeader("X-Request-Id") String requestId);

高级特性

继承支持

定义公共接口:

public interface UserService {
@GetMapping("/user/{id}")
User getUser(@PathVariable("id") Long id);

@PostMapping("/user")
User createUser(@RequestBody User user);
}

Feign Client 继承:

@FeignClient("user-service")
public interface UserClient extends UserService {
// 继承父接口的方法定义
// 可以添加额外的方法
}

服务端实现:

@RestController
public class UserController implements UserService {
@Override
public User getUser(Long id) {
return userService.getById(id);
}

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

自定义配置

@FeignClient(
name = "user-service",
url = "${user-service.url}",
configuration = UserClientConfiguration.class,
fallback = UserClientFallback.class
)
public interface UserClient {
@GetMapping("/user/{id}")
User getUser(@PathVariable("id") Long id);
}

自定义配置类:

public class UserClientConfiguration {

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

@Bean
public RequestInterceptor requestInterceptor() {
return template -> {
template.header("X-Client-Type", "feign");
template.header("X-Request-Time",
LocalDateTime.now().toString());
};
}

@Bean
public ErrorDecoder errorDecoder() {
return new CustomErrorDecoder();
}
}

请求拦截器

用于统一处理请求,如添加认证信息:

@Component
public class AuthRequestInterceptor implements RequestInterceptor {

@Override
public void apply(RequestTemplate template) {
// 从上下文获取 token
String token = SecurityContextHolder.getContext()
.getAuthentication()
.getCredentials()
.toString();

// 添加到请求头
template.header("Authorization", "Bearer " + token);
}
}

错误解码器

自定义错误处理:

public class CustomErrorDecoder implements ErrorDecoder {

@Override
public Exception decode(String methodKey, Response response) {
int status = response.status();

if (status >= 400 && status <= 499) {
return new ClientException("客户端错误: " + status);
}
if (status >= 500) {
return new ServerException("服务端错误: " + status);
}

return new Default().decode(methodKey, response);
}
}

熔断降级

OpenFeign 可以集成 Sentinel 实现熔断降级。

添加依赖

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

开启熔断

feign:
sentinel:
enabled: true

定义降级处理

@FeignClient(
name = "user-service",
fallback = UserClientFallback.class
)
public interface UserClient {
@GetMapping("/user/{id}")
User getUser(@PathVariable("id") Long id);
}

@Component
public class UserClientFallback implements UserClient {

@Override
public User getUser(Long id) {
// 降级逻辑
User user = new User();
user.setId(id);
user.setName("默认用户");
return user;
}
}

带异常信息的降级

@FeignClient(
name = "user-service",
fallbackFactory = UserClientFallbackFactory.class
)
public interface UserClient {
@GetMapping("/user/{id}")
User getUser(@PathVariable("id") Long id);
}

@Component
public class UserClientFallbackFactory implements FallbackFactory<UserClient> {

@Override
public UserClient create(Throwable cause) {
return new UserClient() {
@Override
public User getUser(Long id) {
// 可以获取异常信息
log.error("调用用户服务失败: {}", cause.getMessage());

User user = new User();
user.setId(id);
user.setName("降级用户");
return user;
}
};
}
}

性能优化

连接池配置

默认使用 JDK 的 HttpURLConnection,建议使用 OkHttp 或 Apache HttpClient。

OkHttp 配置

<dependency>
<groupId>io.github.openfeign</groupId>
<artifactId>feign-okhttp</artifactId>
</dependency>
feign:
okhttp:
enabled: true
httpclient:
enabled: false
max-connections: 200
max-connections-per-route: 50

Apache HttpClient 配置

<dependency>
<groupId>io.github.openfeign</groupId>
<artifactId>feign-httpclient</artifactId>
</dependency>
feign:
httpclient:
enabled: true
max-connections: 200
max-connections-per-route: 50
connection-timeout: 5000

GZIP 压缩

feign:
compression:
request:
enabled: true
mime-types: text/xml,application/xml,application/json
min-request-size: 2048
response:
enabled: true

超时配置

feign:
client:
config:
default:
connectTimeout: 5000
readTimeout: 10000

完整示例

用户服务

@RestController
@RequestMapping("/user")
public class UserController {

@Autowired
private UserService userService;

@GetMapping("/{id}")
public User getUser(@PathVariable Long id) {
return userService.getById(id);
}

@PostMapping
public User createUser(@RequestBody User user) {
return userService.create(user);
}

@GetMapping("/search")
public List<User> search(@RequestParam String name,
@RequestParam(required = false) Integer age) {
return userService.search(name, age);
}
}

订单服务 Feign Client

@FeignClient(
name = "user-service",
path = "/user",
fallbackFactory = UserClientFallbackFactory.class
)
public interface UserClient {

@GetMapping("/{id}")
User getUser(@PathVariable("id") Long id);

@PostMapping
User createUser(@RequestBody User user);

@GetMapping("/search")
List<User> search(@RequestParam("name") String name,
@RequestParam(value = "age", required = false) Integer age);
}

订单服务调用

@Service
public class OrderService {

@Autowired
private UserClient userClient;

public Order createOrder(CreateOrderRequest request) {
// 调用用户服务
User user = userClient.getUser(request.getUserId());
if (user == null) {
throw new BusinessException("用户不存在");
}

// 创建订单
Order order = new Order();
order.setUserId(user.getId());
order.setUserName(user.getName());
order.setTotalAmount(calculateTotal(request.getProductIds()));

return order;
}
}

常见问题

1. 调用超时

问题:服务调用超时

解决:调整超时配置

feign:
client:
config:
default:
connectTimeout: 10000
readTimeout: 30000

2. 负载均衡失效

问题:请求总是打到同一个实例

解决:确保添加了 LoadBalancer 依赖

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

3. 序列化失败

问题:日期、枚举等类型序列化失败

解决:配置自定义的编解码器

@Bean
public Encoder feignEncoder(ObjectMapper objectMapper) {
return new SpringEncoder(() -> new HttpMessageConverters(
new MappingJackson2HttpMessageConverter(objectMapper)
));
}

小结

本章我们学习了:

  1. OpenFeign 基本概念:声明式 HTTP 客户端
  2. 基本使用:定义和使用 Feign Client
  3. 参数传递:路径参数、查询参数、请求体、请求头
  4. 高级特性:继承、拦截器、错误处理
  5. 熔断降级:集成 Sentinel 实现服务降级
  6. 性能优化:连接池、压缩、超时配置

参考资源