一、Feign 介绍
1. 什么是 Feign?
Feign 是 Spring Cloud 提供的声明式 HTTP 客户端,让服务调用变得更加简单优雅。
核心特性:
-
✅ 声明式调用:只需定义接口和注解,无需实现具体调用逻辑
-
✅ 基于注解:支持 Spring MVC 注解,学习成本低
-
✅ 集成 Ribbon:内置客户端负载均衡
-
✅ 集成 Hystrix:支持服务熔断和降级(旧版本)
-
✅ 可插拔编码器/解码器:支持自定义序列化
-
✅ 请求/响应压缩:支持 GZIP 压缩
-
✅ 日志配置:支持请求/响应日志
核心优势对比:
| 特性 | RestTemplate + Ribbon | Feign |
|---|---|---|
| 调用方式 | 编程式,硬编码 | 声明式,接口定义 |
| 代码简洁性 | 中等 | 极高 |
| 可读性 | 一般 | 优秀 |
| 维护性 | 一般 | 优秀 |
| 负载均衡 | 需要手动集成 | 内置集成 |
| 注解支持 | 有限 | 完整 Spring MVC 注解 |
一句话总结: Feign = RestTemplate + Ribbon,但更加优雅简洁!
2. Feign 依赖
<!-- Spring Cloud 2020+ 使用 OpenFeign -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<!-- 如果使用旧版本 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-feign</artifactId>
<version>1.4.7.RELEASE</version>
</dependency>
二、Feign 快速入门
1. 项目结构
springcloud-feign-demo/
├── feign-provider/ # 服务提供者
├── feign-interface/ # Feign 接口模块
├── feign-consumer/ # 服务消费者
└── pom.xml # 父工程
2. 服务提供者(Provider)
ProviderApplication.java
@SpringBootApplication
@EnableDiscoveryClient
public class FeignProviderApplication {
public static void main(String[] args) {
SpringApplication.run(FeignProviderApplication.class, args);
}
}
ProviderController.java
@RestController
@RequestMapping("/provider")
@Slf4j
public class ProviderController {
@Value("${server.port}")
private String port;
/**
* GET 请求:查询用户
*/
@GetMapping("/getUserById/{id}")
public User getUserById(@PathVariable Integer id) {
log.info("Provider [{}] 收到请求,用户ID: {}", port, id);
return User.builder()
.id(id)
.name("用户-" + id)
.age(25)
.email("user" + id + "@example.com")
.phone("1380013800" + (id % 10))
.createdAt(new Date())
.providerPort(port)
.build();
}
/**
* POST 请求:创建用户
*/
@PostMapping("/createUser")
public User createUser(@RequestBody User user) {
log.info("Provider [{}] 创建用户: {}", port, user.getName());
user.setId(new Random().nextInt(1000));
user.setCreatedAt(new Date());
user.setProviderPort(port);
return user;
}
/**
* GET 请求:搜索用户
*/
@GetMapping("/search")
public List<User> searchUsers(@RequestParam String keyword,
@RequestParam(defaultValue = "1") Integer page,
@RequestParam(defaultValue = "10") Integer size) {
log.info("Provider [{}] 搜索用户: {}, 页码: {}, 大小: {}",
port, keyword, page, size);
List<User> users = new ArrayList<>();
for (int i = 1; i <= 3; i++) {
users.add(User.builder()
.id(i)
.name(keyword + "-用户" + i)
.age(20 + i)
.providerPort(port)
.build());
}
return users;
}
}
application.yml
server:
port: 8081 # 可启动多个实例,如 8082, 8083
spring:
application:
name: feign-provider
cloud:
nacos:
discovery:
server-addr: localhost:8848
namespace: public
group: DEFAULT_GROUP
metadata:
version: 1.0.0
User.java
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class User {
private Integer id;
private String name;
private Integer age;
private String email;
private String phone;
private Date createdAt;
private String providerPort; // 用于区分不同实例
}
3. Feign 接口模块
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0">
<parent>
<artifactId>springcloud-feign-demo</artifactId>
<groupId>com.example</groupId>
<version>1.0.0</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>feign-interface</artifactId>
<dependencies>
<!-- Feign 核心依赖 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<!-- 公共模块(包含 User 类) -->
<dependency>
<groupId>com.example</groupId>
<artifactId>springcloud-common</artifactId>
<version>1.0.0</version>
</dependency>
<!-- Web 支持 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>
</project>
UserFeignClient.java
/**
* Feign 客户端接口
* 重要:接口路径与服务提供者的 Controller 路径一致
*/
@FeignClient(
value = "feign-provider", // 服务名称,对应 Nacos 中的服务
path = "/provider", // 统一前缀,避免在每个方法上重复
configuration = FeignConfig.class, // 自定义配置
fallbackFactory = UserFeignFallbackFactory.class // 降级处理
)
public interface UserFeignClient {
/**
* RESTful 风格传参
* 对应:GET /provider/getUserById/{id}
*/
@GetMapping("/getUserById/{id}")
User getUserById(@PathVariable("id") Integer id);
/**
* 查询参数传参
* 对应:GET /provider/search?keyword=xxx&page=1&size=10
*/
@GetMapping("/search")
List<User> searchUsers(
@RequestParam("keyword") String keyword,
@RequestParam(value = "page", defaultValue = "1") Integer page,
@RequestParam(value = "size", defaultValue = "10") Integer size
);
/**
* POJO 对象传参(JSON)
* 对应:POST /provider/createUser
*/
@PostMapping("/createUser")
User createUser(@RequestBody User user);
/**
* PUT 请求示例
*/
@PutMapping("/updateUser/{id}")
User updateUser(@PathVariable("id") Integer id, @RequestBody User user);
/**
* DELETE 请求示例
*/
@DeleteMapping("/deleteUser/{id}")
void deleteUser(@PathVariable("id") Integer id);
/**
* 多参数复杂查询示例
*/
@GetMapping("/complexQuery")
List<User> complexQuery(
@RequestParam String name,
@RequestParam(required = false) Integer minAge,
@RequestParam(required = false) Integer maxAge,
@RequestParam(required = false) String email
);
}
FeignConfig.java - 自定义配置
@Configuration
public class FeignConfig {
/**
* 配置日志级别
* NONE: 不记录任何日志
* BASIC: 仅记录请求方法和URL以及响应状态码和执行时间
* HEADERS: 记录BASIC级别的基础上,记录请求和响应的header
* FULL: 记录请求和响应的header、body和元数据
*/
@Bean
public Logger.Level feignLoggerLevel() {
return Logger.Level.FULL;
}
/**
* 配置超时时间
*/
@Bean
public Request.Options options() {
return new Request.Options(
5000, // 连接超时:5秒
10000 // 读取超时:10秒
);
}
/**
* 配置拦截器(添加认证头等)
*/
@Bean
public RequestInterceptor requestInterceptor() {
return requestTemplate -> {
// 添加认证头
requestTemplate.header("Authorization", "Bearer token-xxx");
// 添加自定义头
requestTemplate.header("X-Request-Source", "feign-client");
// 添加时间戳
requestTemplate.header("X-Timestamp", String.valueOf(System.currentTimeMillis()));
};
}
/**
* 配置编解码器
*/
@Bean
public Encoder feignEncoder() {
return new SpringEncoder(new ObjectFactory<>() {
@Override
public HttpMessageConverters getObject() {
return new HttpMessageConverters(
new MappingJackson2HttpMessageConverter()
);
}
});
}
/**
* 配置错误解码器
*/
@Bean
public ErrorDecoder errorDecoder() {
return new FeignErrorDecoder();
}
}
FeignErrorDecoder.java - 错误处理
@Component
public class FeignErrorDecoder implements ErrorDecoder {
private final ErrorDecoder defaultErrorDecoder = new Default();
@Override
public Exception decode(String methodKey, Response response) {
if (response.status() >= 400 && response.status() <= 499) {
// 客户端错误
return new FeignClientException(
response.status(),
"客户端错误: " + response.reason()
);
} else if (response.status() >= 500) {
// 服务端错误
return new FeignServerException(
response.status(),
"服务端错误: " + response.reason()
);
}
return defaultErrorDecoder.decode(methodKey, response);
}
// 自定义异常类
public static class FeignClientException extends RuntimeException {
private final int status;
public FeignClientException(int status, String message) {
super(message);
this.status = status;
}
public int getStatus() {
return status;
}
}
public static class FeignServerException extends RuntimeException {
private final int status;
public FeignServerException(int status, String message) {
super(message);
this.status = status;
}
public int getStatus() {
return status;
}
}
}
UserFeignFallbackFactory.java - 服务降级
@Component
@Slf4j
public class UserFeignFallbackFactory implements FallbackFactory<UserFeignClient> {
@Override
public UserFeignClient create(Throwable cause) {
log.error("Feign客户端调用失败,触发降级", cause);
return new UserFeignClient() {
@Override
public User getUserById(Integer id) {
return User.builder()
.id(id)
.name("降级用户")
.age(0)
.email("fallback@example.com")
.build();
}
@Override
public List<User> searchUsers(String keyword, Integer page, Integer size) {
return Collections.singletonList(
User.builder()
.id(-1)
.name("降级搜索结果: " + keyword)
.age(0)
.build()
);
}
@Override
public User createUser(User user) {
return User.builder()
.id(-1)
.name("创建失败,服务降级")
.build();
}
@Override
public User updateUser(Integer id, User user) {
return user;
}
@Override
public void deleteUser(Integer id) {
log.warn("删除用户失败,用户ID: {}", id);
}
@Override
public List<User> complexQuery(String name, Integer minAge, Integer maxAge, String email) {
return Collections.emptyList();
}
};
}
}
4. 服务消费者(Consumer)
ConsumerApplication.java
/**
* 启动类配置
*/
@SpringBootApplication
@EnableDiscoveryClient
@EnableFeignClients(
basePackages = "com.example.feign.client", // 指定 Feign 接口扫描包
defaultConfiguration = GlobalFeignConfig.class // 全局 Feign 配置
)
public class FeignConsumerApplication {
public static void main(String[] args) {
SpringApplication.run(FeignConsumerApplication.class, args);
}
}
ConsumerController.java
@RestController
@RequestMapping("/consumer")
@Slf4j
public class ConsumerController {
@Autowired
private UserFeignClient userFeignClient;
/**
* 测试 GET 请求
*/
@GetMapping("/user/{id}")
public ResponseData<User> getUserById(@PathVariable Integer id) {
log.info("消费者查询用户,ID: {}", id);
try {
User user = userFeignClient.getUserById(id);
return ResponseData.success(user);
} catch (Exception e) {
log.error("查询用户失败", e);
return ResponseData.error(500, "查询失败: " + e.getMessage());
}
}
/**
* 测试 POST 请求
*/
@PostMapping("/user")
public ResponseData<User> createUser(@RequestBody User user) {
log.info("消费者创建用户: {}", user.getName());
User createdUser = userFeignClient.createUser(user);
return ResponseData.success(createdUser);
}
/**
* 测试查询参数
*/
@GetMapping("/search")
public ResponseData<List<User>> searchUsers(
@RequestParam String keyword,
@RequestParam(defaultValue = "1") Integer page,
@RequestParam(defaultValue = "10") Integer size) {
log.info("消费者搜索用户,关键词: {}, 页码: {}, 大小: {}", keyword, page, size);
List<User> users = userFeignClient.searchUsers(keyword, page, size);
return ResponseData.success(users);
}
/**
* 测试负载均衡
* 调用多次查看不同实例的响应
*/
@GetMapping("/testLoadBalance")
public ResponseData<List<String>> testLoadBalance() {
List<String> results = new ArrayList<>();
for (int i = 0; i < 5; i++) {
User user = userFeignClient.getUserById(i + 1);
results.add(String.format("第 %d 次调用 -> 服务端口: %s",
i + 1, user.getProviderPort()));
// 模拟延迟
try {
Thread.sleep(500);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
return ResponseData.success(results);
}
/**
* 测试复杂查询
*/
@GetMapping("/complex")
public ResponseData<List<User>> complexQuery(
@RequestParam String name,
@RequestParam(required = false) Integer minAge,
@RequestParam(required = false) Integer maxAge) {
List<User> users = userFeignClient.complexQuery(name, minAge, maxAge, null);
return ResponseData.success(users);
}
}
ResponseData.java - 统一响应
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class ResponseData<T> {
private Integer code;
private String message;
private T data;
private Long timestamp;
public static <T> ResponseData<T> success(T data) {
return ResponseData.<T>builder()
.code(200)
.message("成功")
.data(data)
.timestamp(System.currentTimeMillis())
.build();
}
public static <T> ResponseData<T> error(Integer code, String message) {
return ResponseData.<T>builder()
.code(code)
.message(message)
.timestamp(System.currentTimeMillis())
.build();
}
}
GlobalFeignConfig.java - 全局配置
@Configuration
public class GlobalFeignConfig {
/**
* 全局 Feign 配置
*/
@Bean
public Contract feignContract() {
return new SpringMvcContract();
}
/**
* 配置连接池
*/
@Bean
public Client feignClient() {
return new Client.Default(
new PoolingHttpClientConnectionManager(),
new DefaultHttpRequestRetryHandler(3, true) // 重试3次
);
}
/**
* 配置解码器
*/
@Bean
public Decoder feignDecoder() {
return new ResponseEntityDecoder(new SpringDecoder(feignHttpMessageConverter()));
}
private ObjectFactory<HttpMessageConverters> feignHttpMessageConverter() {
return () -> new HttpMessageConverters(
new StringHttpMessageConverter(StandardCharsets.UTF_8),
new MappingJackson2HttpMessageConverter()
);
}
}
application.yml
server:
port: 8080
spring:
application:
name: feign-consumer
cloud:
nacos:
discovery:
server-addr: localhost:8848
# Feign 配置
feign:
client:
config:
default: # 全局默认配置
connectTimeout: 5000 # 连接超时时间
readTimeout: 10000 # 读取超时时间
loggerLevel: full # 日志级别
requestInterceptors: # 拦截器
- com.example.feign.interceptor.AuthRequestInterceptor
feign-provider: # 针对特定服务的配置
connectTimeout: 3000
readTimeout: 5000
# 启用压缩
compression:
request:
enabled: true
mime-types: text/xml,application/xml,application/json
min-request-size: 2048
response:
enabled: true
# 启用 Hystrix(旧版本)
hystrix:
enabled: true
# 启用 Sentinel(新版本推荐)
sentinel:
enabled: true
# 日志配置
logging:
level:
com.example.feign.client.UserFeignClient: DEBUG # Feign 接口日志
三、Feign 高级功能
1. 文件上传
@FeignClient(name = "file-service")
public interface FileUploadFeignClient {
/**
* 单文件上传
*/
@PostMapping(value = "/upload", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
String uploadFile(@RequestPart("file") MultipartFile file);
/**
* 多文件上传
*/
@PostMapping(value = "/uploadMultiple", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
String uploadFiles(@RequestPart("files") MultipartFile[] files);
/**
* 文件 + 参数上传
*/
@PostMapping(value = "/uploadWithParam", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
String uploadFileWithParam(
@RequestPart("file") MultipartFile file,
@RequestParam("description") String description
);
}
2. 自定义请求头
@FeignClient(name = "auth-service")
public interface AuthFeignClient {
/**
* 动态请求头
*/
@GetMapping("/userInfo")
UserInfo getUserInfo(@RequestHeader("Authorization") String token);
/**
* 多个请求头
*/
@PostMapping("/auth")
AuthResult authenticate(
@RequestHeader("X-Client-Id") String clientId,
@RequestHeader("X-Client-Secret") String clientSecret,
@RequestBody AuthRequest request
);
}
3. 继承特性
// 基础接口
public interface BaseFeignClient<T> {
@GetMapping("/{id}")
T getById(@PathVariable("id") Long id);
@PostMapping
T create(@RequestBody T entity);
@PutMapping("/{id}")
T update(@PathVariable("id") Long id, @RequestBody T entity);
@DeleteMapping("/{id}")
void delete(@PathVariable("id") Long id);
}
// 具体实现
@FeignClient(name = "user-service")
public interface UserFeignClient extends BaseFeignClient<User> {
// 可以添加额外的方法
@GetMapping("/search")
List<User> search(@RequestParam("keyword") String keyword);
}
4. 请求/响应日志
@Configuration
@Slf4j
public class FeignLogConfig {
/**
* 自定义日志
*/
@Bean
public Logger.Level feignLoggerLevel() {
return Logger.Level.FULL;
}
/**
* 日志拦截器
*/
@Bean
public RequestInterceptor logRequestInterceptor() {
return template -> {
log.info("Feign请求URL: {}", template.url());
log.info("Feign请求方法: {}", template.method());
log.info("Feign请求头: {}", template.headers());
if (template.body() != null) {
log.info("Feign请求体: {}", new String(template.body()));
}
};
}
}
四、Feign 工作原理
1. 启动流程
/**
* @EnableFeignClients 工作流程:
* 1. 扫描被 @FeignClient 注解的接口
* 2. 为每个接口创建动态代理
* 3. 注册到 Spring 容器
* 4. 生成 RequestTemplate
* 5. 发起 HTTP 请求
*/
@EnableFeignClients
↓
FeignClientsRegistrar.registerFeignClients()
↓
ClassPathScanningCandidateComponentProvider.scan()
↓
ReflectiveFeign.newInstance()
↓
InvocationHandler.invoke()
↓
SynchronousMethodHandler.invoke()
↓
RequestTemplate.create() // 创建请求模板
↓
Client.execute() // 执行HTTP请求
2. 动态代理实现
// Feign 通过 JDK 动态代理实现
public class ReflectiveFeign extends Feign {
public <T> T newInstance(Target<T> target) {
Map<String, MethodHandler> methodToHandler = new LinkedHashMap<>();
for (Method method : target.type().getMethods()) {
if (method.getDeclaringClass() == Object.class) {
continue;
}
// 创建方法处理器
methodToHandler.put(method.getName(),
new SynchronousMethodHandler(target, method));
}
// 创建动态代理
InvocationHandler handler = (proxy, method, args) -> {
MethodHandler methodHandler = methodToHandler.get(method.getName());
return methodHandler.invoke(args);
};
return (T) Proxy.newProxyInstance(
target.type().getClassLoader(),
new Class<?>[]{target.type()},
handler
);
}
}
五、Feign 传参方式详解
1. 查询参数(? 传参)
@FeignClient("user-service")
public interface UserFeign {
// 基本类型参数
@GetMapping("/users")
List<User> getUsers(@RequestParam("page") Integer page,
@RequestParam("size") Integer size);
// 可选参数
@GetMapping("/search")
List<User> search(@RequestParam(value = "keyword", required = false) String keyword);
// 默认值参数
@GetMapping("/list")
List<User> list(@RequestParam(value = "sort", defaultValue = "id") String sort);
// 数组/列表参数
@GetMapping("/byIds")
List<User> getByIds(@RequestParam("ids") List<Long> ids);
// Map参数
@GetMapping("/query")
List<User> query(@RequestParam Map<String, Object> params);
}
2. RESTful 路径参数
@FeignClient("user-service")
public interface UserFeign {
// 单个路径参数
@GetMapping("/users/{id}")
User getById(@PathVariable("id") Long id);
// 多个路径参数
@GetMapping("/departments/{deptId}/users/{userId}")
User getByDeptAndUser(@PathVariable("deptId") Long deptId,
@PathVariable("userId") Long userId);
// 正则表达式限制
@GetMapping("/users/{id:[0-9]+}")
User getByIdWithRegex(@PathVariable("id") Long id);
// 路径参数 + 查询参数
@GetMapping("/users/{id}/posts")
List<Post> getUserPosts(@PathVariable("id") Long id,
@RequestParam("status") String status);
}
3. 请求体参数(JSON)
@FeignClient("user-service")
public interface UserFeign {
// 简单对象
@PostMapping("/users")
User createUser(@RequestBody User user);
// 复杂嵌套对象
@PutMapping("/users/{id}")
User updateUser(@PathVariable("id") Long id,
@RequestBody UserDTO userDTO);
// 多个对象(不推荐,建议封装)
@PostMapping("/batch")
List<User> batchCreate(@RequestBody BatchCreateRequest request);
// 集合参数
@PostMapping("/users/batch")
List<User> createUsers(@RequestBody List<User> users);
}
4. 表单参数
@FeignClient("user-service")
public interface UserFeign {
// 表单提交
@PostMapping(value = "/login", consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE)
LoginResult login(@RequestParam("username") String username,
@RequestParam("password") String password);
// 混合参数
@PostMapping(value = "/register", consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE)
RegisterResult register(@RequestParam("username") String username,
@RequestParam("password") String password,
@RequestParam("email") String email);
}
5. 请求头参数
@FeignClient("user-service")
public interface UserFeign {
// 单个请求头
@GetMapping("/profile")
UserProfile getProfile(@RequestHeader("Authorization") String token);
// 多个请求头
@GetMapping("/secure")
SecureData getSecureData(@RequestHeader("X-API-Key") String apiKey,
@RequestHeader("X-Client-Version") String version);
// Map形式请求头
@PostMapping("/custom")
void customRequest(@RequestHeader Map<String, String> headers,
@RequestBody RequestData data);
}
6. Cookie 参数
@FeignClient("user-service")
public interface UserFeign {
@GetMapping("/session")
SessionInfo getSession(@CookieValue("JSESSIONID") String sessionId);
@PostMapping("/login")
void login(@CookieValue(value = "rememberMe", defaultValue = "false") String remember,
@RequestBody LoginRequest request);
}
六、最佳实践
1. 接口设计规范
// 1. 统一接口前缀
@FeignClient(name = "user-service", path = "/api/v1/users")
public interface UserFeignClient {
// 方法不需要重复写路径前缀
}
// 2. 统一响应格式
public interface BaseFeignClient<T, ID> {
ResponseData<T> getById(ID id);
ResponseData<List<T>> list();
ResponseData<T> create(T entity);
ResponseData<T> update(ID id, T entity);
ResponseData<Void> delete(ID id);
}
// 3. 异常处理
@ControllerAdvice
public class FeignExceptionHandler {
@ExceptionHandler(FeignException.class)
public ResponseEntity<ErrorResponse> handleFeignException(FeignException e) {
// 统一处理Feign异常
}
}
2. 性能优化配置
# application.yml
feign:
client:
config:
default:
connectTimeout: 3000
readTimeout: 10000
loggerLevel: basic
# 启用HTTP连接池
okhttp:
enabled: true
httpclient:
enabled: true
max-connections: 200
max-connections-per-route: 50
# 启用压缩
compression:
request:
enabled: true
mime-types: text/xml,application/xml,application/json
response:
enabled: true
# 启用缓存
cache:
enabled: true
cacheNames: feign-cache
ttl: 300000 # 5分钟
3. 监控与日志
@Configuration
@Slf4j
public class FeignMonitorConfig {
@Bean
public RequestInterceptor monitorInterceptor() {
return template -> {
long startTime = System.currentTimeMillis();
template.requestTemplate().attribute("startTime", startTime);
};
}
@Bean
public ResponseInterceptor responseInterceptor() {
return (response, request) -> {
Long startTime = (Long) request.requestTemplate().attribute("startTime");
if (startTime != null) {
long duration = System.currentTimeMillis() - startTime;
log.info("Feign调用耗时: {}ms, URL: {}", duration, request.url());
// 监控指标
Metrics.counter("feign_request_duration",
"url", request.url(),
"status", String.valueOf(response.status()))
.record(duration);
}
return response;
};
}
}
七、常见问题与解决方案
1. 404 错误
# 问题:服务找不到
# 解决方案:
# 1. 检查服务名是否正确
# 2. 检查服务是否注册到注册中心
# 3. 检查接口路径是否正确
# 开启详细日志
logging:
level:
org.springframework.cloud.openfeign: DEBUG
feign: DEBUG
2. 超时问题
// 方案1:配置文件调整
@Configuration
public class FeignTimeoutConfig {
@Bean
public Request.Options options() {
return new Request.Options(10000, 30000); // 连接10s,读取30s
}
}
// 方案2:使用Hystrix
@Configuration
public class HystrixConfig {
@Bean
public Setter setter() {
return Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey("FeignGroup"))
.andCommandPropertiesDefaults(HystrixCommandProperties.Setter()
.withExecutionTimeoutInMilliseconds(10000));
}
}
3. 序列化问题
// 配置自定义编解码器
@Bean
public Encoder feignEncoder() {
return new SpringEncoder(feignHttpMessageConverter());
}
@Bean
public Decoder feignDecoder() {
ObjectMapper mapper = new ObjectMapper();
mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
mapper.registerModule(new JavaTimeModule());
return new ResponseEntityDecoder(new SpringDecoder(
() -> new HttpMessageConverters(
new MappingJackson2HttpMessageConverter(mapper)
)
));
}
总结
Feign 作为声明式的 HTTP 客户端,极大地简化了微服务之间的调用。通过本指南,你应该能够:
-
✅ 理解 Feign 的核心概念和工作原理
-
✅ 掌握各种传参方式的正确使用
-
✅ 配置自定义的 Feign 客户端
-
✅ 实现服务降级和异常处理
-
✅ 优化 Feign 的性能和稳定性
-
✅ 解决常见的 Feign 使用问题
记住:Feign = 接口声明 + 注解,让服务调用变得像调用本地方法一样简单!