服务调用 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)
));
}
小结
本章我们学习了:
- OpenFeign 基本概念:声明式 HTTP 客户端
- 基本使用:定义和使用 Feign Client
- 参数传递:路径参数、查询参数、请求体、请求头
- 高级特性:继承、拦截器、错误处理
- 熔断降级:集成 Sentinel 实现服务降级
- 性能优化:连接池、压缩、超时配置